Fixed a number of bugs, resolved some TODOs and tested the outbox APIs
Updated the join open group method to retrieve the capabilities as part of the initial request Updated the OpenGroupManager to require a transaction to be passed to the various 'handler' methods (allowing for everything to be processed within a single transaction) Fixed a few issues where we weren't storing the timestamp for open group messages and DMs which could result in duplicate messages Fixed an issue where we were setting the timestamp value for messages sent to an open group without converting it to be milliseconds to be consistent with other messages Fixed an issue where the BatchRequestInfo could incorrectly flag it's response as failing to parse even though the type was optional Fixed a bug where the open group would re-fetch all messages every other time Fixed a bug where the long press context menu wouldn't appear after failing to delete a message Fixed a bug where joining an open group would trigger the join behaviour and APIs twice
This commit is contained in:
parent
c04d4544f2
commit
8ca00ca578
|
@ -145,6 +145,7 @@
|
|||
7BDCFC0B2421EB7600641C39 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B6F509951AA53F760068F56A /* Localizable.strings */; };
|
||||
8DDB50527360BA38AE415C6F /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C5060C3B36A848B71CCE4685 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework */; };
|
||||
9B0A583E9B89FEF0916B793A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 278EF43EB1E6A0B83C9234F5 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework */; };
|
||||
9BF6299C8E265D8AC63E1D07 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit_SessionUtilitiesKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A06DA296F93403656DFA7991 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit_SessionUtilitiesKitTests.framework */; };
|
||||
A11CD70D17FA230600A2D1B1 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A11CD70C17FA230600A2D1B1 /* QuartzCore.framework */; };
|
||||
A163E8AB16F3F6AA0094D68B /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A163E8AA16F3F6A90094D68B /* Security.framework */; };
|
||||
A1C32D5017A06538000A904E /* AddressBookUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1C32D4F17A06537000A904E /* AddressBookUI.framework */; };
|
||||
|
@ -784,6 +785,14 @@
|
|||
FD705A94278D052B00F16121 /* UITableView+ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD705A93278D052B00F16121 /* UITableView+ReusableView.swift */; };
|
||||
FD705A98278E9F4D00F16121 /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD705A97278E9F4D00F16121 /* UIColor+Extensions.swift */; };
|
||||
FD83B9AA27CF149D005E1583 /* ContactUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9A927CF149D005E1583 /* ContactUtilities.swift */; };
|
||||
FD83B9B327CF200A005E1583 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; platformFilter = ios; };
|
||||
FD83B9BB27CF20AF005E1583 /* SessionIdSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9BA27CF20AF005E1583 /* SessionIdSpec.swift */; };
|
||||
FD83B9BF27CF2294005E1583 /* TestConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9BD27CF2243005E1583 /* TestConstants.swift */; };
|
||||
FD83B9C027CF2294005E1583 /* TestConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9BD27CF2243005E1583 /* TestConstants.swift */; };
|
||||
FD83B9C327CF33F7005E1583 /* ServerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9C227CF33F7005E1583 /* ServerSpec.swift */; };
|
||||
FD83B9C527CF3E2A005E1583 /* OpenGroupSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9C427CF3E2A005E1583 /* OpenGroupSpec.swift */; };
|
||||
FD83B9C727CF3F10005E1583 /* CapabilitiesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9C627CF3F10005E1583 /* CapabilitiesSpec.swift */; };
|
||||
FD83B9C927D0487A005E1583 /* SendDirectMessageResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9C827D0487A005E1583 /* SendDirectMessageResponse.swift */; };
|
||||
FD859EF227BF6BA200510D0C /* Data+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF127BF6BA200510D0C /* Data+Utilities.swift */; };
|
||||
FD859EF427C2F49200510D0C /* TestSodium.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF327C2F49200510D0C /* TestSodium.swift */; };
|
||||
FD859EF627C2F52C00510D0C /* TestSign.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF527C2F52C00510D0C /* TestSign.swift */; };
|
||||
|
@ -834,7 +843,7 @@
|
|||
FDC4387627B5BEF300C60D73 /* FileDownloadResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4387527B5BEF300C60D73 /* FileDownloadResponse.swift */; };
|
||||
FDC4387827B5C35400C60D73 /* SendMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4387727B5C35400C60D73 /* SendMessageRequest.swift */; };
|
||||
FDC4389227B9FFC700C60D73 /* SessionMessagingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A6F025539DE700C340D1 /* SessionMessagingKit.framework */; platformFilter = ios; };
|
||||
FDC4389A27BA002500C60D73 /* OpenGroupAPIV2Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4389927BA002500C60D73 /* OpenGroupAPIV2Tests.swift */; };
|
||||
FDC4389A27BA002500C60D73 /* OpenGroupAPISpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4389927BA002500C60D73 /* OpenGroupAPISpec.swift */; };
|
||||
FDC4389D27BA01F000C60D73 /* TestStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4389C27BA01F000C60D73 /* TestStorage.swift */; };
|
||||
FDC438A427BB107F00C60D73 /* UserBanRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438A327BB107F00C60D73 /* UserBanRequest.swift */; };
|
||||
FDC438A627BB113A00C60D73 /* UserUnbanRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438A527BB113A00C60D73 /* UserUnbanRequest.swift */; };
|
||||
|
@ -969,6 +978,13 @@
|
|||
remoteGlobalIDString = C33FD9AA255A548A00E217F9;
|
||||
remoteInfo = SignalUtilitiesKit;
|
||||
};
|
||||
FD83B9B427CF200A005E1583 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = D221A080169C9E5E00537ABF /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = C3C2A678255388CC00C340D1;
|
||||
remoteInfo = SessionUtilitiesKit;
|
||||
};
|
||||
FDC4386E27B4E90300C60D73 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = D221A080169C9E5E00537ABF /* Project object */;
|
||||
|
@ -1240,6 +1256,7 @@
|
|||
9B3329176C10E9640865E65B /* Pods-GlobalDependencies-Session.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-Session.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-Session/Pods-GlobalDependencies-Session.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
9B533A9FA46206D3D99C9ADA /* Pods-SignalMessaging.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalMessaging.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SignalMessaging/Pods-SignalMessaging.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
9C0469AC557930C01552CC83 /* Pods-SignalUtilitiesKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalUtilitiesKit.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SignalUtilitiesKit/Pods-SignalUtilitiesKit.app store release.xcconfig"; sourceTree = "<group>"; };
|
||||
A06DA296F93403656DFA7991 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit_SessionUtilitiesKitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit_SessionUtilitiesKitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
A0FB43B511403A5FAFAC88B8 /* Pods-SessionMessagingKitTests.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionMessagingKitTests.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SessionMessagingKitTests/Pods-SessionMessagingKitTests.app store release.xcconfig"; sourceTree = "<group>"; };
|
||||
A11CD70C17FA230600A2D1B1 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
|
||||
A163E8AA16F3F6A90094D68B /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
|
||||
|
@ -1249,7 +1266,9 @@
|
|||
A5509EC91A69AB8B00ABA4BC /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = "<group>"; };
|
||||
A5C037C0D2746ABEE2684E70 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SignalUtilitiesKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SignalUtilitiesKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SignalUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SignalUtilitiesKit.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
A6344D429FFAC3B44E6A06FA /* Pods-SessionSnodeKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionSnodeKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SessionSnodeKit/Pods-SessionSnodeKit.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
A87BC4AF5BA3E3DE713B08E5 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.app store release.xcconfig"; sourceTree = "<group>"; };
|
||||
A9F14F620D87A5BA98DDB608 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionShareExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionShareExtension/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionShareExtension.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
AAC5927BC89F6F265332C324 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
AD2AB1207E8888E4262D781B /* Pods-SignalTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SignalTests/Pods-SignalTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
ADF724B347C8815D97258101 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SignalUtilitiesKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SignalUtilitiesKit.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SignalUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SignalUtilitiesKit.app store release.xcconfig"; sourceTree = "<group>"; };
|
||||
AEA8083C060FF9BAFF6E0C9F /* Pods-SessionProtocolKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionProtocolKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SessionProtocolKit/Pods-SessionProtocolKit.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
|
@ -1921,6 +1940,13 @@
|
|||
FD705A93278D052B00F16121 /* UITableView+ReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+ReusableView.swift"; sourceTree = "<group>"; };
|
||||
FD705A97278E9F4D00F16121 /* UIColor+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extensions.swift"; sourceTree = "<group>"; };
|
||||
FD83B9A927CF149D005E1583 /* ContactUtilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactUtilities.swift; sourceTree = "<group>"; };
|
||||
FD83B9AF27CF200A005E1583 /* SessionUtilitiesKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SessionUtilitiesKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
FD83B9BA27CF20AF005E1583 /* SessionIdSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionIdSpec.swift; sourceTree = "<group>"; };
|
||||
FD83B9BD27CF2243005E1583 /* TestConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestConstants.swift; sourceTree = "<group>"; };
|
||||
FD83B9C227CF33F7005E1583 /* ServerSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSpec.swift; sourceTree = "<group>"; };
|
||||
FD83B9C427CF3E2A005E1583 /* OpenGroupSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupSpec.swift; sourceTree = "<group>"; };
|
||||
FD83B9C627CF3F10005E1583 /* CapabilitiesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapabilitiesSpec.swift; sourceTree = "<group>"; };
|
||||
FD83B9C827D0487A005E1583 /* SendDirectMessageResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendDirectMessageResponse.swift; sourceTree = "<group>"; };
|
||||
FD859EEF27BF207700510D0C /* SessionProtos.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = SessionProtos.proto; sourceTree = "<group>"; };
|
||||
FD859EF027BF207C00510D0C /* WebSocketResources.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = WebSocketResources.proto; sourceTree = "<group>"; };
|
||||
FD859EF127BF6BA200510D0C /* Data+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Utilities.swift"; sourceTree = "<group>"; };
|
||||
|
@ -1973,7 +1999,7 @@
|
|||
FDC4387527B5BEF300C60D73 /* FileDownloadResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileDownloadResponse.swift; sourceTree = "<group>"; };
|
||||
FDC4387727B5C35400C60D73 /* SendMessageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMessageRequest.swift; sourceTree = "<group>"; };
|
||||
FDC4388E27B9FFC700C60D73 /* SessionMessagingKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SessionMessagingKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
FDC4389927BA002500C60D73 /* OpenGroupAPIV2Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupAPIV2Tests.swift; sourceTree = "<group>"; };
|
||||
FDC4389927BA002500C60D73 /* OpenGroupAPISpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupAPISpec.swift; sourceTree = "<group>"; };
|
||||
FDC4389C27BA01F000C60D73 /* TestStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestStorage.swift; sourceTree = "<group>"; };
|
||||
FDC438A327BB107F00C60D73 /* UserBanRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserBanRequest.swift; sourceTree = "<group>"; };
|
||||
FDC438A527BB113A00C60D73 /* UserUnbanRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserUnbanRequest.swift; sourceTree = "<group>"; };
|
||||
|
@ -2109,6 +2135,15 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
FD83B9AC27CF200A005E1583 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
FD83B9B327CF200A005E1583 /* SessionUtilitiesKit.framework in Frameworks */,
|
||||
9BF6299C8E265D8AC63E1D07 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit_SessionUtilitiesKitTests.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
FDC4388B27B9FFC700C60D73 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -2329,6 +2364,8 @@
|
|||
C8153B96A292A25045BE2C54 /* Pods-SessionTests.app store release.xcconfig */,
|
||||
949F269926ABA08C125DCA9D /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.debug.xcconfig */,
|
||||
0208C84C4D15048D699BEC10 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.app store release.xcconfig */,
|
||||
AAC5927BC89F6F265332C324 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.debug.xcconfig */,
|
||||
A87BC4AF5BA3E3DE713B08E5 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.app store release.xcconfig */,
|
||||
);
|
||||
name = Pods;
|
||||
sourceTree = "<group>";
|
||||
|
@ -3722,7 +3759,9 @@
|
|||
C3C2A6F125539DE700C340D1 /* SessionMessagingKit */,
|
||||
C3C2A5A0255385C100C340D1 /* SessionSnodeKit */,
|
||||
C3C2A67A255388CC00C340D1 /* SessionUtilitiesKit */,
|
||||
FD83B9BC27CF2215005E1583 /* SharedTest */,
|
||||
FDC4388F27B9FFC700C60D73 /* SessionMessagingKitTests */,
|
||||
FD83B9B027CF200A005E1583 /* SessionUtilitiesKitTests */,
|
||||
D221A08C169C9E5E00537ABF /* Frameworks */,
|
||||
D221A08A169C9E5E00537ABF /* Products */,
|
||||
9404664EC513585B05DF1350 /* Pods */,
|
||||
|
@ -3741,6 +3780,7 @@
|
|||
C331FF1B2558F9D300070591 /* SessionUIKit.framework */,
|
||||
C33FD9AB255A548A00E217F9 /* SignalUtilitiesKit.framework */,
|
||||
FDC4388E27B9FFC700C60D73 /* SessionMessagingKitTests.xctest */,
|
||||
FD83B9AF27CF200A005E1583 /* SessionUtilitiesKitTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
|
@ -3795,6 +3835,7 @@
|
|||
8962372EEC51D3F56FE3A68A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework */,
|
||||
D2C155B76C8483CB9A6EA9B4 /* Pods_SessionTests.framework */,
|
||||
F1E0F51F17E4443731B94D32 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework */,
|
||||
A06DA296F93403656DFA7991 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit_SessionUtilitiesKitTests.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
|
@ -3830,6 +3871,40 @@
|
|||
path = "Message Requests";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FD83B9B027CF200A005E1583 /* SessionUtilitiesKitTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FD83B9B927CF20A5005E1583 /* General */,
|
||||
);
|
||||
path = SessionUtilitiesKitTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FD83B9B927CF20A5005E1583 /* General */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FD83B9BA27CF20AF005E1583 /* SessionIdSpec.swift */,
|
||||
);
|
||||
path = General;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FD83B9BC27CF2215005E1583 /* SharedTest */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FD83B9BD27CF2243005E1583 /* TestConstants.swift */,
|
||||
);
|
||||
path = SharedTest;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FD83B9C127CF33EE005E1583 /* Models */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FD83B9C227CF33F7005E1583 /* ServerSpec.swift */,
|
||||
FD83B9C427CF3E2A005E1583 /* OpenGroupSpec.swift */,
|
||||
FD83B9C627CF3F10005E1583 /* CapabilitiesSpec.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FD88BAD727A7438E00BBC442 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -3868,6 +3943,7 @@
|
|||
FDC4386227B4D94E00C60D73 /* OGMessage.swift */,
|
||||
FDC438C627BB6DF000C60D73 /* DirectMessage.swift */,
|
||||
FDC438C827BB706500C60D73 /* SendDirectMessageRequest.swift */,
|
||||
FD83B9C827D0487A005E1583 /* SendDirectMessageResponse.swift */,
|
||||
FDC438A327BB107F00C60D73 /* UserBanRequest.swift */,
|
||||
FDC438A527BB113A00C60D73 /* UserUnbanRequest.swift */,
|
||||
FDC438A927BB12BB00C60D73 /* UserModeratorRequest.swift */,
|
||||
|
@ -3940,7 +4016,8 @@
|
|||
FDC4389827BA001800C60D73 /* Open Groups */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FDC4389927BA002500C60D73 /* OpenGroupAPIV2Tests.swift */,
|
||||
FD83B9C127CF33EE005E1583 /* Models */,
|
||||
FDC4389927BA002500C60D73 /* OpenGroupAPISpec.swift */,
|
||||
);
|
||||
path = "Open Groups";
|
||||
sourceTree = "<group>";
|
||||
|
@ -4305,6 +4382,26 @@
|
|||
productReference = D221A089169C9E5E00537ABF /* Session.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
FD83B9AE27CF200A005E1583 /* SessionUtilitiesKitTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = FD83B9B627CF200A005E1583 /* Build configuration list for PBXNativeTarget "SessionUtilitiesKitTests" */;
|
||||
buildPhases = (
|
||||
96A0691CEB0B517292629903 /* [CP] Check Pods Manifest.lock */,
|
||||
FD83B9AB27CF200A005E1583 /* Sources */,
|
||||
FD83B9AC27CF200A005E1583 /* Frameworks */,
|
||||
FD83B9AD27CF200A005E1583 /* Resources */,
|
||||
71F2A4CB38075EC3A9D35AE3 /* [CP] Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
FD83B9B527CF200A005E1583 /* PBXTargetDependency */,
|
||||
);
|
||||
name = SessionUtilitiesKitTests;
|
||||
productName = SessionUtilitiesKitTests;
|
||||
productReference = FD83B9AF27CF200A005E1583 /* SessionUtilitiesKitTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
FDC4388D27B9FFC700C60D73 /* SessionMessagingKitTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = FDC4389527B9FFC700C60D73 /* Build configuration list for PBXNativeTarget "SessionMessagingKitTests" */;
|
||||
|
@ -4423,6 +4520,9 @@
|
|||
};
|
||||
};
|
||||
};
|
||||
FD83B9AE27CF200A005E1583 = {
|
||||
CreatedOnToolsVersion = 13.2.1;
|
||||
};
|
||||
FDC4388D27B9FFC700C60D73 = {
|
||||
CreatedOnToolsVersion = 13.2.1;
|
||||
};
|
||||
|
@ -4471,6 +4571,7 @@
|
|||
C3C2A59E255385C100C340D1 /* SessionSnodeKit */,
|
||||
C3C2A678255388CC00C340D1 /* SessionUtilitiesKit */,
|
||||
FDC4388D27B9FFC700C60D73 /* SessionMessagingKitTests */,
|
||||
FD83B9AE27CF200A005E1583 /* SessionUtilitiesKitTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
@ -4595,6 +4696,13 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
FD83B9AD27CF200A005E1583 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
FDC4388C27B9FFC700C60D73 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -4702,6 +4810,23 @@
|
|||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
71F2A4CB38075EC3A9D35AE3 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
7D43E8AB603234C5ADEF2812 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -4763,6 +4888,28 @@
|
|||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
96A0691CEB0B517292629903 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
A067C0B8A52FC6C6FDA49939 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -5291,6 +5438,7 @@
|
|||
FDC4386B27B4E88F00C60D73 /* BatchRequestInfo.swift in Sources */,
|
||||
FDC4385127B4807400C60D73 /* QueryParam.swift in Sources */,
|
||||
C32C5B62256DC333003C73A2 /* OWSDisappearingConfigurationUpdateInfoMessage.m in Sources */,
|
||||
FD83B9C927D0487A005E1583 /* SendDirectMessageResponse.swift in Sources */,
|
||||
C352A2F525574B4700338F3E /* Job.swift in Sources */,
|
||||
FDC4385727B484B700C60D73 /* LegacyFileUploadResponse.swift in Sources */,
|
||||
FDC4385B27B485DE00C60D73 /* LegacyFileDownloadResponse.swift in Sources */,
|
||||
|
@ -5477,15 +5625,28 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
FD83B9AB27CF200A005E1583 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
FD83B9BF27CF2294005E1583 /* TestConstants.swift in Sources */,
|
||||
FD83B9BB27CF20AF005E1583 /* SessionIdSpec.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
FDC4388A27B9FFC700C60D73 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
FD859EFA27C2F5C500510D0C /* TestGenericHash.swift in Sources */,
|
||||
FD83B9C327CF33F7005E1583 /* ServerSpec.swift in Sources */,
|
||||
FD83B9C727CF3F10005E1583 /* CapabilitiesSpec.swift in Sources */,
|
||||
FD859EF827C2F58900510D0C /* TestAeadXChaCha20Poly1305Ietf.swift in Sources */,
|
||||
FD859EF427C2F49200510D0C /* TestSodium.swift in Sources */,
|
||||
FD859EFC27C2F60700510D0C /* TestEd25519.swift in Sources */,
|
||||
FDC4389A27BA002500C60D73 /* OpenGroupAPIV2Tests.swift in Sources */,
|
||||
FD83B9C027CF2294005E1583 /* TestConstants.swift in Sources */,
|
||||
FDC4389A27BA002500C60D73 /* OpenGroupAPISpec.swift in Sources */,
|
||||
FD83B9C527CF3E2A005E1583 /* OpenGroupSpec.swift in Sources */,
|
||||
FDC438BD27BB2AB400C60D73 /* Mockable.swift in Sources */,
|
||||
FD859EF627C2F52C00510D0C /* TestSign.swift in Sources */,
|
||||
FDC4389D27BA01F000C60D73 /* TestStorage.swift in Sources */,
|
||||
|
@ -5575,6 +5736,12 @@
|
|||
target = C33FD9AA255A548A00E217F9 /* SignalUtilitiesKit */;
|
||||
targetProxy = C3D90A7025773A44002C9DF5 /* PBXContainerItemProxy */;
|
||||
};
|
||||
FD83B9B527CF200A005E1583 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
platformFilter = ios;
|
||||
target = C3C2A678255388CC00C340D1 /* SessionUtilitiesKit */;
|
||||
targetProxy = FD83B9B427CF200A005E1583 /* PBXContainerItemProxy */;
|
||||
};
|
||||
FDC4386F27B4E90300C60D73 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = C3C2A678255388CC00C340D1 /* SessionUtilitiesKit */;
|
||||
|
@ -6936,6 +7103,112 @@
|
|||
};
|
||||
name = "App Store Release";
|
||||
};
|
||||
FD83B9B727CF200A005E1583 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = AAC5927BC89F6F265332C324 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.oxen.SessionUtilitiesKitTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
FD83B9B827CF200A005E1583 /* App Store Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = A87BC4AF5BA3E3DE713B08E5 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.app store release.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.oxen.SessionUtilitiesKitTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = "App Store Release";
|
||||
};
|
||||
FDC4389627B9FFC700C60D73 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 949F269926ABA08C125DCA9D /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.debug.xcconfig */;
|
||||
|
@ -7126,6 +7399,15 @@
|
|||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = "App Store Release";
|
||||
};
|
||||
FD83B9B627CF200A005E1583 /* Build configuration list for PBXNativeTarget "SessionUtilitiesKitTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
FD83B9B727CF200A005E1583 /* Debug */,
|
||||
FD83B9B827CF200A005E1583 /* App Store Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = "App Store Release";
|
||||
};
|
||||
FDC4389527B9FFC700C60D73 /* Build configuration list for PBXNativeTarget "SessionMessagingKitTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
|
|
@ -105,7 +105,9 @@
|
|||
</CodeCoverageTargets>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
skipped = "NO"
|
||||
parallelizable = "YES"
|
||||
testExecutionOrdering = "random">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "FDC4388D27B9FFC700C60D73"
|
||||
|
@ -114,6 +116,18 @@
|
|||
ReferencedContainer = "container:Session.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
<TestableReference
|
||||
skipped = "NO"
|
||||
parallelizable = "YES"
|
||||
testExecutionOrdering = "random">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "FD83B9AE27CF200A005E1583"
|
||||
BuildableName = "SessionUtilitiesKitTests.xctest"
|
||||
BlueprintName = "SessionUtilitiesKitTests"
|
||||
ReferencedContainer = "container:Session.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
|
|
|
@ -40,7 +40,9 @@
|
|||
</CodeCoverageTargets>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
skipped = "NO"
|
||||
parallelizable = "YES"
|
||||
testExecutionOrdering = "random">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "FDC4388D27B9FFC700C60D73"
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1320"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "C3C2A678255388CC00C340D1"
|
||||
BuildableName = "SessionUtilitiesKit.framework"
|
||||
BlueprintName = "SessionUtilitiesKit"
|
||||
ReferencedContainer = "container:Session.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES"
|
||||
onlyGenerateCoverageForSpecifiedTargets = "YES">
|
||||
<CodeCoverageTargets>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "C3C2A678255388CC00C340D1"
|
||||
BuildableName = "SessionUtilitiesKit.framework"
|
||||
BlueprintName = "SessionUtilitiesKit"
|
||||
ReferencedContainer = "container:Session.xcodeproj">
|
||||
</BuildableReference>
|
||||
</CodeCoverageTargets>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO"
|
||||
parallelizable = "YES"
|
||||
testExecutionOrdering = "random">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "FD83B9AE27CF200A005E1583"
|
||||
BuildableName = "SessionUtilitiesKitTests.xctest"
|
||||
BlueprintName = "SessionUtilitiesKitTests"
|
||||
ReferencedContainer = "container:Session.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "App Store Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "C3C2A678255388CC00C340D1"
|
||||
BuildableName = "SessionUtilitiesKit.framework"
|
||||
BlueprintName = "SessionUtilitiesKit"
|
||||
ReferencedContainer = "container:Session.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "App Store Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
|
@ -490,11 +490,17 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
|
|||
|
||||
// MARK: View Item Interaction
|
||||
func handleViewItemLongPressed(_ viewItem: ConversationViewItem) {
|
||||
// Show the context menu if applicable
|
||||
guard let index = viewItems.firstIndex(where: { $0 === viewItem }),
|
||||
// Note: There seems to be some odd behaviours with how the UI and the data update and as a result the `viewItem` the
|
||||
// user interacted with can be a different instance from what is in `viewItems` (likely the data updated but the user
|
||||
// interacted with old UI which had cached the old value)
|
||||
//
|
||||
// By using an equality check on the interaction we avoid this odd behaviour
|
||||
guard let index = viewItems.firstIndex(where: { $0.interaction == viewItem.interaction }),
|
||||
let cell = messagesTableView.cellForRow(at: IndexPath(row: index, section: 0)) as? VisibleMessageCell,
|
||||
let snapshot = cell.bubbleView.snapshotView(afterScreenUpdates: false), contextMenuWindow == nil,
|
||||
!ContextMenuVC.actions(for: viewItem, delegate: self).isEmpty else { return }
|
||||
|
||||
// Show the context menu if applicable
|
||||
UIImpactFeedbackGenerator(style: .heavy).impactOccurred()
|
||||
let frame = cell.convert(cell.bubbleView.frame, to: UIApplication.shared.keyWindow!)
|
||||
let window = ContextMenuWindow()
|
||||
|
|
|
@ -72,7 +72,7 @@ final class JoinOpenGroupModal : Modal {
|
|||
|
||||
Storage.shared.write { [presentingViewController = self.presentingViewController!] transaction in
|
||||
OpenGroupManager.shared
|
||||
.add(roomToken: room, server: server, publicKey: publicKey, using: transaction)
|
||||
.add(roomToken: room, server: server, publicKey: publicKey, isConfigMessage: false, using: transaction as! YapDatabaseReadWriteTransaction)
|
||||
.done(on: DispatchQueue.main) { _ in
|
||||
let appDelegate = UIApplication.shared.delegate as! AppDelegate
|
||||
appDelegate.forceSyncConfigurationNowIfNeeded().retainUntilComplete() // FIXME: It's probably cleaner to do this inside addOpenGroup(...)
|
||||
|
|
|
@ -142,7 +142,7 @@ final class JoinOpenGroupVC : BaseVC, UIPageViewControllerDataSource, UIPageView
|
|||
ModalActivityIndicatorViewController.present(fromViewController: navigationController!, canCancel: false) { [weak self] _ in
|
||||
Storage.shared.write { transaction in
|
||||
OpenGroupManager.shared
|
||||
.add(roomToken: roomToken, server: server, publicKey: publicKey, using: transaction)
|
||||
.add(roomToken: roomToken, server: server, publicKey: publicKey, isConfigMessage: false, using: transaction as! YapDatabaseReadWriteTransaction)
|
||||
.done(on: DispatchQueue.main) { [weak self] _ in
|
||||
self?.presentingViewController?.dismiss(animated: true, completion: nil)
|
||||
let appDelegate = UIApplication.shared.delegate as! AppDelegate
|
||||
|
|
|
@ -5,9 +5,9 @@ import Foundation
|
|||
enum Header: String {
|
||||
case authorization = "Authorization"
|
||||
case contentType = "Content-Type"
|
||||
case contentDisposition = "Content-Disposition"
|
||||
|
||||
case room = "Room" // TODO: Confirm this is needed
|
||||
case fileName = "X-Filename"
|
||||
|
||||
case sogsPubKey = "X-SOGS-Pubkey"
|
||||
case sogsNonce = "X-SOGS-Nonce"
|
||||
|
|
|
@ -124,7 +124,7 @@ extension OpenGroupAPI.BatchSubResponse {
|
|||
code: try container.decode(Int32.self, forKey: .code),
|
||||
headers: try container.decode([String: String].self, forKey: .headers),
|
||||
body: body,
|
||||
failedToParseBody: (body == nil && T.self != OpenGroupAPI.NoResponse.self)
|
||||
failedToParseBody: (body == nil && T.self != OpenGroupAPI.NoResponse.self && !(T.self is ExpressibleByNilLiteral.Type))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,11 +16,11 @@ extension OpenGroupAPI {
|
|||
/// The unique integer message id
|
||||
public let id: Int64
|
||||
|
||||
/// The (blinded) Session ID of the sender of the message (null on outgoing messages)
|
||||
public let sender: String?
|
||||
/// The (blinded) Session ID of the sender of the message
|
||||
public let sender: String
|
||||
|
||||
/// The (blinded) Session ID of the recipient of the message (null on incoming message)
|
||||
public let recipient: String?
|
||||
/// The (blinded) Session ID of the recipient of the message
|
||||
public let recipient: String
|
||||
|
||||
/// Unix timestamp when the message was received by SOGS
|
||||
public let posted: TimeInterval
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
extension OpenGroupAPI {
|
||||
public struct SendDirectMessageResponse: Codable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case sender
|
||||
case recipient
|
||||
case posted = "posted_at"
|
||||
case expires = "expires_at"
|
||||
}
|
||||
|
||||
/// The unique integer message id
|
||||
public let id: Int64
|
||||
|
||||
/// The (blinded) Session ID of the sender of the message
|
||||
public let sender: String
|
||||
|
||||
/// The (blinded) Session ID of the recipient of the message
|
||||
public let recipient: String
|
||||
|
||||
/// Unix timestamp when the message was received by SOGS
|
||||
public let posted: TimeInterval
|
||||
|
||||
/// Unix timestamp when SOGS will expire and delete the message
|
||||
public let expires: TimeInterval
|
||||
}
|
||||
}
|
|
@ -228,6 +228,11 @@ public final class OpenGroupAPI: NSObject {
|
|||
}
|
||||
|
||||
/// Returns the details of a single room
|
||||
///
|
||||
/// **Note:** This is the direct request to retrieve a room so should only be called from either the `poll()` or `joinRoom()` methods, in order to call
|
||||
/// this directly remove the `@available` line and make sure to route the response of this method to the `OpenGroupManager.handlePollInfo`
|
||||
/// method to ensure things are processed correctly
|
||||
@available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead")
|
||||
public static func room(for roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, Room)> {
|
||||
let request: Request = Request<NoBody>(
|
||||
server: server,
|
||||
|
@ -246,7 +251,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
/// **Note:** This is the direct request to retrieve room updates so should be retrieved automatically from the `poll()` method, in order to call
|
||||
/// this directly remove the `@available` line and make sure to route the response of this method to the `OpenGroupManager.handlePollInfo`
|
||||
/// method to ensure things are processed correctly
|
||||
@available(*, unavailable, message: "Avoid using this directly, use the pre-build `poll()` method instead")
|
||||
@available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead")
|
||||
public static func roomPollInfo(lastUpdated: Int64, for roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, RoomPollInfo)> {
|
||||
let request: Request = Request<NoBody>(
|
||||
server: server,
|
||||
|
@ -257,6 +262,66 @@ public final class OpenGroupAPI: NSObject {
|
|||
.decoded(as: RoomPollInfo.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
}
|
||||
|
||||
/// This is a convenience method which constructs a `/sequence` of the `capabilities` and `room` requests, refer to those
|
||||
/// methods for the documented behaviour of each method
|
||||
public static func capabilitiesAndRoom(
|
||||
for roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> Promise<(capabilities: (OnionRequestResponseInfoType, Capabilities?), room: (OnionRequestResponseInfoType, Room?))> {
|
||||
let requestResponseType: [BatchRequestInfoType] = [
|
||||
// Get the latest capabilities for the server (in case it's a new server or the cached ones are stale)
|
||||
BatchRequestInfo(
|
||||
request: Request<NoBody>(
|
||||
server: server,
|
||||
endpoint: .capabilities
|
||||
),
|
||||
responseType: Capabilities.self
|
||||
),
|
||||
|
||||
// And the room info
|
||||
BatchRequestInfo(
|
||||
request: Request<NoBody>(
|
||||
server: server,
|
||||
endpoint: .room(roomToken)
|
||||
),
|
||||
responseType: Room.self
|
||||
)
|
||||
]
|
||||
|
||||
return sequence(server, requests: requestResponseType, using: dependencies)
|
||||
.map { response -> (capabilities: (OnionRequestResponseInfoType, Capabilities?), room: (OnionRequestResponseInfoType, Room?)) in
|
||||
var capabilities: (OnionRequestResponseInfoType, Capabilities?)? = nil
|
||||
var room: (OnionRequestResponseInfoType, Room?)? = nil
|
||||
|
||||
try response.forEach { (endpoint: Endpoint, endpointResponse: (info: OnionRequestResponseInfoType, data: Codable?)) in
|
||||
switch endpoint {
|
||||
case .capabilities:
|
||||
guard let responseData: BatchSubResponse<Capabilities> = endpointResponse.data as? BatchSubResponse<Capabilities>, let responseBody: Capabilities = responseData.body else {
|
||||
throw Error.parsingFailed
|
||||
}
|
||||
|
||||
capabilities = (endpointResponse.info, responseBody)
|
||||
|
||||
case .room:
|
||||
guard let responseData: OpenGroupAPI.BatchSubResponse<OpenGroupAPI.Room> = endpointResponse.data as? OpenGroupAPI.BatchSubResponse<OpenGroupAPI.Room>, let responseBody: OpenGroupAPI.Room = responseData.body else {
|
||||
throw Error.parsingFailed
|
||||
}
|
||||
|
||||
room = (endpointResponse.info, responseBody)
|
||||
|
||||
default: break // No custom handling needed
|
||||
}
|
||||
}
|
||||
|
||||
guard let capabilities: (OnionRequestResponseInfoType, Capabilities?) = capabilities, let room: (OnionRequestResponseInfoType, Room?) = room else {
|
||||
throw Error.parsingFailed
|
||||
}
|
||||
|
||||
return (capabilities, room)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Messages
|
||||
|
||||
/// Posts a new message to a room
|
||||
|
@ -289,6 +354,15 @@ public final class OpenGroupAPI: NSObject {
|
|||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: Message.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
.map { response, message in
|
||||
// Store the 'message.posted' timestamp to prevent the sent message getting duplicated when it is later retrieved
|
||||
dependencies.storage.write { transaction in
|
||||
// The `posted` value is in seconds but we sent it in ms so need that for de-duping
|
||||
dependencies.storage.addReceivedMessageTimestamp(UInt64(floor(message.posted * 1000)), using: transaction)
|
||||
}
|
||||
|
||||
return (response, message)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a single message by ID
|
||||
|
@ -334,7 +408,6 @@ public final class OpenGroupAPI: NSObject {
|
|||
return send(request, using: dependencies)
|
||||
}
|
||||
|
||||
// TODO: Need to test this once the API has been implemented
|
||||
public static func messageDelete(
|
||||
_ id: Int64,
|
||||
in roomToken: String,
|
||||
|
@ -347,19 +420,13 @@ public final class OpenGroupAPI: NSObject {
|
|||
endpoint: .roomMessageIndividual(roomToken, id: id)
|
||||
)
|
||||
|
||||
// TODO: Handle custom response info? Need to let the OpenGroupManager know to delete the message?
|
||||
// TODO: !!!! This is currently broken - looks like there isn't currently a DELETE endpoint (but there should be)
|
||||
return send(request, using: dependencies)
|
||||
.map { response in
|
||||
print("RAWR")
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
/// **Note:** This is the direct request to retrieve recent messages so should be retrieved automatically from the `poll()` method, in order to call
|
||||
/// this directly remove the `@available` line and make sure to route the response of this method to the `OpenGroupManager.handleMessages`
|
||||
/// method to ensure things are processed correctly
|
||||
@available(*, unavailable, message: "Avoid using this directly, use the pre-build `poll()` method instead")
|
||||
@available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead")
|
||||
public static func recentMessages(in roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, [Message])> {
|
||||
let request: Request = Request<NoBody>(
|
||||
server: server,
|
||||
|
@ -375,7 +442,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
/// **Note:** This is the direct request to retrieve recent messages before a given message and is currently unused, in order to call this directly
|
||||
/// remove the `@available` line and make sure to route the response of this method to the `OpenGroupManager.handleMessages`
|
||||
/// method to ensure things are processed correctly
|
||||
@available(*, unavailable, message: "Avoid using this directly, use the pre-build `poll()` method instead")
|
||||
@available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead")
|
||||
public static func messagesBefore(messageId: Int64, in roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, [Message])> {
|
||||
// TODO: Do we need to be able to load old messages?
|
||||
let request: Request = Request<NoBody>(
|
||||
|
@ -392,7 +459,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
/// **Note:** This is the direct request to retrieve messages since a given message `seqNo` so should be retrieved automatically from the
|
||||
/// `poll()` method, in order to call this directly remove the `@available` line and make sure to route the response of this method to the
|
||||
/// `OpenGroupManager.handleMessages` method to ensure things are processed correctly
|
||||
@available(*, unavailable, message: "Avoid using this directly, use the pre-build `poll()` method instead")
|
||||
@available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead")
|
||||
public static func messagesSince(seqNo: Int64, in roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, [Message])> {
|
||||
let request: Request = Request<NoBody>(
|
||||
server: server,
|
||||
|
@ -463,7 +530,12 @@ public final class OpenGroupAPI: NSObject {
|
|||
method: .post,
|
||||
server: server,
|
||||
endpoint: .roomFile(roomToken),
|
||||
headers: [ .fileName: fileName ].compactMapValues { $0 },
|
||||
headers: [
|
||||
.contentDisposition: [ "attachment", fileName.map { "filename=\"\($0)\"" } ]
|
||||
.compactMap{ $0 }
|
||||
.joined(separator: "; "),
|
||||
.contentType: "application/octet-stream"
|
||||
],
|
||||
body: bytes
|
||||
)
|
||||
|
||||
|
@ -478,7 +550,11 @@ public final class OpenGroupAPI: NSObject {
|
|||
method: .post,
|
||||
server: server,
|
||||
endpoint: .roomFileJson(roomToken),
|
||||
headers: [ .fileName: fileName ].compactMapValues { $0 },
|
||||
headers: [
|
||||
.contentDisposition: [ "attachment", fileName.map { "filename=\"\($0)\"" } ]
|
||||
.compactMap{ $0 }
|
||||
.joined(separator: "; "),
|
||||
],
|
||||
body: base64EncodedString
|
||||
)
|
||||
|
||||
|
@ -517,7 +593,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
/// **Note:** This is the direct request to retrieve DMs for a specific Open Group so should be retrieved automatically from the `poll()`
|
||||
/// method, in order to call this directly remove the `@available` line and make sure to route the response of this method to the
|
||||
/// `OpenGroupManager.handleDirectMessages` method to ensure things are processed correctly
|
||||
@available(*, unavailable, message: "Avoid using this directly, use the pre-build `poll()` method instead")
|
||||
@available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead")
|
||||
public static func inbox(on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, [DirectMessage]?)> {
|
||||
let request: Request = Request<NoBody>(
|
||||
server: server,
|
||||
|
@ -533,7 +609,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
/// **Note:** This is the direct request to retrieve messages requests for a specific Open Group since a given messages so should be retrieved
|
||||
/// automatically from the `poll()` method, in order to call this directly remove the `@available` line and make sure to route the response
|
||||
/// of this method to the `OpenGroupManager.handleDirectMessages` method to ensure things are processed correctly
|
||||
@available(*, unavailable, message: "Avoid using this directly, use the pre-build `poll()` method instead")
|
||||
@available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead")
|
||||
public static func inboxSince(id: Int64, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, [DirectMessage]?)> {
|
||||
let request: Request = Request<NoBody>(
|
||||
server: server,
|
||||
|
@ -547,7 +623,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
/// Delivers a direct message to a user via their blinded Session ID
|
||||
///
|
||||
/// The body of this request is a JSON object containing a message key with a value of the encrypted-then-base64-encoded message to deliver
|
||||
public static func send(_ ciphertext: Data, toInboxFor blindedSessionId: String, on server: String/*, with serverPublicKey: String*/, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, Data?)> {
|
||||
public static func send(_ ciphertext: Data, toInboxFor blindedSessionId: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, SendDirectMessageResponse)> {
|
||||
let requestBody: SendDirectMessageRequest = SendDirectMessageRequest(
|
||||
message: ciphertext
|
||||
)
|
||||
|
@ -560,6 +636,16 @@ public final class OpenGroupAPI: NSObject {
|
|||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: SendDirectMessageResponse.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
.map { response, message in
|
||||
// Store the 'message.posted' timestamp to prevent the sent message getting duplicated when it is later retrieved
|
||||
dependencies.storage.write { transaction in
|
||||
// The `posted` value is in seconds but we sent it in ms so need that for de-duping
|
||||
dependencies.storage.addReceivedMessageTimestamp(UInt64(floor(message.posted * 1000)), using: transaction)
|
||||
}
|
||||
|
||||
return (response, message)
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves all of the user's sent DMs (up to limit)
|
||||
|
@ -567,7 +653,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
/// **Note:** This is the direct request to retrieve DMs sent by the user for a specific Open Group so should be retrieved automatically
|
||||
/// from the `poll()` method, in order to call this directly remove the `@available` line and make sure to route the response of
|
||||
/// this method to the `OpenGroupManager.handleDirectMessages` method to ensure things are processed correctly
|
||||
@available(*, unavailable, message: "Avoid using this directly, use the pre-build `poll()` method instead")
|
||||
@available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead")
|
||||
public static func outbox(on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, [DirectMessage]?)> {
|
||||
let request: Request = Request<NoBody>(
|
||||
server: server,
|
||||
|
@ -583,7 +669,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
/// **Note:** This is the direct request to retrieve messages requests sent by the user for a specific Open Group since a given messages so
|
||||
/// should be retrieved automatically from the `poll()` method, in order to call this directly remove the `@available` line and make sure
|
||||
/// to route the response of this method to the `OpenGroupManager.handleDirectMessages` method to ensure things are processed correctly
|
||||
@available(*, unavailable, message: "Avoid using this directly, use the pre-build `poll()` method instead")
|
||||
@available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead")
|
||||
public static func outboxSince(id: Int64, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, [DirectMessage]?)> {
|
||||
let request: Request = Request<NoBody>(
|
||||
server: server,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import PromiseKit
|
||||
import Sodium
|
||||
import SessionUtilitiesKit
|
||||
import SessionSnodeKit
|
||||
|
||||
@objc(SNOpenGroupManager)
|
||||
public final class OpenGroupManager: NSObject {
|
||||
|
@ -40,28 +41,54 @@ public final class OpenGroupManager: NSObject {
|
|||
|
||||
// MARK: - Adding & Removing
|
||||
|
||||
public func add(roomToken: String, server: String, publicKey: String, using transaction: Any) -> Promise<Void> {
|
||||
let storage = Storage.shared
|
||||
public func add(roomToken: String, server: String, publicKey: String, isConfigMessage: Bool, using transaction: YapDatabaseReadWriteTransaction, dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()) -> Promise<Void> {
|
||||
// If we are currently polling for this server and already have a TSGroupThread for this room the do nothing
|
||||
let groupId: Data = LKGroupUtilities.getEncodedOpenGroupIDAsData("\(server).\(roomToken)")
|
||||
|
||||
if OpenGroupManager.shared.pollers[server] != nil && TSGroupThread.fetch(uniqueId: TSGroupThread.threadId(fromGroupId: groupId), transaction: transaction) != nil {
|
||||
SNLog("Ignoring join open group attempt, user initiated: \(!isConfigMessage)")
|
||||
return Promise.value(())
|
||||
}
|
||||
|
||||
// Clear any existing data if needed
|
||||
storage.removeOpenGroupSequenceNumber(for: roomToken, on: server, using: transaction)
|
||||
dependencies.storage.removeOpenGroupSequenceNumber(for: roomToken, on: server, using: transaction)
|
||||
|
||||
// Store the public key
|
||||
storage.setOpenGroupPublicKey(for: server, to: publicKey, using: transaction)
|
||||
dependencies.storage.setOpenGroupPublicKey(for: server, to: publicKey, using: transaction)
|
||||
|
||||
let (promise, seal) = Promise<Void>.pending()
|
||||
let transaction = transaction as! YapDatabaseReadWriteTransaction
|
||||
|
||||
transaction.addCompletionQueue(DispatchQueue.global(qos: .userInitiated)) {
|
||||
OpenGroupAPI.room(for: roomToken, on: server)
|
||||
.done(on: DispatchQueue.global(qos: .userInitiated)) { _, room in
|
||||
OpenGroupManager.handleRoom(
|
||||
room,
|
||||
publicKey: publicKey,
|
||||
for: roomToken,
|
||||
on: server
|
||||
) {
|
||||
seal.fulfill(())
|
||||
OpenGroupAPI.capabilitiesAndRoom(for: roomToken, on: server, using: dependencies)
|
||||
.done(on: DispatchQueue.global(qos: .userInitiated)) { (capabilitiesResponse: (info: OnionRequestResponseInfoType, data: OpenGroupAPI.Capabilities?), roomResponse: (info: OnionRequestResponseInfoType, data: OpenGroupAPI.Room?)) in
|
||||
guard let capabilities: OpenGroupAPI.Capabilities = capabilitiesResponse.data, let room: OpenGroupAPI.Room = roomResponse.data else {
|
||||
SNLog("Failed to join open group due to invalid data.")
|
||||
seal.reject(OpenGroupAPI.Error.generic)
|
||||
return
|
||||
}
|
||||
|
||||
dependencies.storage.write { anyTransactionas in
|
||||
guard let transaction: YapDatabaseReadWriteTransaction = anyTransactionas as? YapDatabaseReadWriteTransaction else { return }
|
||||
|
||||
// Store the capabilities first
|
||||
OpenGroupManager.handleCapabilities(
|
||||
capabilities,
|
||||
on: server,
|
||||
using: transaction,
|
||||
dependencies: dependencies
|
||||
)
|
||||
|
||||
// Then the room
|
||||
OpenGroupManager.handleRoom(
|
||||
room,
|
||||
publicKey: publicKey,
|
||||
for: roomToken,
|
||||
on: server,
|
||||
using: transaction,
|
||||
dependencies: dependencies
|
||||
) {
|
||||
seal.fulfill(())
|
||||
}
|
||||
}
|
||||
}
|
||||
.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in
|
||||
|
@ -109,79 +136,15 @@ public final class OpenGroupManager: NSObject {
|
|||
internal static func handleCapabilities(
|
||||
_ capabilities: OpenGroupAPI.Capabilities,
|
||||
on server: String,
|
||||
using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()
|
||||
using transaction: YapDatabaseReadWriteTransaction,
|
||||
dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()
|
||||
) {
|
||||
dependencies.storage.write { transaction in
|
||||
let updatedServer: OpenGroupAPI.Server = OpenGroupAPI.Server(
|
||||
name: server,
|
||||
capabilities: capabilities
|
||||
)
|
||||
|
||||
dependencies.storage.setOpenGroupServer(updatedServer, using: transaction)
|
||||
}
|
||||
}
|
||||
|
||||
internal static func handleMessages(
|
||||
_ messages: [OpenGroupAPI.Message],
|
||||
for roomToken: String,
|
||||
on server: String,
|
||||
isBackgroundPoll: Bool,
|
||||
using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()
|
||||
) {
|
||||
// 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).\(roomToken)"
|
||||
let sortedMessages: [OpenGroupAPI.Message] = messages
|
||||
.sorted { lhs, rhs in lhs.id < rhs.id }
|
||||
let seqNo: Int64 = (sortedMessages.map { $0.seqNo }.max() ?? 0)
|
||||
let updatedServer: OpenGroupAPI.Server = OpenGroupAPI.Server(
|
||||
name: server,
|
||||
capabilities: capabilities
|
||||
)
|
||||
|
||||
dependencies.storage.write { transaction in
|
||||
var messageServerIDsToRemove: [UInt64] = []
|
||||
|
||||
// Update the 'openGroupSequenceNumber' value (Note: SOGS V4 uses the 'seqNo' instead of the 'serverId')
|
||||
dependencies.storage.setOpenGroupSequenceNumber(for: roomToken, on: server, to: seqNo, using: transaction)
|
||||
|
||||
// Process the messages
|
||||
sortedMessages.forEach { message in
|
||||
guard let base64EncodedString: String = message.base64EncodedData, let data = Data(base64Encoded: base64EncodedString), let sender: String = message.sender else {
|
||||
// A message with no data has been deleted so add it to the list to remove
|
||||
messageServerIDsToRemove.append(UInt64(message.id))
|
||||
return
|
||||
}
|
||||
|
||||
// Note: The `posted` value is in seconds but all messages in the database use milliseconds for timestamps
|
||||
let envelope = SNProtoEnvelope.builder(type: .sessionMessage, timestamp: UInt64(floor(message.posted * 1000)))
|
||||
envelope.setContent(data)
|
||||
envelope.setSource(sender)
|
||||
|
||||
do {
|
||||
let data = try envelope.buildSerializedData()
|
||||
let (message, proto) = try MessageReceiver.parse(data, openGroupMessageServerID: UInt64(message.id), isRetry: false, using: transaction)
|
||||
try MessageReceiver.handle(message, associatedWithProto: proto, openGroupID: openGroupID, isBackgroundPoll: isBackgroundPoll, using: transaction)
|
||||
}
|
||||
catch {
|
||||
SNLog("Couldn't receive open group message due to error: \(error).")
|
||||
}
|
||||
}
|
||||
|
||||
// Handle any deletions that are needed
|
||||
guard !messageServerIDsToRemove.isEmpty else { return }
|
||||
guard let transaction: YapDatabaseReadWriteTransaction = transaction as? YapDatabaseReadWriteTransaction else { return }
|
||||
guard let threadID = dependencies.storage.getThreadID(for: openGroupID), let thread = TSGroupThread.fetch(uniqueId: threadID, transaction: transaction) else {
|
||||
return
|
||||
}
|
||||
|
||||
var messagesToRemove: [TSMessage] = []
|
||||
|
||||
thread.enumerateInteractions(with: transaction) { interaction, stop in
|
||||
guard let message: TSMessage = interaction as? TSMessage, messageServerIDsToRemove.contains(message.openGroupServerMessageID) else {
|
||||
return
|
||||
}
|
||||
messagesToRemove.append(message)
|
||||
}
|
||||
|
||||
messagesToRemove.forEach { $0.remove(with: transaction) }
|
||||
}
|
||||
dependencies.storage.setOpenGroupServer(updatedServer, using: transaction)
|
||||
}
|
||||
|
||||
internal static func handleRoom(
|
||||
|
@ -189,7 +152,8 @@ public final class OpenGroupManager: NSObject {
|
|||
publicKey: String,
|
||||
for roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies(),
|
||||
using transaction: YapDatabaseReadWriteTransaction,
|
||||
dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies(),
|
||||
completion: (() -> ())? = nil
|
||||
) {
|
||||
OpenGroupManager.handlePollInfo(
|
||||
|
@ -197,7 +161,8 @@ public final class OpenGroupManager: NSObject {
|
|||
publicKey: publicKey,
|
||||
for: roomToken,
|
||||
on: server,
|
||||
using: dependencies,
|
||||
using: transaction,
|
||||
dependencies: dependencies,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
@ -207,7 +172,8 @@ public final class OpenGroupManager: NSObject {
|
|||
publicKey maybePublicKey: String?,
|
||||
for roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies(),
|
||||
using transaction: YapDatabaseReadWriteTransaction,
|
||||
dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies(),
|
||||
completion: (() -> ())? = nil
|
||||
) {
|
||||
// Create the open group model and get or create the thread
|
||||
|
@ -225,90 +191,148 @@ public final class OpenGroupManager: NSObject {
|
|||
var maybeUpdatedModel: TSGroupModel? = nil
|
||||
|
||||
// Store/Update everything
|
||||
dependencies.storage.write(
|
||||
with: { transaction in
|
||||
let transaction = transaction as! YapDatabaseReadWriteTransaction
|
||||
let thread = TSGroupThread.getOrCreateThread(with: initialModel, transaction: transaction)
|
||||
let existingOpenGroup: OpenGroup? = thread.uniqueId.flatMap { uniqueId -> OpenGroup? in
|
||||
dependencies.storage.getOpenGroup(for: uniqueId)
|
||||
}
|
||||
let thread = TSGroupThread.getOrCreateThread(with: initialModel, transaction: transaction)
|
||||
let existingOpenGroup: OpenGroup? = thread.uniqueId.flatMap { uniqueId -> OpenGroup? in
|
||||
dependencies.storage.getOpenGroup(for: uniqueId)
|
||||
}
|
||||
|
||||
guard let threadUniqueId: String = thread.uniqueId else { return }
|
||||
guard let publicKey: String = (maybePublicKey ?? existingOpenGroup?.publicKey) else { return }
|
||||
|
||||
let updatedModel: TSGroupModel = TSGroupModel(
|
||||
title: (pollInfo.details?.name ?? thread.groupModel.groupName),
|
||||
memberIds: Array(Set(thread.groupModel.groupMemberIds).inserting(userPublicKey)),
|
||||
image: thread.groupModel.groupImage,
|
||||
groupId: groupId,
|
||||
groupType: .openGroup,
|
||||
adminIds: (pollInfo.details?.admins ?? thread.groupModel.groupAdminIds),
|
||||
moderatorIds: (pollInfo.details?.moderators ?? thread.groupModel.groupModeratorIds)
|
||||
)
|
||||
maybeUpdatedModel = updatedModel
|
||||
let updatedOpenGroup: OpenGroup = OpenGroup(
|
||||
server: server,
|
||||
room: pollInfo.token,
|
||||
publicKey: publicKey,
|
||||
name: (pollInfo.details?.name ?? thread.name()),
|
||||
groupDescription: (pollInfo.details?.description ?? existingOpenGroup?.description),
|
||||
imageID: (pollInfo.details?.imageId.map { "\($0)" } ?? existingOpenGroup?.imageID),
|
||||
infoUpdates: ((pollInfo.details?.infoUpdates ?? existingOpenGroup?.infoUpdates) ?? 0)
|
||||
)
|
||||
|
||||
// - Thread changes
|
||||
thread.shouldBeVisible = true
|
||||
thread.groupModel = updatedModel
|
||||
thread.save(with: transaction)
|
||||
|
||||
// - Open Group changes
|
||||
dependencies.storage.setOpenGroup(updatedOpenGroup, for: threadUniqueId, using: transaction)
|
||||
|
||||
// - User Count
|
||||
dependencies.storage.setUserCount(
|
||||
to: UInt64(pollInfo.activeUsers),
|
||||
forOpenGroupWithID: updatedOpenGroup.id,
|
||||
using: transaction
|
||||
)
|
||||
},
|
||||
completion: {
|
||||
// Start the poller if needed
|
||||
if OpenGroupManager.shared.pollers[server] == nil {
|
||||
OpenGroupManager.shared.pollers[server] = OpenGroupAPI.Poller(for: server)
|
||||
OpenGroupManager.shared.pollers[server]?.startIfNeeded()
|
||||
}
|
||||
|
||||
// - Moderators
|
||||
if let moderators: [String] = (pollInfo.details?.moderators ?? maybeUpdatedModel?.groupModeratorIds) {
|
||||
OpenGroupManager.moderators[server] = (OpenGroupManager.moderators[server] ?? [:])
|
||||
.setting(roomToken, Set(moderators))
|
||||
}
|
||||
|
||||
// - Admins
|
||||
if let admins: [String] = (pollInfo.details?.admins ?? maybeUpdatedModel?.groupAdminIds) {
|
||||
OpenGroupManager.admins[server] = (OpenGroupManager.admins[server] ?? [:])
|
||||
.setting(roomToken, Set(admins))
|
||||
}
|
||||
|
||||
// - Room image (if there is one)
|
||||
if let imageId: Int64 = pollInfo.details?.imageId {
|
||||
OpenGroupManager.roomImage(imageId, for: roomToken, on: server)
|
||||
.done(on: DispatchQueue.global(qos: .userInitiated)) { data in
|
||||
dependencies.storage.write { transaction in
|
||||
// Update the thread
|
||||
let transaction = transaction as! YapDatabaseReadWriteTransaction
|
||||
let thread = TSGroupThread.getOrCreateThread(with: initialModel, transaction: transaction)
|
||||
thread.groupModel.groupImage = UIImage(data: data)
|
||||
thread.save(with: transaction)
|
||||
}
|
||||
}
|
||||
.retainUntilComplete()
|
||||
}
|
||||
|
||||
// Finish
|
||||
completion?()
|
||||
}
|
||||
guard let threadUniqueId: String = thread.uniqueId else { return }
|
||||
guard let publicKey: String = (maybePublicKey ?? existingOpenGroup?.publicKey) else { return }
|
||||
|
||||
let updatedModel: TSGroupModel = TSGroupModel(
|
||||
title: (pollInfo.details?.name ?? thread.groupModel.groupName),
|
||||
memberIds: Array(Set(thread.groupModel.groupMemberIds).inserting(userPublicKey)),
|
||||
image: thread.groupModel.groupImage,
|
||||
groupId: groupId,
|
||||
groupType: .openGroup,
|
||||
adminIds: (pollInfo.details?.admins ?? thread.groupModel.groupAdminIds),
|
||||
moderatorIds: (pollInfo.details?.moderators ?? thread.groupModel.groupModeratorIds)
|
||||
)
|
||||
maybeUpdatedModel = updatedModel
|
||||
let updatedOpenGroup: OpenGroup = OpenGroup(
|
||||
server: server,
|
||||
room: pollInfo.token,
|
||||
publicKey: publicKey,
|
||||
name: (pollInfo.details?.name ?? thread.name()),
|
||||
groupDescription: (pollInfo.details?.description ?? existingOpenGroup?.description),
|
||||
imageID: (pollInfo.details?.imageId.map { "\($0)" } ?? existingOpenGroup?.imageID),
|
||||
infoUpdates: ((pollInfo.details?.infoUpdates ?? existingOpenGroup?.infoUpdates) ?? 0)
|
||||
)
|
||||
|
||||
// - Thread changes
|
||||
thread.shouldBeVisible = true
|
||||
thread.groupModel = updatedModel
|
||||
thread.save(with: transaction)
|
||||
|
||||
// - Open Group changes
|
||||
dependencies.storage.setOpenGroup(updatedOpenGroup, for: threadUniqueId, using: transaction)
|
||||
|
||||
// - User Count
|
||||
dependencies.storage.setUserCount(
|
||||
to: UInt64(pollInfo.activeUsers),
|
||||
forOpenGroupWithID: updatedOpenGroup.id,
|
||||
using: transaction
|
||||
)
|
||||
|
||||
transaction.addCompletionQueue(DispatchQueue.global(qos: .userInitiated)) {
|
||||
// Start the poller if needed
|
||||
if OpenGroupManager.shared.pollers[server] == nil {
|
||||
OpenGroupManager.shared.pollers[server] = OpenGroupAPI.Poller(for: server)
|
||||
OpenGroupManager.shared.pollers[server]?.startIfNeeded()
|
||||
}
|
||||
|
||||
// - Moderators
|
||||
if let moderators: [String] = (pollInfo.details?.moderators ?? maybeUpdatedModel?.groupModeratorIds) {
|
||||
OpenGroupManager.moderators[server] = (OpenGroupManager.moderators[server] ?? [:])
|
||||
.setting(roomToken, Set(moderators))
|
||||
}
|
||||
|
||||
// - Admins
|
||||
if let admins: [String] = (pollInfo.details?.admins ?? maybeUpdatedModel?.groupAdminIds) {
|
||||
OpenGroupManager.admins[server] = (OpenGroupManager.admins[server] ?? [:])
|
||||
.setting(roomToken, Set(admins))
|
||||
}
|
||||
|
||||
// - Room image (if there is one)
|
||||
if let imageId: Int64 = pollInfo.details?.imageId {
|
||||
OpenGroupManager.roomImage(imageId, for: roomToken, on: server)
|
||||
.done(on: DispatchQueue.global(qos: .userInitiated)) { data in
|
||||
dependencies.storage.write { transaction in
|
||||
// Update the thread
|
||||
let transaction = transaction as! YapDatabaseReadWriteTransaction
|
||||
let thread = TSGroupThread.getOrCreateThread(with: initialModel, transaction: transaction)
|
||||
thread.groupModel.groupImage = UIImage(data: data)
|
||||
thread.save(with: transaction)
|
||||
}
|
||||
}
|
||||
.retainUntilComplete()
|
||||
}
|
||||
|
||||
// Finish
|
||||
completion?()
|
||||
}
|
||||
}
|
||||
|
||||
internal static func handleMessages(
|
||||
_ messages: [OpenGroupAPI.Message],
|
||||
for roomToken: String,
|
||||
on server: String,
|
||||
isBackgroundPoll: Bool,
|
||||
using transaction: YapDatabaseReadWriteTransaction,
|
||||
dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()
|
||||
) {
|
||||
// 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).\(roomToken)"
|
||||
let sortedMessages: [OpenGroupAPI.Message] = messages
|
||||
.sorted { lhs, rhs in lhs.id < rhs.id }
|
||||
let seqNo: Int64? = sortedMessages.map { $0.seqNo }.max()
|
||||
var messageServerIDsToRemove: [UInt64] = []
|
||||
|
||||
// Update the 'openGroupSequenceNumber' value (Note: SOGS V4 uses the 'seqNo' instead of the 'serverId')
|
||||
if let seqNo: Int64 = seqNo {
|
||||
dependencies.storage.setOpenGroupSequenceNumber(for: roomToken, on: server, to: seqNo, using: transaction)
|
||||
}
|
||||
|
||||
// Process the messages
|
||||
sortedMessages.forEach { message in
|
||||
guard let base64EncodedString: String = message.base64EncodedData, let data = Data(base64Encoded: base64EncodedString), let sender: String = message.sender else {
|
||||
// A message with no data has been deleted so add it to the list to remove
|
||||
messageServerIDsToRemove.append(UInt64(message.id))
|
||||
return
|
||||
}
|
||||
|
||||
// Note: The `posted` value is in seconds but all messages in the database use milliseconds for timestamps
|
||||
let envelope = SNProtoEnvelope.builder(type: .sessionMessage, timestamp: UInt64(floor(message.posted * 1000)))
|
||||
envelope.setContent(data)
|
||||
envelope.setSource(sender)
|
||||
|
||||
do {
|
||||
let data = try envelope.buildSerializedData()
|
||||
let (message, proto) = try MessageReceiver.parse(data, openGroupMessageServerID: UInt64(message.id), isRetry: false, using: transaction)
|
||||
try MessageReceiver.handle(message, associatedWithProto: proto, openGroupID: openGroupID, isBackgroundPoll: isBackgroundPoll, using: transaction)
|
||||
}
|
||||
catch {
|
||||
SNLog("Couldn't receive open group message due to error: \(error).")
|
||||
}
|
||||
}
|
||||
|
||||
// Handle any deletions that are needed
|
||||
guard !messageServerIDsToRemove.isEmpty else { return }
|
||||
guard let threadID = dependencies.storage.getThreadID(for: openGroupID), let thread = TSGroupThread.fetch(uniqueId: threadID, transaction: transaction) else {
|
||||
return
|
||||
}
|
||||
|
||||
var messagesToRemove: [TSMessage] = []
|
||||
|
||||
thread.enumerateInteractions(with: transaction) { interaction, stop in
|
||||
guard let message: TSMessage = interaction as? TSMessage, messageServerIDsToRemove.contains(message.openGroupServerMessageID) else {
|
||||
return
|
||||
}
|
||||
messagesToRemove.append(message)
|
||||
}
|
||||
|
||||
messagesToRemove.forEach { $0.remove(with: transaction) }
|
||||
}
|
||||
|
||||
internal static func handleDirectMessages(
|
||||
|
@ -318,7 +342,8 @@ public final class OpenGroupManager: NSObject {
|
|||
fromOutbox: Bool,
|
||||
on server: String,
|
||||
isBackgroundPoll: Bool,
|
||||
using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()
|
||||
using transaction: YapDatabaseReadWriteTransaction,
|
||||
dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()
|
||||
) {
|
||||
// Don't need to do anything if we have no messages (it's a valid case)
|
||||
guard !messages.isEmpty else { return }
|
||||
|
@ -332,73 +357,78 @@ public final class OpenGroupManager: NSObject {
|
|||
let sortedMessages: [OpenGroupAPI.DirectMessage] = messages
|
||||
.sorted { lhs, rhs in lhs.id < rhs.id }
|
||||
let latestMessageId: Int64 = (sortedMessages.last?.id ?? 0)
|
||||
let userSessionId: String = getUserHexEncodedPublicKey()
|
||||
var mappingCache: [String: BlindedIdMapping] = [:]
|
||||
|
||||
dependencies.storage.write { transaction in
|
||||
// Update the 'latestMessageId' value
|
||||
if fromOutbox {
|
||||
dependencies.storage.setOpenGroupOutboxLatestMessageId(for: server, to: latestMessageId, using: transaction)
|
||||
}
|
||||
else {
|
||||
dependencies.storage.setOpenGroupInboxLatestMessageId(for: server, to: latestMessageId, using: transaction)
|
||||
// Update the 'latestMessageId' value
|
||||
if fromOutbox {
|
||||
dependencies.storage.setOpenGroupOutboxLatestMessageId(for: server, to: latestMessageId, using: transaction)
|
||||
}
|
||||
else {
|
||||
dependencies.storage.setOpenGroupInboxLatestMessageId(for: server, to: latestMessageId, using: transaction)
|
||||
}
|
||||
|
||||
// Process the messages
|
||||
sortedMessages.forEach { message in
|
||||
guard let messageData = Data(base64Encoded: message.base64EncodedMessage) else {
|
||||
SNLog("Couldn't receive inbox message.")
|
||||
return
|
||||
}
|
||||
|
||||
// Process the messages
|
||||
sortedMessages.forEach { message in
|
||||
guard let messageData = Data(base64Encoded: message.base64EncodedMessage) else {
|
||||
SNLog("Couldn't receive inbox message.")
|
||||
return
|
||||
}
|
||||
// Note: The `posted` value is in seconds but all messages in the database use milliseconds for timestamps
|
||||
let envelope = SNProtoEnvelope.builder(type: .sessionMessage, timestamp: UInt64(floor(message.posted * 1000)))
|
||||
envelope.setContent(messageData)
|
||||
envelope.setSource(message.sender) // TODO: Need to un-blind/intercept outbox messages? (their sender will be the blinded id)
|
||||
|
||||
// Note: The `posted` value is in seconds but all messages in the database use milliseconds for timestamps
|
||||
let envelope = SNProtoEnvelope.builder(type: .sessionMessage, timestamp: UInt64(floor(message.posted * 1000)))
|
||||
envelope.setContent(messageData)
|
||||
envelope.setSource(message.sender ?? userSessionId) // Outbox messages have no 'sender' so default to current user
|
||||
|
||||
do {
|
||||
let data = try envelope.buildSerializedData()
|
||||
let (receivedMessage, proto) = try MessageReceiver.parse(data, openGroupMessageServerID: nil, openGroupServerPublicKey: serverPublicKey, isRetry: false, using: transaction)
|
||||
do {
|
||||
let data = try envelope.buildSerializedData()
|
||||
let (receivedMessage, proto) = try MessageReceiver.parse(
|
||||
data,
|
||||
openGroupMessageServerID: nil,
|
||||
openGroupServerPublicKey: serverPublicKey,
|
||||
isOutgoing: fromOutbox,
|
||||
otherBlindedPublicKey: (fromOutbox ? message.recipient : message.sender),
|
||||
isRetry: false,
|
||||
using: transaction
|
||||
)
|
||||
|
||||
// TODO: Need to test and validate this unblinding logic.
|
||||
// If the message was an outgoing message then attempt to unblind the recipient (this will help put
|
||||
// messages in the correct thread in case of message request approval race conditions as well as
|
||||
// during device sync'ing and restoration)
|
||||
if fromOutbox {
|
||||
// Attempt to un-blind the 'message.recipient'
|
||||
let mapping: BlindedIdMapping
|
||||
|
||||
// TODO: Need to test and validate this unblinding logic
|
||||
// If the message was an outgoing message then attempt to unblind the recipient (this will help put
|
||||
// messages in the correct thread in case of message request approval race conditions as well as
|
||||
// during device sync'ing and restoration)
|
||||
if fromOutbox, let recipientBlindedId: String = message.recipient {
|
||||
// Attempt to un-blind the 'message.recipient'
|
||||
let mapping: BlindedIdMapping
|
||||
|
||||
// Minor optimisation to avoid processing the same sender multiple times
|
||||
if let result: BlindedIdMapping = mappingCache[recipientBlindedId] {
|
||||
mapping = result
|
||||
}
|
||||
else if let result: BlindedIdMapping = ContactUtilities.mapping(for: recipientBlindedId, serverPublicKey: serverPublicKey) {
|
||||
mapping = result
|
||||
}
|
||||
else {
|
||||
// Cache an "invalid" mapping that has the 'sessionId' set to the recipient so we don't
|
||||
// re-process this recipient if there is another message from them
|
||||
mapping = BlindedIdMapping(
|
||||
blindedId: "",
|
||||
sessionId: recipientBlindedId,
|
||||
serverPublicKey: ""
|
||||
)
|
||||
}
|
||||
|
||||
switch receivedMessage {
|
||||
case let receivedMessage as VisibleMessage: receivedMessage.syncTarget = mapping.sessionId
|
||||
case let receivedMessage as ExpirationTimerUpdate: receivedMessage.syncTarget = mapping.sessionId
|
||||
default: break
|
||||
}
|
||||
|
||||
mappingCache[recipientBlindedId] = mapping
|
||||
// Minor optimisation to avoid processing the same sender multiple times
|
||||
if let result: BlindedIdMapping = mappingCache[message.recipient] {
|
||||
mapping = result
|
||||
}
|
||||
else if let result: BlindedIdMapping = ContactUtilities.mapping(for: message.recipient, serverPublicKey: serverPublicKey) {
|
||||
mapping = result
|
||||
}
|
||||
else {
|
||||
// Cache an "invalid" mapping that has the 'sessionId' set to the recipient so we don't
|
||||
// re-process this recipient if there is another message from them
|
||||
mapping = BlindedIdMapping(
|
||||
blindedId: "",
|
||||
sessionId: message.recipient,
|
||||
serverPublicKey: ""
|
||||
)
|
||||
}
|
||||
|
||||
try MessageReceiver.handle(receivedMessage, associatedWithProto: proto, openGroupID: nil, isBackgroundPoll: isBackgroundPoll, using: transaction)
|
||||
}
|
||||
catch let error {
|
||||
SNLog("Couldn't receive inbox message due to error: \(error).")
|
||||
switch receivedMessage {
|
||||
case let receivedMessage as VisibleMessage: receivedMessage.syncTarget = mapping.sessionId
|
||||
case let receivedMessage as ExpirationTimerUpdate: receivedMessage.syncTarget = mapping.sessionId
|
||||
default: break
|
||||
}
|
||||
|
||||
mappingCache[message.recipient] = mapping
|
||||
}
|
||||
|
||||
try MessageReceiver.handle(receivedMessage, associatedWithProto: proto, openGroupID: nil, isBackgroundPoll: isBackgroundPoll, using: transaction)
|
||||
}
|
||||
catch let error {
|
||||
SNLog("Couldn't receive inbox message due to error: \(error).")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,22 +24,22 @@ extension MessageReceiver {
|
|||
guard isValid else { throw Error.invalidSignature }
|
||||
// 4. ) Get the sender's X25519 public key
|
||||
guard let senderX25519PublicKey = sodium.sign.toX25519(ed25519PublicKey: senderED25519PublicKey) else { throw Error.decryptionFailed }
|
||||
// TODO: Need to rework this as it'll be based on the blinded id
|
||||
return (Data(plaintext), SessionId(.standard, publicKey: senderX25519PublicKey).hexString)
|
||||
}
|
||||
|
||||
internal static func decryptWithSessionBlindingProtocol(data: Data, fromBlindedPublicKey: String, with openGroupPublicKey: String, userEd25519KeyPair: Box.KeyPair, using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()) throws -> (plaintext: Data, senderX25519PublicKey: String) {
|
||||
internal static func decryptWithSessionBlindingProtocol(data: Data, isOutgoing: Bool, otherBlindedPublicKey: String, with openGroupPublicKey: String, userEd25519KeyPair: Box.KeyPair, using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()) throws -> (plaintext: Data, senderX25519PublicKey: String) {
|
||||
guard let blindedKeyPair = dependencies.sodium.blindedKeyPair(serverPublicKey: openGroupPublicKey, edKeyPair: userEd25519KeyPair, genericHash: dependencies.genericHash) else {
|
||||
throw Error.decryptionFailed
|
||||
}
|
||||
|
||||
/// Step one: calculate the shared encryption key, receiving from A to B
|
||||
let kA: Bytes = Data(hex: fromBlindedPublicKey.removingIdPrefixIfNeeded()).bytes
|
||||
let otherKeyBytes: Bytes = Data(hex: otherBlindedPublicKey.removingIdPrefixIfNeeded()).bytes
|
||||
let kA: Bytes = (isOutgoing ? blindedKeyPair.publicKey : otherKeyBytes)
|
||||
guard let dec_key: Bytes = dependencies.sodium.sharedBlindedEncryptionKey(
|
||||
secretKey: userEd25519KeyPair.secretKey,
|
||||
otherBlindedPublicKey: kA,
|
||||
otherBlindedPublicKey: otherKeyBytes,
|
||||
fromBlindedPublicKey: kA,
|
||||
toBlindedPublicKey: blindedKeyPair.publicKey,
|
||||
toBlindedPublicKey: (isOutgoing ? otherKeyBytes : blindedKeyPair.publicKey),
|
||||
genericHash: dependencies.genericHash
|
||||
) else {
|
||||
throw Error.decryptionFailed
|
||||
|
|
|
@ -277,7 +277,7 @@ extension MessageReceiver {
|
|||
// Open groups
|
||||
for openGroupURL in message.openGroups {
|
||||
if let (room, server, publicKey) = OpenGroupManager.parseV2OpenGroup(from: openGroupURL) {
|
||||
OpenGroupManager.shared.add(roomToken: room, server: server, publicKey: publicKey, using: transaction).retainUntilComplete()
|
||||
OpenGroupManager.shared.add(roomToken: room, server: server, publicKey: publicKey, isConfigMessage: true, using: transaction).retainUntilComplete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,7 +48,15 @@ public enum MessageReceiver {
|
|||
}
|
||||
}
|
||||
|
||||
public static func parse(_ data: Data, openGroupMessageServerID: UInt64?, openGroupServerPublicKey: String? = nil, isRetry: Bool = false, using transaction: Any) throws -> (Message, SNProtoContent) {
|
||||
public static func parse(
|
||||
_ data: Data,
|
||||
openGroupMessageServerID: UInt64?,
|
||||
openGroupServerPublicKey: String? = nil,
|
||||
isOutgoing: Bool? = nil,
|
||||
otherBlindedPublicKey: String? = nil,
|
||||
isRetry: Bool = false,
|
||||
using transaction: Any
|
||||
) throws -> (Message, SNProtoContent) {
|
||||
let userPublicKey = SNMessagingKitConfiguration.shared.storage.getUserPublicKey()
|
||||
let isOpenGroupMessage = (openGroupMessageServerID != nil)
|
||||
|
||||
|
@ -79,7 +87,7 @@ public enum MessageReceiver {
|
|||
(plaintext, sender) = try decryptWithSessionProtocol(ciphertext: ciphertext, using: userX25519KeyPair)
|
||||
|
||||
case .blinded:
|
||||
guard let senderSessionId: String = envelope.source else { throw Error.noData }
|
||||
guard let otherBlindedPublicKey: String = otherBlindedPublicKey else { throw Error.noData }
|
||||
guard let openGroupServerPublicKey: String = openGroupServerPublicKey else {
|
||||
throw Error.invalidGroupPublicKey
|
||||
}
|
||||
|
@ -89,7 +97,8 @@ public enum MessageReceiver {
|
|||
|
||||
(plaintext, sender) = try decryptWithSessionBlindingProtocol(
|
||||
data: ciphertext,
|
||||
fromBlindedPublicKey: senderSessionId,
|
||||
isOutgoing: (isOutgoing == true),
|
||||
otherBlindedPublicKey: otherBlindedPublicKey,
|
||||
with: openGroupServerPublicKey,
|
||||
userEd25519KeyPair: userEd25519KeyPair
|
||||
)
|
||||
|
|
|
@ -392,10 +392,11 @@ public final class MessageSender : NSObject {
|
|||
whisperMods: whisperMods
|
||||
)
|
||||
.done(on: DispatchQueue.global(qos: .userInitiated)) { responseInfo, data in
|
||||
message.openGroupServerMessageID = given(data.id) { UInt64($0) }
|
||||
message.openGroupServerMessageID = UInt64(data.id)
|
||||
|
||||
dependencies.storage.write { transaction in
|
||||
MessageSender.handleSuccessfulMessageSend(message, to: destination, serverTimestamp: UInt64(floor(data.posted)), using: transaction)
|
||||
// The `posted` value is in seconds but we sent it in ms so need that for de-duping
|
||||
MessageSender.handleSuccessfulMessageSend(message, to: destination, serverTimestamp: UInt64(floor(data.posted * 1000)), using: transaction)
|
||||
seal.fulfill(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -84,66 +84,79 @@ extension OpenGroupAPI {
|
|||
}
|
||||
|
||||
private func handlePollResponse(_ response: [OpenGroupAPI.Endpoint: (info: OnionRequestResponseInfoType, data: Codable?)], isBackgroundPoll: Bool) {
|
||||
response.forEach { endpoint, endpointResponse in
|
||||
switch endpoint {
|
||||
case .capabilities:
|
||||
guard let responseData: BatchSubResponse<Capabilities> = endpointResponse.data as? BatchSubResponse<Capabilities>, let responseBody: Capabilities = responseData.body else {
|
||||
SNLog("Open group polling failed due to invalid data.")
|
||||
return
|
||||
}
|
||||
|
||||
OpenGroupManager.handleCapabilities(
|
||||
responseBody,
|
||||
on: server
|
||||
)
|
||||
|
||||
case .roomMessagesRecent(let roomToken), .roomMessagesBefore(let roomToken, _), .roomMessagesSince(let roomToken, _):
|
||||
guard let responseData: BatchSubResponse<[Message]> = endpointResponse.data as? BatchSubResponse<[Message]>, let responseBody: [Message] = responseData.body else {
|
||||
SNLog("Open group polling failed due to invalid data.")
|
||||
return
|
||||
}
|
||||
|
||||
OpenGroupManager.handleMessages(
|
||||
responseBody,
|
||||
for: roomToken,
|
||||
on: server,
|
||||
isBackgroundPoll: isBackgroundPoll
|
||||
)
|
||||
|
||||
case .roomPollInfo(let roomToken, _):
|
||||
guard let responseData: BatchSubResponse<RoomPollInfo> = endpointResponse.data as? BatchSubResponse<RoomPollInfo>, let responseBody: RoomPollInfo = responseData.body else {
|
||||
SNLog("Open group polling failed due to invalid data.")
|
||||
return
|
||||
}
|
||||
|
||||
OpenGroupManager.handlePollInfo(
|
||||
responseBody,
|
||||
publicKey: nil,
|
||||
for: roomToken,
|
||||
on: server
|
||||
)
|
||||
|
||||
case .inbox, .inboxSince, .outbox, .outboxSince:
|
||||
guard let responseData: BatchSubResponse<[DirectMessage]?> = endpointResponse.data as? BatchSubResponse<[DirectMessage]?>, let responseBody: [DirectMessage]? = responseData.body else {
|
||||
SNLog("Open group polling failed due to invalid data.")
|
||||
return
|
||||
}
|
||||
|
||||
let fromOutbox: Bool = {
|
||||
switch endpoint {
|
||||
case .outbox, .outboxSince: return true
|
||||
default: return false
|
||||
let server: String = self.server
|
||||
|
||||
Storage.shared.write { anyTransaction in
|
||||
guard let transaction: YapDatabaseReadWriteTransaction = anyTransaction as? YapDatabaseReadWriteTransaction else {
|
||||
SNLog("Open group polling failed due to invalid database transaction.")
|
||||
return
|
||||
}
|
||||
|
||||
response.forEach { endpoint, endpointResponse in
|
||||
switch endpoint {
|
||||
case .capabilities:
|
||||
guard let responseData: BatchSubResponse<Capabilities> = endpointResponse.data as? BatchSubResponse<Capabilities>, let responseBody: Capabilities = responseData.body else {
|
||||
SNLog("Open group polling failed due to invalid data.")
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
OpenGroupManager.handleDirectMessages(
|
||||
(responseBody ?? []),
|
||||
fromOutbox: fromOutbox,
|
||||
on: server,
|
||||
isBackgroundPoll: isBackgroundPoll
|
||||
)
|
||||
|
||||
default: break // No custom handling needed
|
||||
|
||||
OpenGroupManager.handleCapabilities(
|
||||
responseBody,
|
||||
on: server,
|
||||
using: transaction
|
||||
)
|
||||
|
||||
case .roomMessagesRecent(let roomToken), .roomMessagesBefore(let roomToken, _), .roomMessagesSince(let roomToken, _):
|
||||
guard let responseData: BatchSubResponse<[Message]> = endpointResponse.data as? BatchSubResponse<[Message]>, let responseBody: [Message] = responseData.body else {
|
||||
SNLog("Open group polling failed due to invalid data.")
|
||||
return
|
||||
}
|
||||
|
||||
OpenGroupManager.handleMessages(
|
||||
responseBody,
|
||||
for: roomToken,
|
||||
on: server,
|
||||
isBackgroundPoll: isBackgroundPoll,
|
||||
using: transaction
|
||||
)
|
||||
|
||||
case .roomPollInfo(let roomToken, _):
|
||||
guard let responseData: BatchSubResponse<RoomPollInfo> = endpointResponse.data as? BatchSubResponse<RoomPollInfo>, let responseBody: RoomPollInfo = responseData.body else {
|
||||
SNLog("Open group polling failed due to invalid data.")
|
||||
return
|
||||
}
|
||||
|
||||
OpenGroupManager.handlePollInfo(
|
||||
responseBody,
|
||||
publicKey: nil,
|
||||
for: roomToken,
|
||||
on: server,
|
||||
using: transaction
|
||||
)
|
||||
|
||||
case .inbox, .inboxSince, .outbox, .outboxSince:
|
||||
guard let responseData: BatchSubResponse<[DirectMessage]?> = endpointResponse.data as? BatchSubResponse<[DirectMessage]?>, !responseData.failedToParseBody else {
|
||||
SNLog("Open group polling failed due to invalid data.")
|
||||
return
|
||||
}
|
||||
|
||||
let fromOutbox: Bool = {
|
||||
switch endpoint {
|
||||
case .outbox, .outboxSince: return true
|
||||
default: return false
|
||||
}
|
||||
}()
|
||||
|
||||
OpenGroupManager.handleDirectMessages(
|
||||
((responseData.body ?? []) ?? []), // Double optional because the server can return a `304` with an empty body
|
||||
fromOutbox: fromOutbox,
|
||||
on: server,
|
||||
isBackgroundPoll: isBackgroundPoll,
|
||||
using: transaction
|
||||
)
|
||||
|
||||
default: break // No custom handling needed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,7 +123,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
TSThread *thread = [TSThread fetchObjectWithUniqueID:groupID transaction:transaction];
|
||||
|
||||
// Only increase the count for message requests
|
||||
if (!thread.isMessageRequest) { continue; }
|
||||
if (![thread isMessageRequestUsingTransaction:transaction]) { continue; }
|
||||
|
||||
[unreadMessages enumerateKeysAndObjectsInGroup:groupID
|
||||
usingBlock:^(NSString *collection, NSString *key, id object, NSUInteger index, BOOL *stop) {
|
||||
|
|
Loading…
Reference in New Issue