diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 3d58ff168..4951ea3c6 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -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 = ""; }; 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 = ""; }; 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 = ""; }; + 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 = ""; }; 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 = ""; }; 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 = ""; }; 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 = ""; }; + 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 = ""; }; 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 = ""; }; + 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 = ""; }; 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 = ""; }; 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 = ""; }; 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 = ""; }; @@ -1921,6 +1940,13 @@ FD705A93278D052B00F16121 /* UITableView+ReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+ReusableView.swift"; sourceTree = ""; }; FD705A97278E9F4D00F16121 /* UIColor+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extensions.swift"; sourceTree = ""; }; FD83B9A927CF149D005E1583 /* ContactUtilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactUtilities.swift; sourceTree = ""; }; + 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 = ""; }; + FD83B9BD27CF2243005E1583 /* TestConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestConstants.swift; sourceTree = ""; }; + FD83B9C227CF33F7005E1583 /* ServerSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSpec.swift; sourceTree = ""; }; + FD83B9C427CF3E2A005E1583 /* OpenGroupSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupSpec.swift; sourceTree = ""; }; + FD83B9C627CF3F10005E1583 /* CapabilitiesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapabilitiesSpec.swift; sourceTree = ""; }; + FD83B9C827D0487A005E1583 /* SendDirectMessageResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendDirectMessageResponse.swift; sourceTree = ""; }; FD859EEF27BF207700510D0C /* SessionProtos.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = SessionProtos.proto; sourceTree = ""; }; FD859EF027BF207C00510D0C /* WebSocketResources.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = WebSocketResources.proto; sourceTree = ""; }; FD859EF127BF6BA200510D0C /* Data+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Utilities.swift"; sourceTree = ""; }; @@ -1973,7 +1999,7 @@ FDC4387527B5BEF300C60D73 /* FileDownloadResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileDownloadResponse.swift; sourceTree = ""; }; FDC4387727B5C35400C60D73 /* SendMessageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMessageRequest.swift; sourceTree = ""; }; 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 = ""; }; + FDC4389927BA002500C60D73 /* OpenGroupAPISpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupAPISpec.swift; sourceTree = ""; }; FDC4389C27BA01F000C60D73 /* TestStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestStorage.swift; sourceTree = ""; }; FDC438A327BB107F00C60D73 /* UserBanRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserBanRequest.swift; sourceTree = ""; }; FDC438A527BB113A00C60D73 /* UserUnbanRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserUnbanRequest.swift; sourceTree = ""; }; @@ -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 = ""; @@ -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 = ""; @@ -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 = ""; @@ -3830,6 +3871,40 @@ path = "Message Requests"; sourceTree = ""; }; + FD83B9B027CF200A005E1583 /* SessionUtilitiesKitTests */ = { + isa = PBXGroup; + children = ( + FD83B9B927CF20A5005E1583 /* General */, + ); + path = SessionUtilitiesKitTests; + sourceTree = ""; + }; + FD83B9B927CF20A5005E1583 /* General */ = { + isa = PBXGroup; + children = ( + FD83B9BA27CF20AF005E1583 /* SessionIdSpec.swift */, + ); + path = General; + sourceTree = ""; + }; + FD83B9BC27CF2215005E1583 /* SharedTest */ = { + isa = PBXGroup; + children = ( + FD83B9BD27CF2243005E1583 /* TestConstants.swift */, + ); + path = SharedTest; + sourceTree = ""; + }; + FD83B9C127CF33EE005E1583 /* Models */ = { + isa = PBXGroup; + children = ( + FD83B9C227CF33F7005E1583 /* ServerSpec.swift */, + FD83B9C427CF3E2A005E1583 /* OpenGroupSpec.swift */, + FD83B9C627CF3F10005E1583 /* CapabilitiesSpec.swift */, + ); + path = Models; + sourceTree = ""; + }; 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 = ""; @@ -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 = ( diff --git a/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme b/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme index 3f9cd0749..bb11967c8 100644 --- a/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme +++ b/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme @@ -105,7 +105,9 @@ + skipped = "NO" + parallelizable = "YES" + testExecutionOrdering = "random"> + + + + + skipped = "NO" + parallelizable = "YES" + testExecutionOrdering = "random"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index fc5b3e072..99b9b8370 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -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() diff --git a/Session/Conversations/Views & Modals/JoinOpenGroupModal.swift b/Session/Conversations/Views & Modals/JoinOpenGroupModal.swift index fc32e51f9..2d89c3047 100644 --- a/Session/Conversations/Views & Modals/JoinOpenGroupModal.swift +++ b/Session/Conversations/Views & Modals/JoinOpenGroupModal.swift @@ -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(...) diff --git a/Session/Open Groups/JoinOpenGroupVC.swift b/Session/Open Groups/JoinOpenGroupVC.swift index 8973ce1aa..1d1b27af6 100644 --- a/Session/Open Groups/JoinOpenGroupVC.swift +++ b/Session/Open Groups/JoinOpenGroupVC.swift @@ -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 diff --git a/SessionMessagingKit/Common Networking/Header.swift b/SessionMessagingKit/Common Networking/Header.swift index 41f8ad82c..97ea01ef7 100644 --- a/SessionMessagingKit/Common Networking/Header.swift +++ b/SessionMessagingKit/Common Networking/Header.swift @@ -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" diff --git a/SessionMessagingKit/Open Groups/Models/BatchRequestInfo.swift b/SessionMessagingKit/Open Groups/Models/BatchRequestInfo.swift index d31ee4014..ef789bebc 100644 --- a/SessionMessagingKit/Open Groups/Models/BatchRequestInfo.swift +++ b/SessionMessagingKit/Open Groups/Models/BatchRequestInfo.swift @@ -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)) ) } } diff --git a/SessionMessagingKit/Open Groups/Models/DirectMessage.swift b/SessionMessagingKit/Open Groups/Models/DirectMessage.swift index 941f21b7e..f2e7421ef 100644 --- a/SessionMessagingKit/Open Groups/Models/DirectMessage.swift +++ b/SessionMessagingKit/Open Groups/Models/DirectMessage.swift @@ -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 diff --git a/SessionMessagingKit/Open Groups/Models/SendDirectMessageResponse.swift b/SessionMessagingKit/Open Groups/Models/SendDirectMessageResponse.swift new file mode 100644 index 000000000..5768b863a --- /dev/null +++ b/SessionMessagingKit/Open Groups/Models/SendDirectMessageResponse.swift @@ -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 + } +} diff --git a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift index ff06432da..9a27e00d2 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift @@ -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( 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( 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( + server: server, + endpoint: .capabilities + ), + responseType: Capabilities.self + ), + + // And the room info + BatchRequestInfo( + request: Request( + 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 = endpointResponse.data as? BatchSubResponse, let responseBody: Capabilities = responseData.body else { + throw Error.parsingFailed + } + + capabilities = (endpointResponse.info, responseBody) + + case .room: + guard let responseData: OpenGroupAPI.BatchSubResponse = endpointResponse.data as? OpenGroupAPI.BatchSubResponse, 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( 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( @@ -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( 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( 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( 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( 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( server: server, diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index e80a03dd7..1726c15b8 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -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 { - let storage = Storage.shared + public func add(roomToken: String, server: String, publicKey: String, isConfigMessage: Bool, using transaction: YapDatabaseReadWriteTransaction, dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()) -> Promise { + // 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.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).") } } } diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift index a1f05d07b..cf5b03e44 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift @@ -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 diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index 55fd0a4d6..54a65d1a9 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -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() } } } diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index 67082b546..d5bd8f39f 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -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 ) diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index c053ebeb3..d547cbeea 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -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(()) } } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift index 224e5ed15..103491930 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift @@ -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 = endpointResponse.data as? BatchSubResponse, 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 = endpointResponse.data as? BatchSubResponse, 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 = endpointResponse.data as? BatchSubResponse, 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 = endpointResponse.data as? BatchSubResponse, 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 + } } } } diff --git a/SignalUtilitiesKit/Messaging/OWSMessageUtils.m b/SignalUtilitiesKit/Messaging/OWSMessageUtils.m index b07a80bf8..f8a932fa6 100644 --- a/SignalUtilitiesKit/Messaging/OWSMessageUtils.m +++ b/SignalUtilitiesKit/Messaging/OWSMessageUtils.m @@ -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) {