Started resolving TODOs

Added some new properties to the OpenGroupV2
Moved a number of methods and variables from OpenGroupAPI to OpenGroupManager (anything doing actual logic)
Moved the message signing into the OpenGroupAPI (since that's the only place it happens)
Renamed remaining old model classes to start with 'Legacy' to make clean up easier
Updated the OpenGroupAPI poll method to use the same logic as it previously did to determine if it should retrieve recent messages or messages since the last one
This commit is contained in:
Morgan Pretty 2022-02-16 10:31:08 +11:00
parent 8cc9caa0fd
commit b655882cbd
30 changed files with 771 additions and 607 deletions

View File

@ -791,22 +791,22 @@
FDC4381527B329CE00C60D73 /* NonceGenerator16Byte.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381427B329CE00C60D73 /* NonceGenerator16Byte.swift */; };
FDC4381727B32EC700C60D73 /* Personalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381627B32EC700C60D73 /* Personalization.swift */; };
FDC4381A27B34EBA00C60D73 /* LegacyCompactPollBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381927B34EBA00C60D73 /* LegacyCompactPollBody.swift */; };
FDC4381C27B354AC00C60D73 /* PublicKeyBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381B27B354AC00C60D73 /* PublicKeyBody.swift */; };
FDC4381C27B354AC00C60D73 /* LegacyPublicKeyBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381B27B354AC00C60D73 /* LegacyPublicKeyBody.swift */; };
FDC4382027B36ADC00C60D73 /* Endpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381F27B36ADC00C60D73 /* Endpoint.swift */; };
FDC4382627B37F6900C60D73 /* DeletedMessagesResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4382527B37F6900C60D73 /* DeletedMessagesResponse.swift */; };
FDC4382827B37FD300C60D73 /* ModeratorsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4382727B37FD300C60D73 /* ModeratorsResponse.swift */; };
FDC4382627B37F6900C60D73 /* LegacyDeletedMessagesResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4382527B37F6900C60D73 /* LegacyDeletedMessagesResponse.swift */; };
FDC4382827B37FD300C60D73 /* LegacyModeratorsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4382727B37FD300C60D73 /* LegacyModeratorsResponse.swift */; };
FDC4382A27B3802D00C60D73 /* LegacyRoomsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4382927B3802D00C60D73 /* LegacyRoomsResponse.swift */; };
FDC4382C27B380E300C60D73 /* MemberCountResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4382B27B380E300C60D73 /* MemberCountResponse.swift */; };
FDC4382C27B380E300C60D73 /* LegacyMemberCountResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4382B27B380E300C60D73 /* LegacyMemberCountResponse.swift */; };
FDC4382F27B383AF00C60D73 /* UnregisterResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4382E27B383AF00C60D73 /* UnregisterResponse.swift */; };
FDC4383127B3841C00C60D73 /* RegisterResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4383027B3841C00C60D73 /* RegisterResponse.swift */; };
FDC4383827B3863200C60D73 /* VersionResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4383727B3863200C60D73 /* VersionResponse.swift */; };
FDC4383A27B4696200C60D73 /* AuthTokenResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4383927B4696200C60D73 /* AuthTokenResponse.swift */; };
FDC4383A27B4696200C60D73 /* LegacyAuthTokenResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4383927B4696200C60D73 /* LegacyAuthTokenResponse.swift */; };
FDC4383E27B4708600C60D73 /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4383D27B4708600C60D73 /* Atomic.swift */; };
FDC4384027B4746D00C60D73 /* LegacyGetInfoResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4383F27B4746D00C60D73 /* LegacyGetInfoResponse.swift */; };
FDC4384727B47F4D00C60D73 /* OpenGroupMessageV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4384327B47F4D00C60D73 /* OpenGroupMessageV2.swift */; };
FDC4384727B47F4D00C60D73 /* LegacyOpenGroupMessageV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4384327B47F4D00C60D73 /* LegacyOpenGroupMessageV2.swift */; };
FDC4384827B47F4D00C60D73 /* LegacyRoomInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4384427B47F4D00C60D73 /* LegacyRoomInfo.swift */; };
FDC4384927B47F4D00C60D73 /* LegacyCompactPollResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4384527B47F4D00C60D73 /* LegacyCompactPollResponse.swift */; };
FDC4384A27B47F4D00C60D73 /* Deletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4384627B47F4D00C60D73 /* Deletion.swift */; };
FDC4384A27B47F4D00C60D73 /* LegacyDeletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4384627B47F4D00C60D73 /* LegacyDeletion.swift */; };
FDC4384C27B47F7700C60D73 /* OpenGroupV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4384B27B47F7700C60D73 /* OpenGroupV2.swift */; };
FDC4384F27B4804F00C60D73 /* Header.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4384E27B4804F00C60D73 /* Header.swift */; };
FDC4385127B4807400C60D73 /* QueryParam.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4385027B4807400C60D73 /* QueryParam.swift */; };
@ -846,6 +846,8 @@
FDC438C327BB512200C60D73 /* SodiumProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C227BB512200C60D73 /* SodiumProtocols.swift */; };
FDC438C727BB6DF000C60D73 /* DirectMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C627BB6DF000C60D73 /* DirectMessage.swift */; };
FDC438C927BB706500C60D73 /* SendDirectMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C827BB706500C60D73 /* SendDirectMessageRequest.swift */; };
FDC438CB27BB7DB100C60D73 /* UpdateMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438CA27BB7DB100C60D73 /* UpdateMessageRequest.swift */; };
FDC438CD27BC641200C60D73 /* Set+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438CC27BC641200C60D73 /* Set+Utilities.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -1925,22 +1927,22 @@
FDC4381427B329CE00C60D73 /* NonceGenerator16Byte.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonceGenerator16Byte.swift; sourceTree = "<group>"; };
FDC4381627B32EC700C60D73 /* Personalization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Personalization.swift; sourceTree = "<group>"; };
FDC4381927B34EBA00C60D73 /* LegacyCompactPollBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyCompactPollBody.swift; sourceTree = "<group>"; };
FDC4381B27B354AC00C60D73 /* PublicKeyBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicKeyBody.swift; sourceTree = "<group>"; };
FDC4381B27B354AC00C60D73 /* LegacyPublicKeyBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyPublicKeyBody.swift; sourceTree = "<group>"; };
FDC4381F27B36ADC00C60D73 /* Endpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Endpoint.swift; sourceTree = "<group>"; };
FDC4382527B37F6900C60D73 /* DeletedMessagesResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedMessagesResponse.swift; sourceTree = "<group>"; };
FDC4382727B37FD300C60D73 /* ModeratorsResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModeratorsResponse.swift; sourceTree = "<group>"; };
FDC4382527B37F6900C60D73 /* LegacyDeletedMessagesResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyDeletedMessagesResponse.swift; sourceTree = "<group>"; };
FDC4382727B37FD300C60D73 /* LegacyModeratorsResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyModeratorsResponse.swift; sourceTree = "<group>"; };
FDC4382927B3802D00C60D73 /* LegacyRoomsResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyRoomsResponse.swift; sourceTree = "<group>"; };
FDC4382B27B380E300C60D73 /* MemberCountResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberCountResponse.swift; sourceTree = "<group>"; };
FDC4382B27B380E300C60D73 /* LegacyMemberCountResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyMemberCountResponse.swift; sourceTree = "<group>"; };
FDC4382E27B383AF00C60D73 /* UnregisterResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnregisterResponse.swift; sourceTree = "<group>"; };
FDC4383027B3841C00C60D73 /* RegisterResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterResponse.swift; sourceTree = "<group>"; };
FDC4383727B3863200C60D73 /* VersionResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionResponse.swift; sourceTree = "<group>"; };
FDC4383927B4696200C60D73 /* AuthTokenResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthTokenResponse.swift; sourceTree = "<group>"; };
FDC4383927B4696200C60D73 /* LegacyAuthTokenResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyAuthTokenResponse.swift; sourceTree = "<group>"; };
FDC4383D27B4708600C60D73 /* Atomic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Atomic.swift; sourceTree = "<group>"; };
FDC4383F27B4746D00C60D73 /* LegacyGetInfoResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyGetInfoResponse.swift; sourceTree = "<group>"; };
FDC4384327B47F4D00C60D73 /* OpenGroupMessageV2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenGroupMessageV2.swift; sourceTree = "<group>"; };
FDC4384327B47F4D00C60D73 /* LegacyOpenGroupMessageV2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyOpenGroupMessageV2.swift; sourceTree = "<group>"; };
FDC4384427B47F4D00C60D73 /* LegacyRoomInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyRoomInfo.swift; sourceTree = "<group>"; };
FDC4384527B47F4D00C60D73 /* LegacyCompactPollResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyCompactPollResponse.swift; sourceTree = "<group>"; };
FDC4384627B47F4D00C60D73 /* Deletion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Deletion.swift; sourceTree = "<group>"; };
FDC4384627B47F4D00C60D73 /* LegacyDeletion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyDeletion.swift; sourceTree = "<group>"; };
FDC4384B27B47F7700C60D73 /* OpenGroupV2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenGroupV2.swift; sourceTree = "<group>"; };
FDC4384E27B4804F00C60D73 /* Header.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Header.swift; sourceTree = "<group>"; };
FDC4385027B4807400C60D73 /* QueryParam.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryParam.swift; sourceTree = "<group>"; };
@ -1978,6 +1980,8 @@
FDC438C227BB512200C60D73 /* SodiumProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SodiumProtocols.swift; sourceTree = "<group>"; };
FDC438C627BB6DF000C60D73 /* DirectMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectMessage.swift; sourceTree = "<group>"; };
FDC438C827BB706500C60D73 /* SendDirectMessageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendDirectMessageRequest.swift; sourceTree = "<group>"; };
FDC438CA27BB7DB100C60D73 /* UpdateMessageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateMessageRequest.swift; sourceTree = "<group>"; };
FDC438CC27BC641200C60D73 /* Set+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Set+Utilities.swift"; sourceTree = "<group>"; };
FEDBAE1B98C49BBE8C87F575 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig"; sourceTree = "<group>"; };
FF9BA33D021B115B1F5B4E46 /* Pods-SessionMessagingKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionMessagingKit.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SessionMessagingKit/Pods-SessionMessagingKit.app store release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -2500,6 +2504,7 @@
C33FDB8A255A581200E217F9 /* AppContext.h */,
C33FDB85255A581100E217F9 /* AppContext.m */,
C3C2A5D12553860800C340D1 /* Array+Utilities.swift */,
FDC438CC27BC641200C60D73 /* Set+Utilities.swift */,
C33FDAA8255A57FF00E217F9 /* BuildConfiguration.swift */,
B8F5F58225EC94A6003BF8D4 /* Collection+Subscripting.swift */,
B8AE75A325A6C6A6001A84D2 /* Data+Utilities.swift */,
@ -3836,7 +3841,6 @@
isa = PBXGroup;
children = (
FDC4384B27B47F7700C60D73 /* OpenGroupV2.swift */,
FDC4381B27B354AC00C60D73 /* PublicKeyBody.swift */,
FDC4386A27B4E88F00C60D73 /* BatchRequestInfo.swift */,
FDC4386627B4E10E00C60D73 /* Capabilities.swift */,
FDC4385C27B4C18900C60D73 /* Room.swift */,
@ -3844,6 +3848,7 @@
FDC4385E27B4C4A200C60D73 /* PinnedMessage.swift */,
FDC4386027B4CDDF00C60D73 /* FileResponse.swift */,
FDC4387727B5C35400C60D73 /* SendMessageRequest.swift */,
FDC438CA27BB7DB100C60D73 /* UpdateMessageRequest.swift */,
FDC4386227B4D94E00C60D73 /* OGMessage.swift */,
FDC438C627BB6DF000C60D73 /* DirectMessage.swift */,
FDC438C827BB706500C60D73 /* SendDirectMessageRequest.swift */,
@ -3853,17 +3858,18 @@
FDC438A927BB12BB00C60D73 /* UserModeratorRequest.swift */,
FDC438AB27BB145200C60D73 /* UserDeleteMessagesRequest.swift */,
FDC438AD27BB148700C60D73 /* UserDeleteMessagesResponse.swift */,
FDC4382B27B380E300C60D73 /* MemberCountResponse.swift */,
FDC4382527B37F6900C60D73 /* DeletedMessagesResponse.swift */,
FDC4384627B47F4D00C60D73 /* Deletion.swift */,
FDC4382727B37FD300C60D73 /* ModeratorsResponse.swift */,
FDC4381B27B354AC00C60D73 /* LegacyPublicKeyBody.swift */,
FDC4383927B4696200C60D73 /* LegacyAuthTokenResponse.swift */,
FDC4381927B34EBA00C60D73 /* LegacyCompactPollBody.swift */,
FDC4384527B47F4D00C60D73 /* LegacyCompactPollResponse.swift */,
FDC4383F27B4746D00C60D73 /* LegacyGetInfoResponse.swift */,
FDC4382927B3802D00C60D73 /* LegacyRoomsResponse.swift */,
FDC4384427B47F4D00C60D73 /* LegacyRoomInfo.swift */,
FDC4384327B47F4D00C60D73 /* OpenGroupMessageV2.swift */,
FDC4383927B4696200C60D73 /* AuthTokenResponse.swift */,
FDC4382B27B380E300C60D73 /* LegacyMemberCountResponse.swift */,
FDC4382727B37FD300C60D73 /* LegacyModeratorsResponse.swift */,
FDC4382527B37F6900C60D73 /* LegacyDeletedMessagesResponse.swift */,
FDC4384627B47F4D00C60D73 /* LegacyDeletion.swift */,
FDC4384327B47F4D00C60D73 /* LegacyOpenGroupMessageV2.swift */,
);
path = Models;
sourceTree = "<group>";
@ -5048,6 +5054,7 @@
C3D9E35E25675F640040E4F3 /* OWSFileSystem.m in Sources */,
C3C2AC2E2553CBEB00C340D1 /* String+Trimming.swift in Sources */,
C32C5B48256DC211003C73A2 /* NSNotificationCenter+OWS.m in Sources */,
FDC438CD27BC641200C60D73 /* Set+Utilities.swift in Sources */,
C3D9E3C925676AF30040E4F3 /* TSYapDatabaseObject.m in Sources */,
FD705A92278D051200F16121 /* ReusableView.swift in Sources */,
B8AE75A425A6C6A6001A84D2 /* Data+Utilities.swift in Sources */,
@ -5151,14 +5158,14 @@
B8F5F60325EDE16F003BF8D4 /* DataExtractionNotification.swift in Sources */,
C32C5D24256DD4C0003C73A2 /* MentionsManager.swift in Sources */,
C3A71D1E25589AC30043A11F /* WebSocketProto.swift in Sources */,
FDC4384A27B47F4D00C60D73 /* Deletion.swift in Sources */,
FDC4384A27B47F4D00C60D73 /* LegacyDeletion.swift in Sources */,
C3C2A7852553AAF300C340D1 /* SessionProtos.pb.swift in Sources */,
B8566C63256F55930045A0B9 /* OWSLinkPreview+Conversion.swift in Sources */,
FDC4382827B37FD300C60D73 /* ModeratorsResponse.swift in Sources */,
FDC4382827B37FD300C60D73 /* LegacyModeratorsResponse.swift in Sources */,
C32C5B3F256DC1DF003C73A2 /* TSQuotedMessage+Conversion.swift in Sources */,
B8EB20EE2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift in Sources */,
FD5D202227B1D74F00FEA984 /* ECKeyPair+Conversion.swift in Sources */,
FDC4381C27B354AC00C60D73 /* PublicKeyBody.swift in Sources */,
FDC4381C27B354AC00C60D73 /* LegacyPublicKeyBody.swift in Sources */,
FDC4382F27B383AF00C60D73 /* UnregisterResponse.swift in Sources */,
FDC4386327B4D94E00C60D73 /* OGMessage.swift in Sources */,
C3C2A7712553A41E00C340D1 /* ControlMessage.swift in Sources */,
@ -5173,7 +5180,7 @@
C32C5D9C256DD6DC003C73A2 /* OWSOutgoingReceiptManager.m in Sources */,
B8AE760B25ABFB5A001A84D2 /* GeneralUtilities.m in Sources */,
C32C5C4F256DCC36003C73A2 /* Storage+OpenGroups.swift in Sources */,
FDC4384727B47F4D00C60D73 /* OpenGroupMessageV2.swift in Sources */,
FDC4384727B47F4D00C60D73 /* LegacyOpenGroupMessageV2.swift in Sources */,
FDC4384F27B4804F00C60D73 /* Header.swift in Sources */,
C3DA9C0725AE7396008F7C7E /* ConfigurationMessage.swift in Sources */,
B8856CEE256F1054001CE70E /* OWSAudioPlayer.m in Sources */,
@ -5185,7 +5192,7 @@
C3BBE0762554CDA60050F1E3 /* Configuration.swift in Sources */,
FDC4387627B5BEF300C60D73 /* FileDownloadResponse.swift in Sources */,
C35D76DB26606304009AA5FB /* ThreadUpdateBatcher.swift in Sources */,
FDC4382C27B380E300C60D73 /* MemberCountResponse.swift in Sources */,
FDC4382C27B380E300C60D73 /* LegacyMemberCountResponse.swift in Sources */,
B8B32033258B235D0020074B /* Storage+Contacts.swift in Sources */,
B8856D69256F141F001CE70E /* OWSWindowManager.m in Sources */,
C3D9E3BE25676AD70040E4F3 /* TSAttachmentPointer.m in Sources */,
@ -5205,7 +5212,7 @@
C32C5BEF256DC8EE003C73A2 /* OWSDisappearingMessagesJob.m in Sources */,
C34A977425A3E34A00852C71 /* ClosedGroupControlMessage.swift in Sources */,
FDC4384C27B47F7700C60D73 /* OpenGroupV2.swift in Sources */,
FDC4382627B37F6900C60D73 /* DeletedMessagesResponse.swift in Sources */,
FDC4382627B37F6900C60D73 /* LegacyDeletedMessagesResponse.swift in Sources */,
B88FA7B826045D100049422F /* OpenGroupAPI.swift in Sources */,
C32C5E97256DE0CB003C73A2 /* OWSPrimaryStorage.m in Sources */,
FDC438C927BB706500C60D73 /* SendDirectMessageRequest.swift in Sources */,
@ -5214,7 +5221,7 @@
B8856E94256F1C37001CE70E /* OWSSounds.m in Sources */,
C32C5BCC256DC830003C73A2 /* Storage+ClosedGroups.swift in Sources */,
C3A3A0EC256E1949004D228D /* OWSRecipientIdentity.m in Sources */,
FDC4383A27B4696200C60D73 /* AuthTokenResponse.swift in Sources */,
FDC4383A27B4696200C60D73 /* LegacyAuthTokenResponse.swift in Sources */,
B8F5F56525EC8453003BF8D4 /* Notification+Contacts.swift in Sources */,
C32C5AB2256DBE8F003C73A2 /* TSMessage.m in Sources */,
C3A3A0FE256E1A3C004D228D /* TSDatabaseSecondaryIndexes.m in Sources */,
@ -5227,6 +5234,7 @@
C3D9E3C025676AD70040E4F3 /* TSAttachment.m in Sources */,
FDC4384827B47F4D00C60D73 /* LegacyRoomInfo.swift in Sources */,
C32C598A256D0664003C73A2 /* SNProtoEnvelope+Conversion.swift in Sources */,
FDC438CB27BB7DB100C60D73 /* UpdateMessageRequest.swift in Sources */,
C352A2FF25574B6300338F3E /* MessageSendJob.swift in Sources */,
FDC438C327BB512200C60D73 /* SodiumProtocols.swift in Sources */,
B8856D11256F112A001CE70E /* OWSAudioSession.swift in Sources */,

View File

@ -26,7 +26,9 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "NO">
shouldUseLaunchSchemeArgsEnv = "NO"
codeCoverageEnabled = "YES"
onlyGenerateCoverageForSpecifiedTargets = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
@ -43,6 +45,64 @@
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
<CodeCoverageTargets>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D221A088169C9E5E00537ABF"
BuildableName = "Session.app"
BlueprintName = "Session"
ReferencedContainer = "container:Session.xcodeproj">
</BuildableReference>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "7BC01A3A241F40AB00BC7C55"
BuildableName = "SessionNotificationServiceExtension.appex"
BlueprintName = "SessionNotificationServiceExtension"
ReferencedContainer = "container:Session.xcodeproj">
</BuildableReference>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "453518671FC635DD00210559"
BuildableName = "SessionShareExtension.appex"
BlueprintName = "SessionShareExtension"
ReferencedContainer = "container:Session.xcodeproj">
</BuildableReference>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C3C2A6EF25539DE700C340D1"
BuildableName = "SessionMessagingKit.framework"
BlueprintName = "SessionMessagingKit"
ReferencedContainer = "container:Session.xcodeproj">
</BuildableReference>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C3C2A59E255385C100C340D1"
BuildableName = "SessionSnodeKit.framework"
BlueprintName = "SessionSnodeKit"
ReferencedContainer = "container:Session.xcodeproj">
</BuildableReference>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C331FF1A2558F9D300070591"
BuildableName = "SessionUIKit.framework"
BlueprintName = "SessionUIKit"
ReferencedContainer = "container:Session.xcodeproj">
</BuildableReference>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C3C2A678255388CC00C340D1"
BuildableName = "SessionUtilitiesKit.framework"
BlueprintName = "SessionUtilitiesKit"
ReferencedContainer = "container:Session.xcodeproj">
</BuildableReference>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C33FD9AA255A548A00E217F9"
BuildableName = "SignalUtilitiesKit.framework"
BlueprintName = "SignalUtilitiesKit"
ReferencedContainer = "container:Session.xcodeproj">
</BuildableReference>
</CodeCoverageTargets>
<Testables>
<TestableReference
skipped = "NO">

View File

@ -1006,7 +1006,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
// If it's an incoming message the user must have moderator status
if (self.interaction.interactionType == OWSInteractionType_IncomingMessage) {
NSString *userPublicKey = [LKStorage.shared getUserPublicKey];
if (![SNOpenGroupAPI isUserModerator:userPublicKey forRoom:openGroupV2.room onServer:openGroupV2.server]) { return; }
if (![SNOpenGroupManager isUserModerator:userPublicKey forRoom:openGroupV2.room onServer:openGroupV2.server]) { return; }
}
// Delete the message
@ -1060,7 +1060,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
if (self.interaction.interactionType == OWSInteractionType_IncomingMessage) {
NSString *userPublicKey = [LKStorage.shared getUserPublicKey];
if (openGroupV2 != nil) {
if (![SNOpenGroupAPI isUserModerator:userPublicKey forRoom:openGroupV2.room onServer:openGroupV2.server]) { return; }
if (![SNOpenGroupManager isUserModerator:userPublicKey forRoom:openGroupV2.room onServer:openGroupV2.server]) { return; }
}
}
@ -1133,7 +1133,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
if (interationType == OWSInteractionType_IncomingMessage) {
// Only allow deletion on incoming messages if the user has moderation permission
if (openGroupV2 != nil) {
return [SNOpenGroupAPI isUserModerator:[SNGeneralUtilities getUserPublicKey] forRoom:openGroupV2.room onServer:openGroupV2.server];
return [SNOpenGroupManager isUserModerator:[SNGeneralUtilities getUserPublicKey] forRoom:openGroupV2.room onServer:openGroupV2.server];
}
} else {
return YES;
@ -1155,7 +1155,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
// Check that we're a moderator
if (openGroupV2 != nil) {
return [SNOpenGroupAPI isUserModerator:[SNGeneralUtilities getUserPublicKey] forRoom:openGroupV2.room onServer:openGroupV2.server];
return [SNOpenGroupManager isUserModerator:[SNGeneralUtilities getUserPublicKey] forRoom:openGroupV2.room onServer:openGroupV2.server];
}
}

View File

@ -163,7 +163,7 @@ private extension MentionSelectionView {
profilePictureView.publicKey = mentionCandidate.publicKey
profilePictureView.update()
if let server = openGroupServer, let room = openGroupRoom {
let isUserModerator = OpenGroupAPI.isUserModerator(mentionCandidate.publicKey, for: room, on: server)
let isUserModerator = OpenGroupManager.isUserModerator(mentionCandidate.publicKey, for: room, on: server)
moderatorIconImageView.isHidden = !isUserModerator
} else {
moderatorIconImageView.isHidden = true

View File

@ -219,7 +219,7 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate {
}
if let senderSessionID = senderSessionID, message.isOpenGroupMessage {
if let openGroupV2 = Storage.shared.getV2OpenGroup(for: message.uniqueThreadId) {
let isUserModerator = OpenGroupAPI.isUserModerator(senderSessionID, for: openGroupV2.room, on: openGroupV2.server)
let isUserModerator = OpenGroupManager.isUserModerator(senderSessionID, for: openGroupV2.room, on: openGroupV2.server)
moderatorIconImageView.isHidden = !isUserModerator || profilePictureView.isHidden
} else {
moderatorIconImageView.isHidden = true

View File

@ -69,9 +69,10 @@ final class JoinOpenGroupModal : Modal {
return presentingViewController!.present(alert, animated: true, completion: nil)
}
presentingViewController!.dismiss(animated: true, completion: nil)
Storage.shared.write { [presentingViewController = self.presentingViewController!] transaction in
OpenGroupManager.shared
.add(room: room, server: server, publicKey: publicKey, using: transaction)
.add(roomToken: room, server: server, publicKey: publicKey, using: transaction)
.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

@ -128,7 +128,7 @@ final class JoinOpenGroupVC : BaseVC, UIPageViewControllerDataSource, UIPageView
// A V2 open group URL will look like: <optional scheme> + <host> + <optional port> + <room> + <public key>
// The host doesn't parse if no explicit scheme is provided
if let (room, server, publicKey) = OpenGroupManager.parseV2OpenGroup(from: string) {
joinV2OpenGroup(room: room, server: server, publicKey: publicKey)
joinV2OpenGroup(roomToken: room, server: server, publicKey: publicKey)
} else {
let title = NSLocalizedString("invalid_url", comment: "")
let message = "Please check the URL you entered and try again."
@ -136,24 +136,25 @@ final class JoinOpenGroupVC : BaseVC, UIPageViewControllerDataSource, UIPageView
}
}
fileprivate func joinV2OpenGroup(room: String, server: String, publicKey: String) {
fileprivate func joinV2OpenGroup(roomToken: String, server: String, publicKey: String) {
guard !isJoining else { return }
isJoining = true
ModalActivityIndicatorViewController.present(fromViewController: navigationController!, canCancel: false) { [weak self] _ in
Storage.shared.write { transaction in
OpenGroupManager.shared.add(room: room, server: server, publicKey: publicKey, using: transaction)
.done(on: DispatchQueue.main) { [weak self] _ in
self?.presentingViewController?.dismiss(animated: true, completion: nil)
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.forceSyncConfigurationNowIfNeeded().retainUntilComplete() // FIXME: It's probably cleaner to do this inside addOpenGroup(...)
}
.catch(on: DispatchQueue.main) { [weak self] error in
self?.dismiss(animated: true, completion: nil) // Dismiss the loader
let title = "Couldn't Join"
let message = error.localizedDescription
self?.isJoining = false
self?.showError(title: title, message: message)
}
OpenGroupManager.shared
.add(roomToken: roomToken, server: server, publicKey: publicKey, using: transaction)
.done(on: DispatchQueue.main) { [weak self] _ in
self?.presentingViewController?.dismiss(animated: true, completion: nil)
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.forceSyncConfigurationNowIfNeeded().retainUntilComplete() // FIXME: It's probably cleaner to do this inside addOpenGroup(...)
}
.catch(on: DispatchQueue.main) { [weak self] error in
self?.dismiss(animated: true, completion: nil) // Dismiss the loader
let title = "Couldn't Join"
let message = error.localizedDescription
self?.isJoining = false
self?.showError(title: title, message: message)
}
}
}
}
@ -166,10 +167,11 @@ final class JoinOpenGroupVC : BaseVC, UIPageViewControllerDataSource, UIPageView
}
}
private final class EnterURLVC : UIViewController, UIGestureRecognizerDelegate, OpenGroupSuggestionGridDelegate {
private final class EnterURLVC: UIViewController, UIGestureRecognizerDelegate, OpenGroupSuggestionGridDelegate {
weak var joinOpenGroupVC: JoinOpenGroupVC!
// MARK: Components
// MARK: - Components
private lazy var urlTextView: TextView = {
let result = TextView(placeholder: NSLocalizedString("vc_enter_chat_url_text_field_hint", comment: ""))
result.keyboardType = .URL
@ -185,7 +187,8 @@ private final class EnterURLVC : UIViewController, UIGestureRecognizerDelegate,
return result
}()
// MARK: Lifecycle
// MARK: - Lifecycle
override func viewDidLoad() {
// Remove background color
view.backgroundColor = .clear
@ -223,7 +226,8 @@ private final class EnterURLVC : UIViewController, UIGestureRecognizerDelegate,
view.addGestureRecognizer(tapGestureRecognizer)
}
// MARK: General
// MARK: - General
func constrainHeight(to height: CGFloat) {
view.set(.height, to: height)
}
@ -232,14 +236,15 @@ private final class EnterURLVC : UIViewController, UIGestureRecognizerDelegate,
urlTextView.resignFirstResponder()
}
// MARK: Interaction
// MARK: - Interaction
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
let location = gestureRecognizer.location(in: view)
return !suggestionGrid.frame.contains(location)
}
func join(_ room: OpenGroupAPI.LegacyRoomInfo) {
joinOpenGroupVC.joinV2OpenGroup(room: room.id, server: OpenGroupAPI.defaultServer, publicKey: OpenGroupAPI.defaultServerPublicKey)
func join(_ room: OpenGroupAPI.Room) {
joinOpenGroupVC.joinV2OpenGroup(roomToken: room.token, server: OpenGroupAPI.defaultServer, publicKey: OpenGroupAPI.defaultServerPublicKey)
}
@objc private func joinOpenGroup() {

View File

@ -3,11 +3,12 @@ import NVActivityIndicatorView
final class OpenGroupSuggestionGrid : UIView, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
private let maxWidth: CGFloat
private var rooms: [OpenGroupAPI.LegacyRoomInfo] = [] { didSet { update() } }
private var rooms: [OpenGroupAPI.Room] = [] { didSet { update() } }
private var heightConstraint: NSLayoutConstraint!
var delegate: OpenGroupSuggestionGridDelegate?
// MARK: UI Components
// MARK: - UI
private lazy var layout: UICollectionViewFlowLayout = {
let result = UICollectionViewFlowLayout()
result.minimumLineSpacing = 0
@ -32,11 +33,13 @@ final class OpenGroupSuggestionGrid : UIView, UICollectionViewDataSource, UIColl
return result
}()
// MARK: Settings
// MARK: - Settings
private static let cellHeight: CGFloat = 40
private static let separatorWidth = 1 / UIScreen.main.scale
// MARK: Initialization
// MARK: - Initialization
init(maxWidth: CGFloat) {
self.maxWidth = maxWidth
super.init(frame: CGRect.zero)
@ -59,16 +62,15 @@ final class OpenGroupSuggestionGrid : UIView, UICollectionViewDataSource, UIColl
spinner.startAnimating()
heightConstraint = set(.height, to: OpenGroupSuggestionGrid.cellHeight)
widthAnchor.constraint(greaterThanOrEqualToConstant: OpenGroupSuggestionGrid.cellHeight).isActive = true
if OpenGroupAPI.defaultRoomsPromise == nil {
OpenGroupAPI.legacyGetDefaultRoomsIfNeeded()
}
let _ = OpenGroupAPI.legacyDefaultRoomsPromise?.done { [weak self] rooms in
// TODO: Update this for the new rooms API
OpenGroupManager.getDefaultRoomsIfNeeded()
_ = OpenGroupManager.defaultRoomsPromise?.done { [weak self] rooms in
self?.rooms = rooms
}
}
// MARK: Updating
// MARK: - Updating
private func update() {
spinner.stopAnimating()
spinner.isHidden = true
@ -78,12 +80,14 @@ final class OpenGroupSuggestionGrid : UIView, UICollectionViewDataSource, UIColl
collectionView.reloadData()
}
// MARK: Layout
// MARK: - Layout
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: maxWidth / 2, height: OpenGroupSuggestionGrid.cellHeight)
}
// MARK: Data Source
// MARK: - Data Source
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return min(rooms.count, 8) // Cap to a maximum of 8 (4 rows of 2)
}
@ -94,18 +98,20 @@ final class OpenGroupSuggestionGrid : UIView, UICollectionViewDataSource, UIColl
return cell
}
// MARK: Interaction
// MARK: - Interaction
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let room = rooms[indexPath.item]
delegate?.join(room)
}
}
// MARK: Cell
// MARK: - Cell
extension OpenGroupSuggestionGrid {
fileprivate final class Cell : UICollectionViewCell {
var room: OpenGroupAPI.LegacyRoomInfo? { didSet { update() } }
var room: OpenGroupAPI.Room? { didSet { update() } }
static let identifier = "OpenGroupSuggestionGridCell"
@ -172,17 +178,24 @@ extension OpenGroupSuggestionGrid {
}
private func update() {
guard let room = room else { return }
let promise = OpenGroupAPI.legacyGetGroupImage(for: room.id, on: OpenGroupAPI.defaultServer)
imageView.image = given(promise.value) { UIImage(data: $0)! }
imageView.isHidden = (imageView.image == nil)
guard let room: OpenGroupAPI.Room = room else { return }
label.text = room.name
if let imageId: Int64 = room.imageId {
let promise = OpenGroupManager.roomImage(imageId, for: room.token, on: OpenGroupAPI.defaultServer)
imageView.image = given(promise.value) { UIImage(data: $0)! }
imageView.isHidden = (imageView.image == nil)
}
else {
imageView.isHidden = true
}
}
}
}
// MARK: Delegate
// MARK: - Delegate
protocol OpenGroupSuggestionGridDelegate {
func join(_ room: OpenGroupAPI.LegacyRoomInfo)
func join(_ room: OpenGroupAPI.Room)
}

View File

@ -2,6 +2,12 @@
public protocol SessionMessagingKitOpenGroupStorageProtocol {
func getOpenGroupImage(for room: String, on server: String) -> Data?
func setOpenGroupImage(to data: Data, for room: String, on server: String, using transaction: Any)
func getV2OpenGroup(for threadID: String) -> OpenGroupV2?
func setV2OpenGroup(_ openGroup: OpenGroupV2, for threadID: String, using transaction: Any)
func getUserCount(forV2OpenGroupWithID openGroupID: String) -> UInt64?
func setUserCount(to newValue: UInt64, forV2OpenGroupWithID openGroupID: String, using transaction: Any)
}
extension Storage: SessionMessagingKitOpenGroupStorageProtocol {

View File

@ -3,7 +3,7 @@
import Foundation
extension OpenGroupAPI {
struct AuthTokenResponse: Codable {
struct LegacyAuthTokenResponse: Codable {
struct Challenge: Codable {
enum CodingKeys: String, CodingKey {
case ciphertext = "ciphertext"
@ -20,7 +20,7 @@ extension OpenGroupAPI {
// MARK: - Codable
extension OpenGroupAPI.AuthTokenResponse.Challenge {
extension OpenGroupAPI.LegacyAuthTokenResponse.Challenge {
init(from decoder: Decoder) throws {
let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
@ -31,7 +31,7 @@ extension OpenGroupAPI.AuthTokenResponse.Challenge {
throw OpenGroupAPI.Error.parsingFailed
}
self = OpenGroupAPI.AuthTokenResponse.Challenge(
self = OpenGroupAPI.LegacyAuthTokenResponse.Challenge(
ciphertext: ciphertext,
ephemeralPublicKey: ephemeralPublicKey
)

View File

@ -15,8 +15,8 @@ extension OpenGroupAPI {
public let room: String
public let statusCode: UInt
public let messages: [OpenGroupMessageV2]?
public let deletions: [Deletion]?
public let messages: [LegacyOpenGroupMessageV2]?
public let deletions: [LegacyDeletion]?
public let moderators: [String]?
}

View File

@ -3,11 +3,11 @@
import Foundation
extension OpenGroupAPI {
struct DeletedMessagesResponse: Codable {
struct LegacyDeletedMessagesResponse: Codable {
enum CodingKeys: String, CodingKey {
case deletions = "ids"
}
let deletions: [Deletion]
let deletions: [LegacyDeletion]
}
}

View File

@ -3,7 +3,7 @@
import Foundation
extension OpenGroupAPI {
public struct Deletion: Codable {
public struct LegacyDeletion: Codable {
enum CodingKeys: String, CodingKey {
case id
case deletedMessageID = "deleted_message_id"
@ -12,12 +12,12 @@ extension OpenGroupAPI {
let id: Int64
let deletedMessageID: Int64
public static func from(_ json: JSON) -> Deletion? {
public static func from(_ json: JSON) -> LegacyDeletion? {
guard let id = json["id"] as? Int64, let deletedMessageID = json["deleted_message_id"] as? Int64 else {
return nil
}
return Deletion(id: id, deletedMessageID: deletedMessageID)
return LegacyDeletion(id: id, deletedMessageID: deletedMessageID)
}
}
}

View File

@ -3,7 +3,7 @@
import Foundation
extension OpenGroupAPI {
struct MemberCountResponse: Codable {
struct LegacyMemberCountResponse: Codable {
enum CodingKeys: String, CodingKey {
case memberCount = "member_count"
}

View File

@ -3,7 +3,7 @@
import Foundation
extension OpenGroupAPI {
struct ModeratorsResponse: Codable {
struct LegacyModeratorsResponse: Codable {
let moderators: [String]
}
}

View File

@ -1,7 +1,7 @@
import Foundation
import SessionUtilitiesKit
public struct OpenGroupMessageV2: Codable {
public struct LegacyOpenGroupMessageV2: Codable {
enum CodingKeys: String, CodingKey {
case serverID = "server_id"
case sender = "public_key"
@ -19,7 +19,7 @@ public struct OpenGroupMessageV2: Codable {
/// a receiving user can verify that the message wasn't tampered with.
public let base64EncodedSignature: String?
public func sign(with publicKey: String) -> OpenGroupMessageV2? {
public func sign(with publicKey: String) -> LegacyOpenGroupMessageV2? {
guard let userKeyPair = SNMessagingKitConfiguration.shared.storage.getUserKeyPair() else { return nil }
guard let data = Data(base64Encoded: base64EncodedData) else { return nil }
guard let signature = try? Ed25519.sign(data, with: userKeyPair) else {
@ -27,7 +27,7 @@ public struct OpenGroupMessageV2: Codable {
return nil
}
return OpenGroupMessageV2(
return LegacyOpenGroupMessageV2(
serverID: serverID,
sender: sender,
sentTimestamp: sentTimestamp,
@ -39,7 +39,7 @@ public struct OpenGroupMessageV2: Codable {
// MARK: - Decoder
extension OpenGroupMessageV2 {
extension LegacyOpenGroupMessageV2 {
public init(from decoder: Decoder) throws {
let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
@ -60,7 +60,7 @@ extension OpenGroupMessageV2 {
throw OpenGroupAPI.Error.parsingFailed
}
self = OpenGroupMessageV2(
self = LegacyOpenGroupMessageV2(
serverID: try? container.decode(Int64.self, forKey: .serverID),
sender: sender,
sentTimestamp: try container.decode(UInt64.self, forKey: .sentTimestamp),

View File

@ -3,7 +3,7 @@
import Foundation
extension OpenGroupAPI {
struct PublicKeyBody: Codable {
struct LegacyPublicKeyBody: Codable {
enum CodingKeys: String, CodingKey {
case publicKey = "public_key"
}

View File

@ -71,3 +71,37 @@ extension OpenGroupAPI {
public let details: Room?
}
}
// MARK: - Convenience
extension OpenGroupAPI.RoomPollInfo {
init(room: OpenGroupAPI.Room) {
self.init(
token: room.token,
created: room.created,
name: room.name,
description: room.description,
imageId: room.imageId,
infoUpdates: room.infoUpdates,
messageSequence: room.messageSequence,
activeUsers: room.activeUsers,
activeUsersCutoff: room.activeUsersCutoff,
pinnedMessages: room.pinnedMessages,
admin: room.admin,
globalAdmin: room.globalAdmin,
admins: room.admins,
hiddenAdmins: room.hiddenAdmins,
moderator: room.moderator,
globalModerator: room.globalModerator,
moderators: room.moderators,
hiddenModerators: room.hiddenModerators,
read: room.read,
defaultRead: room.defaultRead,
write: room.write,
defaultWrite: room.defaultWrite,
upload: room.upload,
defaultUpload: room.defaultUpload,
details: nil
)
}
}

View File

@ -45,23 +45,5 @@ extension OpenGroupAPI {
try container.encode(whisperMods, forKey: .whisperMods)
try container.encodeIfPresent(fileIds, forKey: .fileIds)
}
// MARK: - Signing
public static func sign(message: Data, for idType: IdPrefix, with publicKey: String) -> (data: Data, signature: Data)? {
guard let userKeyPair: ECKeyPair = SNMessagingKitConfiguration.shared.storage.getUserKeyPair() else {
return nil
}
guard let targetKeyPair: ECKeyPair = try? userKeyPair.convert(to: idType, with: publicKey) else {
return nil
}
guard let signature = try? Ed25519.sign(message, with: targetKeyPair) else {
SNLog("Failed to sign open group message.")
return nil
}
return (message, signature)
}
}
}

View File

@ -0,0 +1,19 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
extension OpenGroupAPI {
public struct UpdateMessageRequest: Codable {
let data: Data
let signature: Data
// MARK: - Encodable
public func encode(to encoder: Encoder) throws {
var container: KeyedEncodingContainer<CodingKeys> = encoder.container(keyedBy: CodingKeys.self)
try container.encode(data.base64EncodedString(), forKey: .data)
try container.encode(signature.base64EncodedString(), forKey: .signature)
}
}
}

View File

@ -7,11 +7,6 @@ extension OpenGroupAPI {
// TODO: Upgrade this to use the non-legacy version.
return AnyPromise.from(legacyDeleteMessage(with: serverID, from: room, on: server))
}
@objc(isUserModerator:forRoom:onServer:)
public static func objc_isUserModerator(_ publicKey: String, for room: String, on server: String) -> Bool {
return isUserModerator(publicKey, for: room, on: server)
}
@objc(legacyGetDefaultRoomsIfNeeded)
public static func objc_legacyGetDefaultRoomsIfNeeded() {

View File

@ -11,42 +11,46 @@ public final class OpenGroupAPI: NSObject {
public static let defaultServer = "http://116.203.70.33"
public static let defaultServerPublicKey = "a03c383cf63c3c4efe67acc52112a6dd734b3a946b9545f488aaa93da7991238"
// MARK: - Cache
private static var authTokenPromises: Atomic<[String: Promise<String>]> = Atomic([:])
private static var hasPerformedInitialPoll: [String: Bool] = [:]
private static var hasUpdatedLastOpenDate = false
public static let workQueue = DispatchQueue(label: "OpenGroupAPI.workQueue", qos: .userInitiated) // It's important that this is a serial queue
public static var moderators: [String: [String: Set<String>]] = [:] // Server URL to room ID to set of moderator IDs
public static var defaultRoomsPromise: Promise<[Room]>?
public static var groupImagePromises: [String: Promise<Data>] = [:]
// MARK: - Polling State
private static var hasPerformedInitialPoll: [String: Bool] = [:]
private static var timeSinceLastPoll: [String: TimeInterval] = [:]
private static var lastPollTime: TimeInterval = .greatestFiniteMagnitude
private static let timeSinceLastOpen: TimeInterval = {
guard let lastOpen = UserDefaults.standard[.lastOpen] else { return .greatestFiniteMagnitude }
return Date().timeIntervalSince(lastOpen)
}()
// TODO: Remove these
private static var legacyAuthTokenPromises: Atomic<[String: Promise<String>]> = Atomic([:])
private static var legacyHasUpdatedLastOpenDate = false
private static var legacyGroupImagePromises: [String: Promise<Data>] = [:]
// MARK: - Batching & Polling
/// This is a convenience method which calls `/batch` with a pre-defined set of requests used to update an Open Group
/// This is a convenience method which calls `/batch` with a pre-defined set of requests used to update an Open
/// Group, currently this will retrieve:
/// - Capabilities for the server
/// - For each room:
/// - Poll Info
/// - Messages (includes additions and deletions)
public static func poll(_ server: String, using dependencies: Dependencies = Dependencies()) -> Promise<[Endpoint: (OnionRequestResponseInfoType, Codable)]> {
// TODO: Remove comments
// Capabilities
// Fetch each room
// Poll Info
// /room/<token>/pollInfo/<id> instead?
// Fetch messages for each room
// /room/{roomToken}/messages/since/{messageSequence}:
// Fetch deletions for each room (included in messages)
// Store a local copy of the cached state for this server
let hadPerformedInitialPoll: Bool = (hasPerformedInitialPoll[server] == true)
let originalTimeSinceLastPoll: TimeInterval = (timeSinceLastPoll[server] ?? min(lastPollTime, timeSinceLastOpen))
// old compact_poll data
// public let room: String
// public let statusCode: UInt
// public let messages: [OpenGroupMessageV2]?
// public let deletions: [Deletion]?
// public let moderators: [String]?
// Update the cached state for this server
hasPerformedInitialPoll[server] = true
lastPollTime = min(lastPollTime, timeSinceLastOpen)
UserDefaults.standard[.lastOpen] = Date()
// Generate the requests
let requestResponseType: [BatchRequestInfo] = [
BatchRequestInfo(
request: Request(
@ -59,27 +63,37 @@ public final class OpenGroupAPI: NSObject {
]
.appending(
dependencies.storage.getAllV2OpenGroups().values
.filter { $0.server == server.lowercased() } // Note: The `OpenGroupV2` converts the server value to lowercase during init
.filter { $0.server == server.lowercased() } // Note: The `OpenGroupV2` type converts to lowercase in init
.flatMap { openGroup -> [BatchRequestInfo] in
let lastSeqNo: Int64? = dependencies.storage.getLastMessageServerID(for: openGroup.room, on: server)
let targetSeqNo: Int64 = (lastSeqNo ?? 0)
let shouldRetrieveRecentMessages: Bool = (
lastSeqNo == nil || (
// If it's the first poll for this launch and it's been longer than
// 'maxInactivityPeriod' then just retrieve recent messages instead
// of trying to get all messages since the last one retrieved
!hadPerformedInitialPoll &&
originalTimeSinceLastPoll > OpenGroupAPI.Poller.maxInactivityPeriod
)
)
return [
BatchRequestInfo(
request: Request(
server: server,
// TODO: Source the '0' from the open group (will need to add a new field and default to 0)
endpoint: .roomPollInfo(openGroup.room, 0)
endpoint: .roomPollInfo(openGroup.room, openGroup.infoUpdates)
),
responseType: RoomPollInfo.self
),
BatchRequestInfo(
request: Request(
server: server,
endpoint: (lastSeqNo == nil ?
endpoint: (shouldRetrieveRecentMessages ?
.roomMessagesRecent(openGroup.room) :
.roomMessagesSince(openGroup.room, seqNo: targetSeqNo)
)
// TODO: Limit?
// queryParameters: [ .limit: 256 ]
),
responseType: [Message].self
)
@ -87,7 +101,6 @@ public final class OpenGroupAPI: NSObject {
}
)
// TODO: Handle response (maybe in the poller or the OpenGroupManager?)
return batch(server, requests: requestResponseType, using: dependencies)
}
@ -121,64 +134,35 @@ public final class OpenGroupAPI: NSObject {
}
}
// TODO: `/sequence` request.
public static func compactPoll(_ server: String, using dependencies: Dependencies = Dependencies()) -> Promise<LegacyCompactPollResponse> {
let rooms: [String] = dependencies.storage.getAllV2OpenGroups().values
.filter { $0.server == server }
.map { $0.room }
let useMessageLimit = (hasPerformedInitialPoll[server] != true && timeSinceLastOpen > OpenGroupAPI.Poller.maxInactivityPeriod)
hasPerformedInitialPoll[server] = true
if !hasUpdatedLastOpenDate {
UserDefaults.standard[.lastOpen] = dependencies.date
hasUpdatedLastOpenDate = true
}
let requestBody: LegacyCompactPollBody = LegacyCompactPollBody(
requests: rooms
.map { roomId -> LegacyCompactPollBody.Room in
LegacyCompactPollBody.Room(
id: roomId,
fromMessageServerId: (useMessageLimit ? nil :
dependencies.storage.getLastMessageServerID(for: roomId, on: server)
),
fromDeletionServerId: (useMessageLimit ? nil :
dependencies.storage.getLastDeletionServerID(for: roomId, on: server)
),
legacyAuthToken: nil
)
}
)
/// The requests are guaranteed to be performed sequentially in the order given in the request and will abort if any request does not return a status-`2xx` response.
///
/// For example, this can be used to ban and delete all of a user's messages by sequencing the ban followed by the `delete_all`: if the ban fails (e.g. because
/// permission is denied) then the `delete_all` will not occur. The batch body and response are identical to the `/batch` endpoint; requests that are not
/// carried out because of an earlier failure will have a response code of `412` (Precondition Failed)."
private static func sequence(_ server: String, requests: [BatchRequestInfo], using dependencies: Dependencies = Dependencies()) -> Promise<[Endpoint: (OnionRequestResponseInfoType, Codable)]> {
let requestBody: BatchRequest = requests.map { BatchSubRequest(request: $0.request) }
let responseTypes = requests.map { $0.responseType }
guard let body: Data = try? JSONEncoder().encode(requestBody) else {
return Promise(error: HTTP.Error.invalidJSON)
}
let request = Request(
let request: Request = Request(
method: .post,
server: server,
endpoint: .legacyCompactPoll(legacyAuth: false),
endpoint: .sequence,
body: body
)
// TODO: Handle a `412` response (ie. a required capability isn't supported)
return send(request, using: dependencies)
.then(on: OpenGroupAPI.workQueue) { _, maybeData -> Promise<LegacyCompactPollResponse> in
guard let data: Data = maybeData else { throw Error.parsingFailed }
let response: LegacyCompactPollResponse = try data.decoded(as: LegacyCompactPollResponse.self, customError: Error.parsingFailed)
return when(
fulfilled: response.results
.map { (result: LegacyCompactPollResponse.Result) in
legacyProcess(messages: result.messages, for: result.room, on: server)
.then(on: OpenGroupAPI.workQueue) { _ in
process(deletions: result.deletions, for: result.room, on: server)
}
}
).then(on: OpenGroupAPI.workQueue) { _ in Promise.value(response) }
}
.decoded(as: responseTypes, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
.map { result in
result.enumerated()
.reduce(into: [:]) { prev, next in
prev[requests[next.offset].request.endpoint] = next.element
}
}
}
// MARK: - Capabilities
@ -239,13 +223,13 @@ public final class OpenGroupAPI: NSObject {
using dependencies: Dependencies = Dependencies()
) -> Promise<(OnionRequestResponseInfoType, Message)> {
// TODO: Change this to use '.blinded' once it's working.
guard let signedRequest: (data: Data, signature: Data) = SendMessageRequest.sign(message: plaintext, for: .standard, with: serverPublicKey) else {
guard let signedMessage: (data: Data, signature: Data) = sign(message: plaintext, for: .standard, with: serverPublicKey) else {
return Promise(error: Error.signingFailed)
}
let requestBody: SendMessageRequest = SendMessageRequest(
data: signedRequest.data,
signature: signedRequest.signature,
data: signedMessage.data,
signature: signedMessage.signature,
whisperTo: whisperTo,
whisperMods: whisperMods,
fileIds: nil // TODO: Add support for 'fileIds'.
@ -265,62 +249,97 @@ public final class OpenGroupAPI: NSObject {
return send(request, using: dependencies)
.decoded(as: Message.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
}
public static func message(_ id: Int64, in roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, Message)> {
let request: Request = Request(
server: server,
endpoint: .roomMessageIndividual(roomToken, id: id)
)
return send(request, using: dependencies)
.decoded(as: Message.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
}
public static func messageUpdate(
_ id: Int64,
plaintext: Data,
in roomToken: String,
on server: String,
with serverPublicKey: String,
using dependencies: Dependencies = Dependencies()
) -> Promise<(OnionRequestResponseInfoType, Data?)> {
// TODO: Change this to use '.blinded' once it's working.
guard let signedMessage: (data: Data, signature: Data) = sign(message: plaintext, for: .standard, with: serverPublicKey) else {
return Promise(error: Error.signingFailed)
}
let requestBody: UpdateMessageRequest = UpdateMessageRequest(
data: signedMessage.data,
signature: signedMessage.signature
)
guard let body: Data = try? JSONEncoder().encode(requestBody) else {
return Promise(error: Error.parsingFailed)
}
let request: Request = Request(
method: .put,
server: server,
endpoint: .roomMessageIndividual(roomToken, id: id),
body: body
)
// TODO: Handle custom response info?
return send(request, using: dependencies)
}
/// This is the direct request to retrieve recent messages from an Open Group so should be retrieved automatically from the `poll()`
/// method, if the logic should change then remove the `@available` line and make sure to route the response of this method to
/// the `OpenGroupManager` `handleMessages` method (otherwise the logic may not work correctly)
@available(*, unavailable, message: "Avoid using this directly, use the pre-build `poll()` method instead")
public static func recentMessages(in roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, [Message])> {
// TODO: Recent vs. Since?.
let request: Request = Request(
server: server,
endpoint: .roomMessagesRecent(roomToken)
// TODO: Limit?.
// queryParameters: [
// .fromServerId: storage.getLastMessageServerID(for: room, on: server).map { String($0) }
// ].compactMapValues { $0 }
// queryParameters: [ .limit: 50 ]
)
return send(request, using: dependencies)
.decoded(as: [Message].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
.then(on: OpenGroupAPI.workQueue) { responseInfo, messages -> Promise<(OnionRequestResponseInfoType, [Message])> in
process(messages: messages, for: roomToken, on: server, using: dependencies)
.map { processedMessages in (responseInfo, processedMessages) }
}
}
/// This is the direct request to retrieve recent messages from an Open Group so should be retrieved automatically from the `poll()`
/// method, if the logic should change then remove the `@available` line and make sure to route the response of this method to
/// the `OpenGroupManager` `handleMessages` method (otherwise the logic may not work correctly)
@available(*, unavailable, message: "Avoid using this directly, use the pre-build `poll()` method instead")
public static func messagesBefore(messageId: Int64, in roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, [Message])> {
// TODO: Recent vs. Since?.
// TODO: Do we need to be able to load old messages?
let request: Request = Request(
server: server,
endpoint: .roomMessagesBefore(roomToken, id: messageId)
// TODO: Limit?.
// queryParameters: [
// .fromServerId: storage.getLastMessageServerID(for: room, on: server).map { String($0) }
// ].compactMapValues { $0 }
// queryParameters: [ .limit: 50 ]
)
return send(request, using: dependencies)
.decoded(as: [Message].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
.then(on: OpenGroupAPI.workQueue) { responseInfo, messages -> Promise<(OnionRequestResponseInfoType, [Message])> in
process(messages: messages, for: roomToken, on: server, using: dependencies)
.map { processedMessages in (responseInfo, processedMessages) }
}
}
/// This is the direct request to retrieve recent messages from an Open Group so should be retrieved automatically from the `poll()`
/// method, if the logic should change then remove the `@available` line and make sure to route the response of this method to
/// the `OpenGroupManager` `handleMessages` method (otherwise the logic may not work correctly)
@available(*, unavailable, message: "Avoid using this directly, use the pre-build `poll()` method instead")
public static func messagesSince(seqNo: Int64, in roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, [Message])> {
// TODO: Recent vs. Since?.
let request: Request = Request(
server: server,
endpoint: .roomMessagesSince(roomToken, seqNo: seqNo)
// TODO: Limit?.
// queryParameters: [
// .fromServerId: storage.getLastMessageServerID(for: room, on: server).map { String($0) }
// ].compactMapValues { $0 }
// queryParameters: [ .limit: 50 ]
)
return send(request, using: dependencies)
.decoded(as: [Message].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
.then(on: OpenGroupAPI.workQueue) { responseInfo, messages -> Promise<(OnionRequestResponseInfoType, [Message])> in
process(messages: messages, for: roomToken, on: server, using: dependencies)
.map { processedMessages in (responseInfo, processedMessages) }
}
}
// MARK: - Pinning
@ -360,45 +379,6 @@ public final class OpenGroupAPI: NSObject {
// MARK: - Files
// TODO: Shift this logic to the `OpenGroupManager` (makes more sense since it's not API logic).
public static func roomImage(_ fileId: Int64, for roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<Data> {
// Normally the image for a given group is stored with the group thread, so it's only
// fetched once. However, on the join open group screen we show images for groups the
// user * hasn't * joined yet. We don't want to re-fetch these images every time the
// user opens the app because that could slow the app down or be data-intensive. So
// instead we assume that these images don't change that often and just fetch them once
// a week. We also assume that they're all fetched at the same time as well, so that
// we only need to maintain one date in user defaults. On top of all of this we also
// don't double up on fetch requests by storing the existing request as a promise if
// there is one.
let lastOpenGroupImageUpdate: Date? = UserDefaults.standard[.lastOpenGroupImageUpdate]
let now: Date = dependencies.date
let timeSinceLastUpdate: TimeInterval = (given(lastOpenGroupImageUpdate) { now.timeIntervalSince($0) } ?? .greatestFiniteMagnitude)
let updateInterval: TimeInterval = (7 * 24 * 60 * 60)
if let data = dependencies.storage.getOpenGroupImage(for: roomToken, on: server), server == defaultServer, timeSinceLastUpdate < updateInterval {
return Promise.value(data)
}
if let promise = groupImagePromises["\(server).\(roomToken)"] {
return promise
}
let promise: Promise<Data> = downloadFile(fileId, from: roomToken, on: server, using: dependencies)
.map { _, data in data }
_ = promise.done(on: OpenGroupAPI.workQueue) { imageData in
if server == defaultServer {
dependencies.storage.write { transaction in
dependencies.storage.setOpenGroupImage(to: imageData, for: roomToken, on: server, using: transaction)
}
UserDefaults.standard[.lastOpenGroupImageUpdate] = now
}
}
groupImagePromises["\(server).\(roomToken)"] = promise
return promise
}
public static func uploadFile(_ bytes: [UInt8], fileName: String? = nil, to roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, FileUploadResponse)> {
let request: Request = Request(
method: .post,
@ -475,13 +455,13 @@ public final class OpenGroupAPI: NSObject {
public static func sendMessageRequest(_ plaintext: Data, to sessionId: String, on server: String, with serverPublicKey: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, [DirectMessage])> {
// TODO: Change this to use '.blinded' once it's working
guard let signedRequest: (data: Data, signature: Data) = SendMessageRequest.sign(message: plaintext, for: .standard, with: serverPublicKey) else {
guard let signedMessage: (data: Data, signature: Data) = sign(message: plaintext, for: .standard, with: serverPublicKey) else {
return Promise(error: Error.signingFailed)
}
let requestBody: SendDirectMessageRequest = SendDirectMessageRequest(
data: signedRequest.data,
signature: signedRequest.signature
data: signedMessage.data,
signature: signedMessage.signature
)
guard let body: Data = try? JSONEncoder().encode(requestBody) else {
@ -609,96 +589,24 @@ public final class OpenGroupAPI: NSObject {
.decoded(as: UserDeleteMessagesResponse.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
}
// MARK: - Processing
// TODO: Move these methods to the OpenGroupManager? (seems odd for them to be in the API).
private static func process(messages: [Message]?, for room: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<[Message]> {
guard let messages: [Message] = messages, !messages.isEmpty else { return Promise.value([]) }
let seqNo: Int64 = (messages.compactMap { $0.seqNo }.max() ?? 0)
let lastMessageSeqNo: Int64 = (dependencies.storage.getLastMessageServerID(for: room, on: server) ?? 0)
if seqNo > lastMessageSeqNo {
let (promise, seal) = Promise<[Message]>.pending()
dependencies.storage.write(
with: { transaction in
dependencies.storage.setLastMessageServerID(for: room, on: server, to: seqNo, using: transaction)
},
completion: {
seal.fulfill(messages)
}
)
return promise
}
return Promise.value(messages)
}
private static func process(deletions: [Deletion]?, for room: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<[Deletion]> {
guard let deletions: [Deletion] = deletions else { return Promise.value([]) }
let serverID: Int64 = (deletions.compactMap { $0.id }.max() ?? 0)
let lastDeletionServerID: Int64 = (dependencies.storage.getLastDeletionServerID(for: room, on: server) ?? 0)
if serverID > lastDeletionServerID {
let (promise, seal) = Promise<[Deletion]>.pending()
dependencies.storage.write(
with: { transaction in
dependencies.storage.setLastDeletionServerID(for: room, on: server, to: serverID, using: transaction)
},
completion: {
seal.fulfill(deletions)
}
)
return promise
}
return Promise.value(deletions)
}
public static func isUserModerator(_ publicKey: String, for room: String, on server: String) -> Bool {
return moderators[server]?[room]?.contains(publicKey) ?? false
}
// MARK: - General
// TODO: Shift this to the OpenGroupManager? (seems more at place there than in the API)
public static func getDefaultRoomsIfNeeded(using dependencies: Dependencies = Dependencies()) {
Storage.shared.write(
with: { transaction in
dependencies.storage.setOpenGroupPublicKey(for: defaultServer, to: defaultServerPublicKey, using: transaction)
},
completion: {
let promise = attempt(maxRetryCount: 8, recoveringOn: DispatchQueue.main) {
OpenGroupAPI.rooms(for: defaultServer, using: dependencies)
.map { _, data in data }
}
_ = promise.done(on: OpenGroupAPI.workQueue) { items in
items
.compactMap { room -> (Int64, String)? in
guard let imageId: Int64 = room.imageId else { return nil}
return (imageId, room.token)
}
.forEach { imageId, roomToken in
roomImage(imageId, for: roomToken, on: defaultServer, using: dependencies)
.retainUntilComplete()
}
}
promise.catch(on: OpenGroupAPI.workQueue) { _ in
OpenGroupAPI.defaultRoomsPromise = nil
}
defaultRoomsPromise = promise
}
)
}
// MARK: - Authentication
public static func sign(message: Data, for idType: IdPrefix, with publicKey: String, using dependencies: Dependencies = Dependencies()) -> (data: Data, signature: Data)? {
guard let userKeyPair: ECKeyPair = dependencies.storage.getUserKeyPair() else {
return nil
}
guard let targetKeyPair: ECKeyPair = try? userKeyPair.convert(to: idType, with: publicKey) else {
return nil
}
guard let signature = try? Ed25519.sign(message, with: targetKeyPair) else {
SNLog("Failed to sign open group message.")
return nil
}
return (message, signature)
}
private static func sign(_ request: URLRequest, with publicKey: String, using dependencies: Dependencies = Dependencies()) -> URLRequest? {
guard let url: URL = request.url else { return nil }
@ -812,7 +720,7 @@ public final class OpenGroupAPI: NSObject {
return Promise.value(authToken)
}
if let authTokenPromise: Promise<String> = authTokenPromises.wrappedValue["\(server).\(room)"] {
if let authTokenPromise: Promise<String> = legacyAuthTokenPromises.wrappedValue["\(server).\(room)"] {
return authTokenPromise
}
@ -830,13 +738,13 @@ public final class OpenGroupAPI: NSObject {
promise
.done(on: OpenGroupAPI.workQueue) { _ in
authTokenPromises.wrappedValue["\(server).\(room)"] = nil
legacyAuthTokenPromises.wrappedValue["\(server).\(room)"] = nil
}
.catch(on: OpenGroupAPI.workQueue) { _ in
authTokenPromises.wrappedValue["\(server).\(room)"] = nil
legacyAuthTokenPromises.wrappedValue["\(server).\(room)"] = nil
}
authTokenPromises.wrappedValue["\(server).\(room)"] = promise
legacyAuthTokenPromises.wrappedValue["\(server).\(room)"] = promise
return promise
}
@ -859,7 +767,7 @@ public final class OpenGroupAPI: NSObject {
return legacySend(request).map(on: OpenGroupAPI.workQueue) { _, maybeData in
guard let data: Data = maybeData else { throw Error.parsingFailed }
let response = try data.decoded(as: AuthTokenResponse.self, customError: Error.parsingFailed)
let response = try data.decoded(as: LegacyAuthTokenResponse.self, customError: Error.parsingFailed)
let symmetricKey = try AESGCM.generateSymmetricKey(x25519PublicKey: response.challenge.ephemeralPublicKey, x25519PrivateKey: userKeyPair.privateKey)
guard let tokenAsData = try? AESGCM.decrypt(response.challenge.ciphertext, with: symmetricKey) else {
@ -872,7 +780,7 @@ public final class OpenGroupAPI: NSObject {
@available(*, deprecated, message: "Use request signing instead")
public static func legacyClaimAuthToken(_ authToken: String, for room: String, on server: String) -> Promise<String> {
let requestBody: PublicKeyBody = PublicKeyBody(publicKey: getUserHexEncodedPublicKey())
let requestBody: LegacyPublicKeyBody = LegacyPublicKeyBody(publicKey: getUserHexEncodedPublicKey())
guard let body: Data = try? JSONEncoder().encode(requestBody) else {
return Promise(error: HTTP.Error.invalidJSON)
@ -926,9 +834,9 @@ public final class OpenGroupAPI: NSObject {
hasPerformedInitialPoll[server] = true
if !hasUpdatedLastOpenDate {
if !legacyHasUpdatedLastOpenDate {
UserDefaults.standard[.lastOpen] = Date()
hasUpdatedLastOpenDate = true
legacyHasUpdatedLastOpenDate = true
}
for room in rooms {
@ -985,7 +893,7 @@ public final class OpenGroupAPI: NSObject {
return when(
fulfilled: response.results
.compactMap { (result: LegacyCompactPollResponse.Result) -> Promise<[Deletion]>? in
.compactMap { (result: LegacyCompactPollResponse.Result) -> Promise<[LegacyDeletion]>? in
// A 401 means that we didn't provide a (valid) auth token for a route that
// required one. We use this as an indication that the token we're using has
// expired. Note that a 403 has a different meaning; it means that we provided
@ -1000,7 +908,7 @@ public final class OpenGroupAPI: NSObject {
}
return legacyProcess(messages: result.messages, for: result.room, on: server)
.then(on: OpenGroupAPI.workQueue) { _ -> Promise<[Deletion]> in
.then(on: OpenGroupAPI.workQueue) { _ -> Promise<[LegacyDeletion]> in
legacyProcess(deletions: result.deletions, for: result.room, on: server)
}
}
@ -1023,7 +931,7 @@ public final class OpenGroupAPI: NSObject {
items.forEach { legacyGetGroupImage(for: $0.id, on: defaultServer).retainUntilComplete() }
}
promise.catch(on: OpenGroupAPI.workQueue) { _ in
OpenGroupAPI.defaultRoomsPromise = nil
OpenGroupAPI.legacyDefaultRoomsPromise = nil
}
legacyDefaultRoomsPromise = promise
}
@ -1085,7 +993,7 @@ public final class OpenGroupAPI: NSObject {
return Promise.value(data)
}
if let promise = groupImagePromises["\(server).\(room)"] {
if let promise = legacyGroupImagePromises["\(server).\(room)"] {
return promise
}
@ -1109,7 +1017,7 @@ public final class OpenGroupAPI: NSObject {
return response.data
}
groupImagePromises["\(server).\(room)"] = promise
legacyGroupImagePromises["\(server).\(room)"] = promise
return promise
}
@ -1125,7 +1033,7 @@ public final class OpenGroupAPI: NSObject {
return legacySend(request)
.map(on: OpenGroupAPI.workQueue) { _, maybeData in
guard let data: Data = maybeData else { throw Error.parsingFailed }
let response: MemberCountResponse = try data.decoded(as: MemberCountResponse.self, customError: Error.parsingFailed)
let response: LegacyMemberCountResponse = try data.decoded(as: LegacyMemberCountResponse.self, customError: Error.parsingFailed)
let storage = SNMessagingKitConfiguration.shared.storage
storage.write { transaction in
@ -1171,7 +1079,7 @@ public final class OpenGroupAPI: NSObject {
// MARK: - Legacy Message Sending & Receiving
@available(*, deprecated, message: "Use send(_:to:on:whisperTo:whisperMods:with:) instead")
public static func legacySend(_ message: OpenGroupMessageV2, to room: String, on server: String, with publicKey: String) -> Promise<OpenGroupMessageV2> {
public static func legacySend(_ message: LegacyOpenGroupMessageV2, to room: String, on server: String, with publicKey: String) -> Promise<LegacyOpenGroupMessageV2> {
guard let signedMessage = message.sign(with: publicKey) else { return Promise(error: Error.signingFailed) }
guard let body: Data = try? JSONEncoder().encode(signedMessage) else {
return Promise(error: Error.parsingFailed)
@ -1180,7 +1088,7 @@ public final class OpenGroupAPI: NSObject {
return legacySend(request).map(on: OpenGroupAPI.workQueue) { _, maybeData in
guard let data: Data = maybeData else { throw Error.parsingFailed }
let message: OpenGroupMessageV2 = try data.decoded(as: OpenGroupMessageV2.self, customError: Error.parsingFailed)
let message: LegacyOpenGroupMessageV2 = try data.decoded(as: LegacyOpenGroupMessageV2.self, customError: Error.parsingFailed)
Storage.shared.write { transaction in
Storage.shared.addReceivedMessageTimestamp(message.sentTimestamp, using: transaction)
}
@ -1189,7 +1097,7 @@ public final class OpenGroupAPI: NSObject {
}
@available(*, deprecated, message: "Use recentMessages(in:on:) or messagesSince(seqNo:in:on:) instead")
public static func legacyGetMessages(for room: String, on server: String) -> Promise<[OpenGroupMessageV2]> {
public static func legacyGetMessages(for room: String, on server: String) -> Promise<[LegacyOpenGroupMessageV2]> {
let storage = SNMessagingKitConfiguration.shared.storage
let request: Request = Request(
server: server,
@ -1200,9 +1108,9 @@ public final class OpenGroupAPI: NSObject {
].compactMapValues { $0 }
)
return legacySend(request).then(on: OpenGroupAPI.workQueue) { _, maybeData -> Promise<[OpenGroupMessageV2]> in
return legacySend(request).then(on: OpenGroupAPI.workQueue) { _, maybeData -> Promise<[LegacyOpenGroupMessageV2]> in
guard let data: Data = maybeData else { throw Error.parsingFailed }
let messages: [OpenGroupMessageV2] = try data.decoded(as: [OpenGroupMessageV2].self, customError: Error.parsingFailed)
let messages: [LegacyOpenGroupMessageV2] = try data.decoded(as: [LegacyOpenGroupMessageV2].self, customError: Error.parsingFailed)
return legacyProcess(messages: messages, for: room, on: server)
}
@ -1224,7 +1132,7 @@ public final class OpenGroupAPI: NSObject {
}
@available(*, deprecated, message: "Use v4 endpoint instead")
public static func legacyGetDeletedMessages(for room: String, on server: String) -> Promise<[Deletion]> {
public static func legacyGetDeletedMessages(for room: String, on server: String) -> Promise<[LegacyDeletion]> {
let storage = SNMessagingKitConfiguration.shared.storage
let request: Request = Request(
@ -1236,11 +1144,11 @@ public final class OpenGroupAPI: NSObject {
].compactMapValues { $0 }
)
return legacySend(request).then(on: OpenGroupAPI.workQueue) { _, maybeData -> Promise<[Deletion]> in
return legacySend(request).then(on: OpenGroupAPI.workQueue) { _, maybeData -> Promise<[LegacyDeletion]> in
guard let data: Data = maybeData else { throw Error.parsingFailed }
let response: DeletedMessagesResponse = try data.decoded(as: DeletedMessagesResponse.self, customError: Error.parsingFailed)
let response: LegacyDeletedMessagesResponse = try data.decoded(as: LegacyDeletedMessagesResponse.self, customError: Error.parsingFailed)
return process(deletions: response.deletions, for: room, on: server)
return legacyProcess(deletions: response.deletions, for: room, on: server)
}
}
@ -1257,7 +1165,7 @@ public final class OpenGroupAPI: NSObject {
return legacySend(request)
.map(on: OpenGroupAPI.workQueue) { _, maybeData in
guard let data: Data = maybeData else { throw Error.parsingFailed }
let response: ModeratorsResponse = try data.decoded(as: ModeratorsResponse.self, customError: Error.parsingFailed)
let response: LegacyModeratorsResponse = try data.decoded(as: LegacyModeratorsResponse.self, customError: Error.parsingFailed)
if var x = self.moderators[server] {
x[room] = Set(response.moderators)
@ -1273,7 +1181,7 @@ public final class OpenGroupAPI: NSObject {
@available(*, deprecated, message: "Use v4 endpoint instead")
public static func legacyBan(_ publicKey: String, from room: String, on server: String) -> Promise<Void> {
let requestBody: PublicKeyBody = PublicKeyBody(publicKey: getUserHexEncodedPublicKey())
let requestBody: LegacyPublicKeyBody = LegacyPublicKeyBody(publicKey: getUserHexEncodedPublicKey())
guard let body: Data = try? JSONEncoder().encode(requestBody) else {
return Promise(error: HTTP.Error.invalidJSON)
@ -1292,7 +1200,7 @@ public final class OpenGroupAPI: NSObject {
@available(*, deprecated, message: "Use v4 endpoint instead")
public static func legacyBanAndDeleteAllMessages(_ publicKey: String, from room: String, on server: String) -> Promise<Void> {
let requestBody: PublicKeyBody = PublicKeyBody(publicKey: getUserHexEncodedPublicKey())
let requestBody: LegacyPublicKeyBody = LegacyPublicKeyBody(publicKey: getUserHexEncodedPublicKey())
guard let body: Data = try? JSONEncoder().encode(requestBody) else {
return Promise(error: HTTP.Error.invalidJSON)
@ -1325,15 +1233,15 @@ public final class OpenGroupAPI: NSObject {
// TODO: Move these methods to the OpenGroupManager? (seems odd for them to be in the API)
@available(*, deprecated, message: "Use v4 endpoint instead")
private static func legacyProcess(messages: [OpenGroupMessageV2]?, for room: String, on server: String) -> Promise<[OpenGroupMessageV2]> {
guard let messages: [OpenGroupMessageV2] = messages, !messages.isEmpty else { return Promise.value([]) }
private static func legacyProcess(messages: [LegacyOpenGroupMessageV2]?, for room: String, on server: String) -> Promise<[LegacyOpenGroupMessageV2]> {
guard let messages: [LegacyOpenGroupMessageV2] = messages, !messages.isEmpty else { return Promise.value([]) }
let storage = SNMessagingKitConfiguration.shared.storage
let serverID: Int64 = (messages.compactMap { $0.serverID }.max() ?? 0)
let lastMessageServerID: Int64 = (storage.getLastMessageServerID(for: room, on: server) ?? 0)
if serverID > lastMessageServerID {
let (promise, seal) = Promise<[OpenGroupMessageV2]>.pending()
let (promise, seal) = Promise<[LegacyOpenGroupMessageV2]>.pending()
storage.write(
with: { transaction in
@ -1351,15 +1259,15 @@ public final class OpenGroupAPI: NSObject {
}
@available(*, deprecated, message: "Use v4 endpoint instead")
private static func legacyProcess(deletions: [Deletion]?, for room: String, on server: String) -> Promise<[Deletion]> {
guard let deletions: [Deletion] = deletions else { return Promise.value([]) }
private static func legacyProcess(deletions: [LegacyDeletion]?, for room: String, on server: String) -> Promise<[LegacyDeletion]> {
guard let deletions: [LegacyDeletion] = deletions else { return Promise.value([]) }
let storage = SNMessagingKitConfiguration.shared.storage
let serverID: Int64 = (deletions.compactMap { $0.id }.max() ?? 0)
let lastDeletionServerID: Int64 = (storage.getLastDeletionServerID(for: room, on: server) ?? 0)
if serverID > lastDeletionServerID {
let (promise, seal) = Promise<[Deletion]>.pending()
let (promise, seal) = Promise<[LegacyDeletion]>.pending()
storage.write(
with: { transaction in

View File

@ -6,8 +6,15 @@ public final class OpenGroupManager: NSObject {
private var pollers: [String: OpenGroupAPI.Poller] = [:] // One for each server
private var isPolling = false
// MARK: - Cache
public static var defaultRoomsPromise: Promise<[OpenGroupAPI.Room]>?
private static var groupImagePromises: [String: Promise<Data>] = [:]
private static var moderators: [String: [String: Set<String>]] = [:] // Server URL to room ID to set of moderator IDs
// MARK: - Polling
@objc public func startPolling() {
guard !isPolling else { return }
@ -30,13 +37,13 @@ public final class OpenGroupManager: NSObject {
// MARK: - Adding & Removing
public func add(room: String, server: String, publicKey: String, using transaction: Any) -> Promise<Void> {
public func add(roomToken: String, server: String, publicKey: String, using transaction: Any) -> Promise<Void> {
let storage = Storage.shared
// Clear any existing data if needed
storage.removeLastMessageServerID(for: room, on: server, using: transaction)
storage.removeLastDeletionServerID(for: room, on: server, using: transaction)
storage.removeAuthToken(for: room, on: server, using: transaction)
storage.removeLastMessageServerID(for: roomToken, on: server, using: transaction)
storage.removeLastDeletionServerID(for: roomToken, on: server, using: transaction)
storage.removeAuthToken(for: roomToken, on: server, using: transaction)
// Store the public key
storage.setOpenGroupPublicKey(for: server, to: publicKey, using: transaction)
@ -45,111 +52,17 @@ public final class OpenGroupManager: NSObject {
let transaction = transaction as! YapDatabaseReadWriteTransaction
transaction.addCompletionQueue(DispatchQueue.global(qos: .userInitiated)) {
// Get the group info
// TODO: Remove this legacy method
// OpenGroupAPI.legacyGetRoomInfo(for: room, on: server).done(on: DispatchQueue.global(qos: .userInitiated)) { info in
// // Create the open group model and the thread
// let openGroup = OpenGroupV2(server: server, room: room, name: info.name, publicKey: publicKey, imageID: info.imageID)
// let groupID = LKGroupUtilities.getEncodedOpenGroupIDAsData(openGroup.id)
// let model = TSGroupModel(title: openGroup.name, memberIds: [ getUserHexEncodedPublicKey() ], image: nil, groupId: groupID, groupType: .openGroup, adminIds: [])
// // Store everything
// storage.write(with: { transaction in
// let transaction = transaction as! YapDatabaseReadWriteTransaction
// let thread = TSGroupThread.getOrCreateThread(with: model, transaction: transaction)
// thread.shouldBeVisible = true
// thread.save(with: transaction)
// storage.setV2OpenGroup(openGroup, for: thread.uniqueId!, using: transaction)
// }, completion: {
// // Start the poller if needed
// if OpenGroupManager.shared.pollers[server] == nil {
// let poller = OpenGroupPollerV2(for: server)
// poller.startIfNeeded()
// OpenGroupManager.shared.pollers[server] = poller
// }
// // Fetch the group image
// OpenGroupAPI.legacyGetGroupImage(for: room, on: server).done(on: DispatchQueue.global(qos: .userInitiated)) { data in
// storage.write { transaction in
// // Update the thread
// let transaction = transaction as! YapDatabaseReadWriteTransaction
// let thread = TSGroupThread.getOrCreateThread(with: model, transaction: transaction)
// thread.groupModel.groupImage = UIImage(data: data)
// thread.save(with: transaction)
// }
// }.retainUntilComplete()
// // Finish
// seal.fulfill(())
// })
// }.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in
// seal.reject(error)
// }
OpenGroupAPI.room(for: room, on: server)
OpenGroupAPI.room(for: roomToken, on: server)
.done(on: DispatchQueue.global(qos: .userInitiated)) { _, room in
// Create the open group model and the thread
let openGroup: OpenGroupV2 = OpenGroupV2(
server: server,
room: room.token,
name: room.name,
OpenGroupManager.handleRoom(
room,
publicKey: publicKey,
imageID: room.imageId.map { "\($0)" } // TODO: Update this?
)
let groupID: Data = LKGroupUtilities.getEncodedOpenGroupIDAsData(openGroup.id)
let model: TSGroupModel = TSGroupModel(
title: openGroup.name,
memberIds: [ getUserHexEncodedPublicKey() ],
image: nil,
groupId: groupID,
groupType: .openGroup,
adminIds: [] // TODO: This is part of the 'room' object
)
// Store everything
storage.write(
with: { transaction in
let transaction = transaction as! YapDatabaseReadWriteTransaction
let thread = TSGroupThread.getOrCreateThread(with: model, transaction: transaction)
thread.shouldBeVisible = true
thread.save(with: transaction)
storage.setV2OpenGroup(openGroup, for: thread.uniqueId!, using: transaction)
},
completion: {
// Start the poller if needed
if OpenGroupManager.shared.pollers[server] == nil {
let poller = OpenGroupAPI.Poller(for: server)
poller.startIfNeeded()
OpenGroupManager.shared.pollers[server] = poller
}
// Fetch the group image (if there is one)
// TODO: Need to test this.
// TODO: Clean this up (can we avoid the if/else with fancy promise wrangling?).
if let imageId: Int64 = room.imageId {
OpenGroupAPI.roomImage(imageId, for: room.token, on: server)
.done(on: DispatchQueue.global(qos: .userInitiated)) { data in
storage.write { transaction in
// Update the thread
let transaction = transaction as! YapDatabaseReadWriteTransaction
let thread = TSGroupThread.getOrCreateThread(with: model, transaction: transaction)
thread.groupModel.groupImage = UIImage(data: data)
thread.save(with: transaction)
}
}
.retainUntilComplete()
}
else {
storage.write { transaction in
// Update the thread
let transaction = transaction as! YapDatabaseReadWriteTransaction
let thread = TSGroupThread.getOrCreateThread(with: model, transaction: transaction)
thread.save(with: transaction)
}
}
// Finish
seal.fulfill(())
}
)
for: roomToken,
on: server,
isBackgroundPoll: false
) {
seal.fulfill(())
}
}
.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in
seal.reject(error)
@ -192,7 +105,279 @@ public final class OpenGroupManager: NSObject {
}
}
// MARK: Convenience
// MARK: - Response Processing
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.seqNo < rhs.seqNo }
let seqNo: Int64 = (sortedMessages.last?.seqNo ?? 0)
let lastMessageSeqNo: Int64 = (dependencies.storage.getLastMessageServerID(for: roomToken, on: server) ?? 0)
dependencies.storage.write { transaction in
var messageServerIDsToRemove: [UInt64] = []
// Update the 'lastMessageServerId' value if we've gotten a newer message
if seqNo > lastMessageSeqNo {
dependencies.storage.setLastMessageServerID(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.seqNo))
return
}
let envelope = SNProtoEnvelope.builder(type: .sessionMessage, timestamp: UInt64(floor(message.posted)))
envelope.setContent(data)
envelope.setSource(sender)
do {
let data = try envelope.buildSerializedData()
let (message, proto) = try MessageReceiver.parse(data, openGroupMessageServerID: UInt64(message.seqNo), 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.v2GetThreadID(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 handleRoom(
_ room: OpenGroupAPI.Room,
publicKey: String,
for roomToken: String,
on server: String,
isBackgroundPoll: Bool,
using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies(),
completion: (() -> ())? = nil
) {
OpenGroupManager.handlePollInfo(
OpenGroupAPI.RoomPollInfo(room: room),
publicKey: publicKey,
for: roomToken,
on: server,
isBackgroundPoll: isBackgroundPoll,
using: dependencies,
completion: completion
)
}
internal static func handlePollInfo(
_ pollInfo: OpenGroupAPI.RoomPollInfo,
publicKey maybePublicKey: String?,
for roomToken: String,
on server: String,
isBackgroundPoll: Bool,
using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies(),
completion: (() -> ())? = nil
) {
// Create the open group model and get or create the thread
let groupId: Data = LKGroupUtilities.getEncodedOpenGroupIDAsData("\(server).\(roomToken)")
let userPublicKey: String = getUserHexEncodedPublicKey()
let initialModel: TSGroupModel = TSGroupModel(
title: pollInfo.name,
memberIds: [ userPublicKey ],
image: nil,
groupId: groupId,
groupType: .openGroup,
adminIds: (pollInfo.admins ?? [])
)
// Store/Update everything
dependencies.storage.write(
with: { transaction in
let transaction = transaction as! YapDatabaseReadWriteTransaction
let thread = TSGroupThread.getOrCreateThread(with: initialModel, transaction: transaction)
let existingOpenGroup: OpenGroupV2? = thread.uniqueId.flatMap { uniqueId -> OpenGroupV2? in
dependencies.storage.getV2OpenGroup(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.name ?? thread.groupModel.groupName),
memberIds: Array(Set(thread.groupModel.groupMemberIds).inserting(userPublicKey)),
image: thread.groupModel.groupImage,
groupId: groupId,
groupType: .openGroup,
adminIds: (pollInfo.admins ?? thread.groupModel.groupAdminIds)
)
let updatedOpenGroup: OpenGroupV2 = OpenGroupV2(
server: server,
room: (pollInfo.token ?? roomToken),
publicKey: publicKey,
name: (pollInfo.name ?? thread.name()),
groupDescription: (pollInfo.description ?? existingOpenGroup?.description),
imageID: (pollInfo.imageId.map { "\($0)" } ?? existingOpenGroup?.imageID),
infoUpdates: ((pollInfo.infoUpdates ?? existingOpenGroup?.infoUpdates) ?? 0)
)
let existingUserCount: UInt64? = dependencies.storage.getUserCount(forV2OpenGroupWithID: updatedOpenGroup.id)
// - Thread changes
thread.shouldBeVisible = true
thread.groupModel = updatedModel
thread.save(with: transaction)
// - Open Group changes
dependencies.storage.setV2OpenGroup(updatedOpenGroup, for: threadUniqueId, using: transaction)
// - User Count
dependencies.storage.setUserCount(
to: ((pollInfo.activeUsers.map { UInt64($0) } ?? existingUserCount) ?? 0),
forV2OpenGroupWithID: 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.moderators {
OpenGroupManager.moderators[server] = (OpenGroupManager.moderators[server] ?? [:])
.setting(roomToken, Set(moderators))
}
// - Room image (if there is one)
if let imageId: Int64 = pollInfo.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?()
}
)
}
// MARK: - Convenience
@objc(isUserModerator:forRoom:onServer:)
public static func isUserModerator(_ publicKey: String, for room: String, on server: String) -> Bool {
return (OpenGroupManager.moderators[server]?[room]?.contains(publicKey) ?? false)
}
public static func getDefaultRoomsIfNeeded(using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()) {
// Note: If we already have a 'defaultRoomsPromise' then there is no need to get it again
guard OpenGroupManager.defaultRoomsPromise == nil else { return }
dependencies.storage.write(
with: { transaction in
dependencies.storage.setOpenGroupPublicKey(
for: OpenGroupAPI.defaultServer,
to: OpenGroupAPI.defaultServerPublicKey,
using: transaction
)
},
completion: {
OpenGroupManager.defaultRoomsPromise = attempt(maxRetryCount: 8, recoveringOn: DispatchQueue.main) {
OpenGroupAPI.rooms(for: OpenGroupAPI.defaultServer, using: dependencies)
.map { _, data in data }
}
OpenGroupManager.defaultRoomsPromise?
.done(on: OpenGroupAPI.workQueue) { items in
items
.compactMap { room -> (Int64, String)? in
guard let imageId: Int64 = room.imageId else { return nil}
return (imageId, room.token)
}
.forEach { imageId, roomToken in
roomImage(imageId, for: roomToken, on: OpenGroupAPI.defaultServer, using: dependencies)
.retainUntilComplete()
}
}
.catch(on: OpenGroupAPI.workQueue) { _ in
OpenGroupManager.defaultRoomsPromise = nil
}
}
)
}
public static func roomImage(
_ fileId: Int64,
for roomToken: String,
on server: String,
using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()
) -> Promise<Data> {
// Normally the image for a given group is stored with the group thread, so it's only
// fetched once. However, on the join open group screen we show images for groups the
// user * hasn't * joined yet. We don't want to re-fetch these images every time the
// user opens the app because that could slow the app down or be data-intensive. So
// instead we assume that these images don't change that often and just fetch them once
// a week. We also assume that they're all fetched at the same time as well, so that
// we only need to maintain one date in user defaults. On top of all of this we also
// don't double up on fetch requests by storing the existing request as a promise if
// there is one.
let lastOpenGroupImageUpdate: Date? = UserDefaults.standard[.lastOpenGroupImageUpdate]
let now: Date = dependencies.date
let timeSinceLastUpdate: TimeInterval = (lastOpenGroupImageUpdate.map { now.timeIntervalSince($0) } ?? .greatestFiniteMagnitude)
let updateInterval: TimeInterval = (7 * 24 * 60 * 60)
if let data = dependencies.storage.getOpenGroupImage(for: roomToken, on: server), server == OpenGroupAPI.defaultServer, timeSinceLastUpdate < updateInterval {
return Promise.value(data)
}
if let promise = OpenGroupManager.groupImagePromises["\(server).\(roomToken)"] {
return promise
}
let promise: Promise<Data> = OpenGroupAPI
.downloadFile(fileId, from: roomToken, on: server, using: dependencies)
.map { _, data in data }
_ = promise.done(on: OpenGroupAPI.workQueue) { imageData in
if server == OpenGroupAPI.defaultServer {
dependencies.storage.write { transaction in
dependencies.storage.setOpenGroupImage(to: imageData, for: roomToken, on: server, using: transaction)
}
UserDefaults.standard[.lastOpenGroupImageUpdate] = now
}
}
OpenGroupManager.groupImagePromises["\(server).\(roomToken)"] = promise
return promise
}
public static func parseV2OpenGroup(from string: String) -> (room: String, server: String, publicKey: String)? {
guard let url = URL(string: string), let host = url.host ?? given(string.split(separator: "/").first, { String($0) }), let query = url.query else { return nil }
// Inputs that should work:

View File

@ -20,7 +20,7 @@ extension OpenGroupAPI {
// Messages
case roomMessage(String)
case roomMessageIndividual(String, String)
case roomMessageIndividual(String, id: Int64)
case roomMessagesRecent(String)
case roomMessagesBefore(String, id: Int64)
case roomMessagesSince(String, seqNo: Int64)

View File

@ -234,7 +234,7 @@ extension MessageReceiver {
// Open groups
for openGroupURL in message.openGroups {
if let (room, server, publicKey) = OpenGroupManager.parseV2OpenGroup(from: openGroupURL) {
OpenGroupManager.shared.add(room: room, server: server, publicKey: publicKey, using: transaction).retainUntilComplete()
OpenGroupManager.shared.add(roomToken: room, server: server, publicKey: publicKey, using: transaction).retainUntilComplete()
}
}
}

View File

@ -80,122 +80,40 @@ extension OpenGroupAPI {
}
private func handlePollResponse(_ response: [OpenGroupAPI.Endpoint: (info: OnionRequestResponseInfoType, data: Codable)], isBackgroundPoll: Bool) {
let storage = SNMessagingKitConfiguration.shared.storage
response.forEach { endpoint, response in
switch endpoint {
case .roomMessagesRecent(let roomToken), .roomMessagesBefore(let roomToken, _), .roomMessagesSince(let roomToken, _):
guard let responseData: [OpenGroupAPI.Message] = response.data as? [OpenGroupAPI.Message] else {
//SNLog("Open group polling failed due to error: \(error).")
return // TODO: Throw error?
SNLog("Open group polling failed due to invalid data.")
return
}
handleMessages(responseData, roomToken: roomToken, isBackgroundPoll: isBackgroundPoll, using: storage)
OpenGroupManager.handleMessages(
responseData,
for: roomToken,
on: server,
isBackgroundPoll: isBackgroundPoll
)
case .roomPollInfo(let roomToken, _):
guard let responseData: OpenGroupAPI.RoomPollInfo = response.data as? OpenGroupAPI.RoomPollInfo else {
//SNLog("Open group polling failed due to error: \(error).")
return // TODO: Throw error?
SNLog("Open group polling failed due to invalid data.")
return
}
handlePollInfo(responseData, roomToken: roomToken, isBackgroundPoll: isBackgroundPoll, using: storage)
OpenGroupManager.handlePollInfo(
responseData,
publicKey: nil,
for: roomToken,
on: server,
isBackgroundPoll: isBackgroundPoll
)
default: break // No custom handling needed
}
}
}
// MARK: - Custom response handling
// TODO: Shift this logic to the OpenGroupManager? (seems like the place it should belong?)
private func handleMessages(_ messages: [OpenGroupAPI.Message], roomToken: String, isBackgroundPoll: Bool, using storage: SessionMessagingKitStorageProtocol) {
// 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.seqNo < rhs.seqNo }
storage.write { transaction in
var messageServerIDsToRemove: [UInt64] = []
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.seqNo))
return
}
let envelope = SNProtoEnvelope.builder(type: .sessionMessage, timestamp: UInt64(floor(message.posted)))
envelope.setContent(data)
envelope.setSource(sender)
do {
let data = try envelope.buildSerializedData()
let (message, proto) = try MessageReceiver.parse(data, openGroupMessageServerID: UInt64(message.seqNo), 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 = storage.v2GetThreadID(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) }
}
}
private func handlePollInfo(_ pollInfo: OpenGroupAPI.RoomPollInfo, roomToken: String, isBackgroundPoll: Bool, using storage: SessionMessagingKitStorageProtocol) {
// TODO: Handle other properties???.
// public let token: String?
// public let created: TimeInterval?
// public let name: String?
// public let description: String?
// public let imageId: Int64?
//
// public let infoUpdates: Int64?
// public let messageSequence: Int64?
// public let activeUsers: Int64?
// public let activeUsersCutoff: Int64?
// public let pinnedMessages: [PinnedMessage]?
//
// public let admin: Bool?
// public let globalAdmin: Bool?
// public let admins: [String]?
// public let hiddenAdmins: [String]?
//
// public let moderator: Bool?
// public let globalModerator: Bool?
// public let moderators: [String]?
// public let hiddenModerators: [String]?
// - Moderators
OpenGroupAPI.moderators[server] = (OpenGroupAPI.moderators[server] ?? [:])
.setting(roomToken, Set(pollInfo.moderators ?? []))
// public let read: Bool?
// public let defaultRead: Bool?
// public let write: Bool?
// public let defaultWrite: Bool?
// public let upload: Bool?
// public let defaultUpload: Bool?
//
// /// Only populated and different if the `info_updates` counter differs from the provided `info_updated` value
// public let details: Room?
}
// MARK: - Legacy Handling
private func handleCompactPollBody(_ body: OpenGroupAPI.LegacyCompactPollResponse.Result, isBackgroundPoll: Bool) {

View File

@ -67,10 +67,6 @@ public protocol SessionMessagingKitStorageProtocol: SessionMessagingKitOpenGroup
func setLastDeletionServerID(for room: String, on server: String, to newValue: Int64, using transaction: Any)
func removeLastDeletionServerID(for room: String, on server: String, using transaction: Any)
// MARK: - Open Group Metadata
func setUserCount(to newValue: UInt64, forV2OpenGroupWithID openGroupID: String, using transaction: Any)
// MARK: - Message Handling
func getReceivedMessageTimestamps(using transaction: Any) -> [UInt64]

View File

@ -81,9 +81,19 @@ class OpenGroupAPITests: XCTestCase {
)
testStorage.mockData[.allV2OpenGroups] = [
"0": OpenGroupV2(server: "testServer", room: "testRoom", name: "Test", publicKey: "7aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d", imageID: nil)
"0": OpenGroupV2(
server: "testServer",
room: "testRoom",
publicKey: "7aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d",
name: "Test",
groupDescription: nil,
imageID: nil,
infoUpdates: 0
)
]
testStorage.mockData[.openGroupPublicKeys] = [
"testServer": "7aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d"
]
testStorage.mockData[.openGroupPublicKeys] = ["testServer": "7aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d"]
// Test Private key, not actually used (from here https://www.notion.so/oxen/SOGS-Authentication-dc64cc846cb24b2abbf7dd4bfd74abbb)
testStorage.mockData[.userKeyPair] = try! ECKeyPair(

View File

@ -13,7 +13,9 @@ class TestStorage: SessionMessagingKitStorageProtocol, Mockable {
case allV2OpenGroups
case openGroupPublicKeys
case userKeyPair
case openGroup
case openGroupImage
case openGroupUserCount
}
typealias Key = DataKey
@ -71,7 +73,7 @@ class TestStorage: SessionMessagingKitStorageProtocol, Mockable {
// MARK: - Open Groups
func getAllV2OpenGroups() -> [String: OpenGroupV2] { return (mockData[.allV2OpenGroups] as! [String: OpenGroupV2]) }
func getV2OpenGroup(for threadID: String) -> OpenGroupV2? { return nil }
func getV2OpenGroup(for threadID: String) -> OpenGroupV2? { return (mockData[.openGroup] as? OpenGroupV2) }
func v2GetThreadID(for v2OpenGroupID: String) -> String? { return nil }
func updateMessageIDCollectionByPruningMessagesWithIDs(_ messageIDs: Set<String>, using transaction: Any) {}
@ -99,10 +101,6 @@ class TestStorage: SessionMessagingKitStorageProtocol, Mockable {
func setLastDeletionServerID(for room: String, on server: String, to newValue: Int64, using transaction: Any) {}
func removeLastDeletionServerID(for room: String, on server: String, using transaction: Any) {}
// MARK: - Open Group Metadata
func setUserCount(to newValue: UInt64, forV2OpenGroupWithID openGroupID: String, using transaction: Any) {}
// MARK: - Message Handling
func getReceivedMessageTimestamps(using transaction: Any) -> [UInt64] { return [] }
@ -118,5 +116,19 @@ class TestStorage: SessionMessagingKitStorageProtocol, Mockable {
extension TestStorage: SessionMessagingKitOpenGroupStorageProtocol {
func getOpenGroupImage(for room: String, on server: String) -> Data? { return (mockData[.openGroupImage] as? Data) }
func setOpenGroupImage(to data: Data, for room: String, on server: String, using transaction: Any) {}
func setOpenGroupImage(to data: Data, for room: String, on server: String, using transaction: Any) {
mockData[.openGroupImage] = data
}
func setV2OpenGroup(_ openGroup: OpenGroupV2, for threadID: String, using transaction: Any) {
mockData[.openGroup] = openGroup
}
func getUserCount(forV2OpenGroupWithID openGroupID: String) -> UInt64? {
return (mockData[.openGroupUserCount] as? UInt64)
}
func setUserCount(to newValue: UInt64, forV2OpenGroupWithID openGroupID: String, using transaction: Any) {
mockData[.openGroupUserCount] = newValue
}
}

View File

@ -0,0 +1,12 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
public extension Set {
func inserting(_ other: Element) -> Set<Element> {
var updatedSet: Set<Element> = self
updatedSet.insert(other)
return updatedSet
}
}