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:
Morgan Pretty 2022-03-03 17:46:35 +11:00
parent c04d4544f2
commit 8ca00ca578
19 changed files with 897 additions and 334 deletions

View File

@ -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 = (

View File

@ -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

View File

@ -40,7 +40,9 @@
</CodeCoverageTargets>
<Testables>
<TestableReference
skipped = "NO">
skipped = "NO"
parallelizable = "YES"
testExecutionOrdering = "random">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "FDC4388D27B9FFC700C60D73"

View File

@ -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>

View File

@ -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()

View File

@ -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(...)

View File

@ -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

View File

@ -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"

View File

@ -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))
)
}
}

View File

@ -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

View File

@ -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
}
}

View File

@ -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,

View File

@ -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).")
}
}
}

View File

@ -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

View File

@ -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()
}
}
}

View File

@ -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
)

View File

@ -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(())
}
}

View File

@ -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
}
}
}
}

View File

@ -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) {