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

View File

@ -26,7 +26,9 @@
buildConfiguration = "Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "NO"> shouldUseLaunchSchemeArgsEnv = "NO"
codeCoverageEnabled = "YES"
onlyGenerateCoverageForSpecifiedTargets = "YES">
<MacroExpansion> <MacroExpansion>
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
@ -43,6 +45,64 @@
isEnabled = "YES"> isEnabled = "YES">
</EnvironmentVariable> </EnvironmentVariable>
</EnvironmentVariables> </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> <Testables>
<TestableReference <TestableReference
skipped = "NO"> 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 it's an incoming message the user must have moderator status
if (self.interaction.interactionType == OWSInteractionType_IncomingMessage) { if (self.interaction.interactionType == OWSInteractionType_IncomingMessage) {
NSString *userPublicKey = [LKStorage.shared getUserPublicKey]; 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 // Delete the message
@ -1060,7 +1060,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
if (self.interaction.interactionType == OWSInteractionType_IncomingMessage) { if (self.interaction.interactionType == OWSInteractionType_IncomingMessage) {
NSString *userPublicKey = [LKStorage.shared getUserPublicKey]; NSString *userPublicKey = [LKStorage.shared getUserPublicKey];
if (openGroupV2 != nil) { 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) { if (interationType == OWSInteractionType_IncomingMessage) {
// Only allow deletion on incoming messages if the user has moderation permission // Only allow deletion on incoming messages if the user has moderation permission
if (openGroupV2 != nil) { 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 { } else {
return YES; return YES;
@ -1155,7 +1155,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
// Check that we're a moderator // Check that we're a moderator
if (openGroupV2 != nil) { 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.publicKey = mentionCandidate.publicKey
profilePictureView.update() profilePictureView.update()
if let server = openGroupServer, let room = openGroupRoom { 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 moderatorIconImageView.isHidden = !isUserModerator
} else { } else {
moderatorIconImageView.isHidden = true moderatorIconImageView.isHidden = true

View File

@ -219,7 +219,7 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate {
} }
if let senderSessionID = senderSessionID, message.isOpenGroupMessage { if let senderSessionID = senderSessionID, message.isOpenGroupMessage {
if let openGroupV2 = Storage.shared.getV2OpenGroup(for: message.uniqueThreadId) { 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 moderatorIconImageView.isHidden = !isUserModerator || profilePictureView.isHidden
} else { } else {
moderatorIconImageView.isHidden = true moderatorIconImageView.isHidden = true

View File

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

View File

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

View File

@ -2,6 +2,12 @@
public protocol SessionMessagingKitOpenGroupStorageProtocol { public protocol SessionMessagingKitOpenGroupStorageProtocol {
func getOpenGroupImage(for room: String, on server: String) -> Data? func getOpenGroupImage(for room: String, on server: String) -> 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)
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 { extension Storage: SessionMessagingKitOpenGroupStorageProtocol {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -71,3 +71,37 @@ extension OpenGroupAPI {
public let details: Room? 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.encode(whisperMods, forKey: .whisperMods)
try container.encodeIfPresent(fileIds, forKey: .fileIds) 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. // TODO: Upgrade this to use the non-legacy version.
return AnyPromise.from(legacyDeleteMessage(with: serverID, from: room, on: server)) 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) @objc(legacyGetDefaultRoomsIfNeeded)
public static func 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 defaultServer = "http://116.203.70.33"
public static let defaultServerPublicKey = "a03c383cf63c3c4efe67acc52112a6dd734b3a946b9545f488aaa93da7991238" 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 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]>? // MARK: - Polling State
public static var groupImagePromises: [String: Promise<Data>] = [:]
private static var hasPerformedInitialPoll: [String: Bool] = [:]
private static var timeSinceLastPoll: [String: TimeInterval] = [:]
private static var lastPollTime: TimeInterval = .greatestFiniteMagnitude
private static let timeSinceLastOpen: TimeInterval = { private static let timeSinceLastOpen: TimeInterval = {
guard let lastOpen = UserDefaults.standard[.lastOpen] else { return .greatestFiniteMagnitude } guard let lastOpen = UserDefaults.standard[.lastOpen] else { return .greatestFiniteMagnitude }
return Date().timeIntervalSince(lastOpen) 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 // 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)]> { public static func poll(_ server: String, using dependencies: Dependencies = Dependencies()) -> Promise<[Endpoint: (OnionRequestResponseInfoType, Codable)]> {
// TODO: Remove comments // Store a local copy of the cached state for this server
// Capabilities let hadPerformedInitialPoll: Bool = (hasPerformedInitialPoll[server] == true)
// Fetch each room let originalTimeSinceLastPoll: TimeInterval = (timeSinceLastPoll[server] ?? min(lastPollTime, timeSinceLastOpen))
// 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)
// old compact_poll data // Update the cached state for this server
// public let room: String hasPerformedInitialPoll[server] = true
// public let statusCode: UInt lastPollTime = min(lastPollTime, timeSinceLastOpen)
// public let messages: [OpenGroupMessageV2]? UserDefaults.standard[.lastOpen] = Date()
// public let deletions: [Deletion]?
// public let moderators: [String]?
// Generate the requests
let requestResponseType: [BatchRequestInfo] = [ let requestResponseType: [BatchRequestInfo] = [
BatchRequestInfo( BatchRequestInfo(
request: Request( request: Request(
@ -59,27 +63,37 @@ public final class OpenGroupAPI: NSObject {
] ]
.appending( .appending(
dependencies.storage.getAllV2OpenGroups().values 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 .flatMap { openGroup -> [BatchRequestInfo] in
let lastSeqNo: Int64? = dependencies.storage.getLastMessageServerID(for: openGroup.room, on: server) let lastSeqNo: Int64? = dependencies.storage.getLastMessageServerID(for: openGroup.room, on: server)
let targetSeqNo: Int64 = (lastSeqNo ?? 0) 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 [ return [
BatchRequestInfo( BatchRequestInfo(
request: Request( request: Request(
server: server, 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, openGroup.infoUpdates)
endpoint: .roomPollInfo(openGroup.room, 0)
), ),
responseType: RoomPollInfo.self responseType: RoomPollInfo.self
), ),
BatchRequestInfo( BatchRequestInfo(
request: Request( request: Request(
server: server, server: server,
endpoint: (lastSeqNo == nil ? endpoint: (shouldRetrieveRecentMessages ?
.roomMessagesRecent(openGroup.room) : .roomMessagesRecent(openGroup.room) :
.roomMessagesSince(openGroup.room, seqNo: targetSeqNo) .roomMessagesSince(openGroup.room, seqNo: targetSeqNo)
) )
// TODO: Limit?
// queryParameters: [ .limit: 256 ]
), ),
responseType: [Message].self 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) return batch(server, requests: requestResponseType, using: dependencies)
} }
@ -121,64 +134,35 @@ public final class OpenGroupAPI: NSObject {
} }
} }
// TODO: `/sequence` request. /// 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.
///
public static func compactPoll(_ server: String, using dependencies: Dependencies = Dependencies()) -> Promise<LegacyCompactPollResponse> { /// 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
let rooms: [String] = dependencies.storage.getAllV2OpenGroups().values /// 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
.filter { $0.server == server } /// carried out because of an earlier failure will have a response code of `412` (Precondition Failed)."
.map { $0.room } private static func sequence(_ server: String, requests: [BatchRequestInfo], using dependencies: Dependencies = Dependencies()) -> Promise<[Endpoint: (OnionRequestResponseInfoType, Codable)]> {
let useMessageLimit = (hasPerformedInitialPoll[server] != true && timeSinceLastOpen > OpenGroupAPI.Poller.maxInactivityPeriod) let requestBody: BatchRequest = requests.map { BatchSubRequest(request: $0.request) }
let responseTypes = requests.map { $0.responseType }
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
)
}
)
guard let body: Data = try? JSONEncoder().encode(requestBody) else { guard let body: Data = try? JSONEncoder().encode(requestBody) else {
return Promise(error: HTTP.Error.invalidJSON) return Promise(error: HTTP.Error.invalidJSON)
} }
let request = Request( let request: Request = Request(
method: .post, method: .post,
server: server, server: server,
endpoint: .legacyCompactPoll(legacyAuth: false), endpoint: .sequence,
body: body body: body
) )
// TODO: Handle a `412` response (ie. a required capability isn't supported)
return send(request, using: dependencies) return send(request, using: dependencies)
.then(on: OpenGroupAPI.workQueue) { _, maybeData -> Promise<LegacyCompactPollResponse> in .decoded(as: responseTypes, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
guard let data: Data = maybeData else { throw Error.parsingFailed } .map { result in
result.enumerated()
let response: LegacyCompactPollResponse = try data.decoded(as: LegacyCompactPollResponse.self, customError: Error.parsingFailed) .reduce(into: [:]) { prev, next in
prev[requests[next.offset].request.endpoint] = next.element
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) }
}
} }
// MARK: - Capabilities // MARK: - Capabilities
@ -239,13 +223,13 @@ public final class OpenGroupAPI: NSObject {
using dependencies: Dependencies = Dependencies() using dependencies: Dependencies = Dependencies()
) -> Promise<(OnionRequestResponseInfoType, Message)> { ) -> Promise<(OnionRequestResponseInfoType, Message)> {
// TODO: Change this to use '.blinded' once it's working. // 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) return Promise(error: Error.signingFailed)
} }
let requestBody: SendMessageRequest = SendMessageRequest( let requestBody: SendMessageRequest = SendMessageRequest(
data: signedRequest.data, data: signedMessage.data,
signature: signedRequest.signature, signature: signedMessage.signature,
whisperTo: whisperTo, whisperTo: whisperTo,
whisperMods: whisperMods, whisperMods: whisperMods,
fileIds: nil // TODO: Add support for 'fileIds'. fileIds: nil // TODO: Add support for 'fileIds'.
@ -265,62 +249,97 @@ public final class OpenGroupAPI: NSObject {
return send(request, using: dependencies) return send(request, using: dependencies)
.decoded(as: Message.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed) .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])> { 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( let request: Request = Request(
server: server, server: server,
endpoint: .roomMessagesRecent(roomToken) endpoint: .roomMessagesRecent(roomToken)
// TODO: Limit?. // TODO: Limit?.
// queryParameters: [ // queryParameters: [ .limit: 50 ]
// .fromServerId: storage.getLastMessageServerID(for: room, on: server).map { String($0) }
// ].compactMapValues { $0 }
) )
return send(request, using: dependencies) return send(request, using: dependencies)
.decoded(as: [Message].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed) .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])> { 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( let request: Request = Request(
server: server, server: server,
endpoint: .roomMessagesBefore(roomToken, id: messageId) endpoint: .roomMessagesBefore(roomToken, id: messageId)
// TODO: Limit?. // TODO: Limit?.
// queryParameters: [ // queryParameters: [ .limit: 50 ]
// .fromServerId: storage.getLastMessageServerID(for: room, on: server).map { String($0) }
// ].compactMapValues { $0 }
) )
return send(request, using: dependencies) return send(request, using: dependencies)
.decoded(as: [Message].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed) .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])> { 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( let request: Request = Request(
server: server, server: server,
endpoint: .roomMessagesSince(roomToken, seqNo: seqNo) endpoint: .roomMessagesSince(roomToken, seqNo: seqNo)
// TODO: Limit?. // TODO: Limit?.
// queryParameters: [ // queryParameters: [ .limit: 50 ]
// .fromServerId: storage.getLastMessageServerID(for: room, on: server).map { String($0) }
// ].compactMapValues { $0 }
) )
return send(request, using: dependencies) return send(request, using: dependencies)
.decoded(as: [Message].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed) .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 // MARK: - Pinning
@ -360,45 +379,6 @@ public final class OpenGroupAPI: NSObject {
// MARK: - Files // 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)> { 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( let request: Request = Request(
method: .post, 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])> { 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 // 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) return Promise(error: Error.signingFailed)
} }
let requestBody: SendDirectMessageRequest = SendDirectMessageRequest( let requestBody: SendDirectMessageRequest = SendDirectMessageRequest(
data: signedRequest.data, data: signedMessage.data,
signature: signedRequest.signature signature: signedMessage.signature
) )
guard let body: Data = try? JSONEncoder().encode(requestBody) else { 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) .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 // 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? { private static func sign(_ request: URLRequest, with publicKey: String, using dependencies: Dependencies = Dependencies()) -> URLRequest? {
guard let url: URL = request.url else { return nil } guard let url: URL = request.url else { return nil }
@ -812,7 +720,7 @@ public final class OpenGroupAPI: NSObject {
return Promise.value(authToken) return Promise.value(authToken)
} }
if let authTokenPromise: Promise<String> = authTokenPromises.wrappedValue["\(server).\(room)"] { if let authTokenPromise: Promise<String> = legacyAuthTokenPromises.wrappedValue["\(server).\(room)"] {
return authTokenPromise return authTokenPromise
} }
@ -830,13 +738,13 @@ public final class OpenGroupAPI: NSObject {
promise promise
.done(on: OpenGroupAPI.workQueue) { _ in .done(on: OpenGroupAPI.workQueue) { _ in
authTokenPromises.wrappedValue["\(server).\(room)"] = nil legacyAuthTokenPromises.wrappedValue["\(server).\(room)"] = nil
} }
.catch(on: OpenGroupAPI.workQueue) { _ in .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 return promise
} }
@ -859,7 +767,7 @@ public final class OpenGroupAPI: NSObject {
return legacySend(request).map(on: OpenGroupAPI.workQueue) { _, maybeData in return legacySend(request).map(on: OpenGroupAPI.workQueue) { _, maybeData in
guard let data: Data = maybeData else { throw Error.parsingFailed } 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) let symmetricKey = try AESGCM.generateSymmetricKey(x25519PublicKey: response.challenge.ephemeralPublicKey, x25519PrivateKey: userKeyPair.privateKey)
guard let tokenAsData = try? AESGCM.decrypt(response.challenge.ciphertext, with: symmetricKey) else { 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") @available(*, deprecated, message: "Use request signing instead")
public static func legacyClaimAuthToken(_ authToken: String, for room: String, on server: String) -> Promise<String> { 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 { guard let body: Data = try? JSONEncoder().encode(requestBody) else {
return Promise(error: HTTP.Error.invalidJSON) return Promise(error: HTTP.Error.invalidJSON)
@ -926,9 +834,9 @@ public final class OpenGroupAPI: NSObject {
hasPerformedInitialPoll[server] = true hasPerformedInitialPoll[server] = true
if !hasUpdatedLastOpenDate { if !legacyHasUpdatedLastOpenDate {
UserDefaults.standard[.lastOpen] = Date() UserDefaults.standard[.lastOpen] = Date()
hasUpdatedLastOpenDate = true legacyHasUpdatedLastOpenDate = true
} }
for room in rooms { for room in rooms {
@ -985,7 +893,7 @@ public final class OpenGroupAPI: NSObject {
return when( return when(
fulfilled: response.results 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 // 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 // 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 // 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) 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) 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() } items.forEach { legacyGetGroupImage(for: $0.id, on: defaultServer).retainUntilComplete() }
} }
promise.catch(on: OpenGroupAPI.workQueue) { _ in promise.catch(on: OpenGroupAPI.workQueue) { _ in
OpenGroupAPI.defaultRoomsPromise = nil OpenGroupAPI.legacyDefaultRoomsPromise = nil
} }
legacyDefaultRoomsPromise = promise legacyDefaultRoomsPromise = promise
} }
@ -1085,7 +993,7 @@ public final class OpenGroupAPI: NSObject {
return Promise.value(data) return Promise.value(data)
} }
if let promise = groupImagePromises["\(server).\(room)"] { if let promise = legacyGroupImagePromises["\(server).\(room)"] {
return promise return promise
} }
@ -1109,7 +1017,7 @@ public final class OpenGroupAPI: NSObject {
return response.data return response.data
} }
groupImagePromises["\(server).\(room)"] = promise legacyGroupImagePromises["\(server).\(room)"] = promise
return promise return promise
} }
@ -1125,7 +1033,7 @@ public final class OpenGroupAPI: NSObject {
return legacySend(request) return legacySend(request)
.map(on: OpenGroupAPI.workQueue) { _, maybeData in .map(on: OpenGroupAPI.workQueue) { _, maybeData in
guard let data: Data = maybeData else { throw Error.parsingFailed } 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 let storage = SNMessagingKitConfiguration.shared.storage
storage.write { transaction in storage.write { transaction in
@ -1171,7 +1079,7 @@ public final class OpenGroupAPI: NSObject {
// MARK: - Legacy Message Sending & Receiving // MARK: - Legacy Message Sending & Receiving
@available(*, deprecated, message: "Use send(_:to:on:whisperTo:whisperMods:with:) instead") @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 signedMessage = message.sign(with: publicKey) else { return Promise(error: Error.signingFailed) }
guard let body: Data = try? JSONEncoder().encode(signedMessage) else { guard let body: Data = try? JSONEncoder().encode(signedMessage) else {
return Promise(error: Error.parsingFailed) return Promise(error: Error.parsingFailed)
@ -1180,7 +1088,7 @@ public final class OpenGroupAPI: NSObject {
return legacySend(request).map(on: OpenGroupAPI.workQueue) { _, maybeData in return legacySend(request).map(on: OpenGroupAPI.workQueue) { _, maybeData in
guard let data: Data = maybeData else { throw Error.parsingFailed } 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.write { transaction in
Storage.shared.addReceivedMessageTimestamp(message.sentTimestamp, using: transaction) 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") @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 storage = SNMessagingKitConfiguration.shared.storage
let request: Request = Request( let request: Request = Request(
server: server, server: server,
@ -1200,9 +1108,9 @@ public final class OpenGroupAPI: NSObject {
].compactMapValues { $0 } ].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 } 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) return legacyProcess(messages: messages, for: room, on: server)
} }
@ -1224,7 +1132,7 @@ public final class OpenGroupAPI: NSObject {
} }
@available(*, deprecated, message: "Use v4 endpoint instead") @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 storage = SNMessagingKitConfiguration.shared.storage
let request: Request = Request( let request: Request = Request(
@ -1236,11 +1144,11 @@ public final class OpenGroupAPI: NSObject {
].compactMapValues { $0 } ].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 } 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) return legacySend(request)
.map(on: OpenGroupAPI.workQueue) { _, maybeData in .map(on: OpenGroupAPI.workQueue) { _, maybeData in
guard let data: Data = maybeData else { throw Error.parsingFailed } 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] { if var x = self.moderators[server] {
x[room] = Set(response.moderators) x[room] = Set(response.moderators)
@ -1273,7 +1181,7 @@ public final class OpenGroupAPI: NSObject {
@available(*, deprecated, message: "Use v4 endpoint instead") @available(*, deprecated, message: "Use v4 endpoint instead")
public static func legacyBan(_ publicKey: String, from room: String, on server: String) -> Promise<Void> { 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 { guard let body: Data = try? JSONEncoder().encode(requestBody) else {
return Promise(error: HTTP.Error.invalidJSON) return Promise(error: HTTP.Error.invalidJSON)
@ -1292,7 +1200,7 @@ public final class OpenGroupAPI: NSObject {
@available(*, deprecated, message: "Use v4 endpoint instead") @available(*, deprecated, message: "Use v4 endpoint instead")
public static func legacyBanAndDeleteAllMessages(_ publicKey: String, from room: String, on server: String) -> Promise<Void> { 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 { guard let body: Data = try? JSONEncoder().encode(requestBody) else {
return Promise(error: HTTP.Error.invalidJSON) 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) // TODO: Move these methods to the OpenGroupManager? (seems odd for them to be in the API)
@available(*, deprecated, message: "Use v4 endpoint instead") @available(*, deprecated, message: "Use v4 endpoint instead")
private static func legacyProcess(messages: [OpenGroupMessageV2]?, for room: String, on server: String) -> Promise<[OpenGroupMessageV2]> { private static func legacyProcess(messages: [LegacyOpenGroupMessageV2]?, for room: String, on server: String) -> Promise<[LegacyOpenGroupMessageV2]> {
guard let messages: [OpenGroupMessageV2] = messages, !messages.isEmpty else { return Promise.value([]) } guard let messages: [LegacyOpenGroupMessageV2] = messages, !messages.isEmpty else { return Promise.value([]) }
let storage = SNMessagingKitConfiguration.shared.storage let storage = SNMessagingKitConfiguration.shared.storage
let serverID: Int64 = (messages.compactMap { $0.serverID }.max() ?? 0) let serverID: Int64 = (messages.compactMap { $0.serverID }.max() ?? 0)
let lastMessageServerID: Int64 = (storage.getLastMessageServerID(for: room, on: server) ?? 0) let lastMessageServerID: Int64 = (storage.getLastMessageServerID(for: room, on: server) ?? 0)
if serverID > lastMessageServerID { if serverID > lastMessageServerID {
let (promise, seal) = Promise<[OpenGroupMessageV2]>.pending() let (promise, seal) = Promise<[LegacyOpenGroupMessageV2]>.pending()
storage.write( storage.write(
with: { transaction in with: { transaction in
@ -1351,15 +1259,15 @@ public final class OpenGroupAPI: NSObject {
} }
@available(*, deprecated, message: "Use v4 endpoint instead") @available(*, deprecated, message: "Use v4 endpoint instead")
private static func legacyProcess(deletions: [Deletion]?, for room: String, on server: String) -> Promise<[Deletion]> { private static func legacyProcess(deletions: [LegacyDeletion]?, for room: String, on server: String) -> Promise<[LegacyDeletion]> {
guard let deletions: [Deletion] = deletions else { return Promise.value([]) } guard let deletions: [LegacyDeletion] = deletions else { return Promise.value([]) }
let storage = SNMessagingKitConfiguration.shared.storage let storage = SNMessagingKitConfiguration.shared.storage
let serverID: Int64 = (deletions.compactMap { $0.id }.max() ?? 0) let serverID: Int64 = (deletions.compactMap { $0.id }.max() ?? 0)
let lastDeletionServerID: Int64 = (storage.getLastDeletionServerID(for: room, on: server) ?? 0) let lastDeletionServerID: Int64 = (storage.getLastDeletionServerID(for: room, on: server) ?? 0)
if serverID > lastDeletionServerID { if serverID > lastDeletionServerID {
let (promise, seal) = Promise<[Deletion]>.pending() let (promise, seal) = Promise<[LegacyDeletion]>.pending()
storage.write( storage.write(
with: { transaction in 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 pollers: [String: OpenGroupAPI.Poller] = [:] // One for each server
private var isPolling = false 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 // MARK: - Polling
@objc public func startPolling() { @objc public func startPolling() {
guard !isPolling else { return } guard !isPolling else { return }
@ -30,13 +37,13 @@ public final class OpenGroupManager: NSObject {
// MARK: - Adding & Removing // 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 let storage = Storage.shared
// Clear any existing data if needed // Clear any existing data if needed
storage.removeLastMessageServerID(for: room, on: server, using: transaction) storage.removeLastMessageServerID(for: roomToken, on: server, using: transaction)
storage.removeLastDeletionServerID(for: room, on: server, using: transaction) storage.removeLastDeletionServerID(for: roomToken, on: server, using: transaction)
storage.removeAuthToken(for: room, on: server, using: transaction) storage.removeAuthToken(for: roomToken, on: server, using: transaction)
// Store the public key // Store the public key
storage.setOpenGroupPublicKey(for: server, to: publicKey, using: transaction) storage.setOpenGroupPublicKey(for: server, to: publicKey, using: transaction)
@ -45,111 +52,17 @@ public final class OpenGroupManager: NSObject {
let transaction = transaction as! YapDatabaseReadWriteTransaction let transaction = transaction as! YapDatabaseReadWriteTransaction
transaction.addCompletionQueue(DispatchQueue.global(qos: .userInitiated)) { transaction.addCompletionQueue(DispatchQueue.global(qos: .userInitiated)) {
// Get the group info OpenGroupAPI.room(for: roomToken, on: server)
// 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)
.done(on: DispatchQueue.global(qos: .userInitiated)) { _, room in .done(on: DispatchQueue.global(qos: .userInitiated)) { _, room in
// Create the open group model and the thread OpenGroupManager.handleRoom(
let openGroup: OpenGroupV2 = OpenGroupV2( room,
server: server,
room: room.token,
name: room.name,
publicKey: publicKey, publicKey: publicKey,
imageID: room.imageId.map { "\($0)" } // TODO: Update this? for: roomToken,
) on: server,
isBackgroundPoll: false
let groupID: Data = LKGroupUtilities.getEncodedOpenGroupIDAsData(openGroup.id) ) {
let model: TSGroupModel = TSGroupModel( seal.fulfill(())
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(())
}
)
} }
.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in .catch(on: DispatchQueue.global(qos: .userInitiated)) { error in
seal.reject(error) 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)? { 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 } 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: // Inputs that should work:

View File

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

View File

@ -234,7 +234,7 @@ extension MessageReceiver {
// Open groups // Open groups
for openGroupURL in message.openGroups { for openGroupURL in message.openGroups {
if let (room, server, publicKey) = OpenGroupManager.parseV2OpenGroup(from: openGroupURL) { 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) { private func handlePollResponse(_ response: [OpenGroupAPI.Endpoint: (info: OnionRequestResponseInfoType, data: Codable)], isBackgroundPoll: Bool) {
let storage = SNMessagingKitConfiguration.shared.storage
response.forEach { endpoint, response in response.forEach { endpoint, response in
switch endpoint { switch endpoint {
case .roomMessagesRecent(let roomToken), .roomMessagesBefore(let roomToken, _), .roomMessagesSince(let roomToken, _): case .roomMessagesRecent(let roomToken), .roomMessagesBefore(let roomToken, _), .roomMessagesSince(let roomToken, _):
guard let responseData: [OpenGroupAPI.Message] = response.data as? [OpenGroupAPI.Message] else { guard let responseData: [OpenGroupAPI.Message] = response.data as? [OpenGroupAPI.Message] else {
//SNLog("Open group polling failed due to error: \(error).") SNLog("Open group polling failed due to invalid data.")
return // TODO: Throw error? return
} }
handleMessages(responseData, roomToken: roomToken, isBackgroundPoll: isBackgroundPoll, using: storage) OpenGroupManager.handleMessages(
responseData,
for: roomToken,
on: server,
isBackgroundPoll: isBackgroundPoll
)
case .roomPollInfo(let roomToken, _): case .roomPollInfo(let roomToken, _):
guard let responseData: OpenGroupAPI.RoomPollInfo = response.data as? OpenGroupAPI.RoomPollInfo else { guard let responseData: OpenGroupAPI.RoomPollInfo = response.data as? OpenGroupAPI.RoomPollInfo else {
//SNLog("Open group polling failed due to error: \(error).") SNLog("Open group polling failed due to invalid data.")
return // TODO: Throw error? 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 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 // MARK: - Legacy Handling
private func handleCompactPollBody(_ body: OpenGroupAPI.LegacyCompactPollResponse.Result, isBackgroundPoll: Bool) { 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 setLastDeletionServerID(for room: String, on server: String, to newValue: Int64, using transaction: Any)
func removeLastDeletionServerID(for room: String, on server: String, 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 // MARK: - Message Handling
func getReceivedMessageTimestamps(using transaction: Any) -> [UInt64] func getReceivedMessageTimestamps(using transaction: Any) -> [UInt64]

View File

@ -81,9 +81,19 @@ class OpenGroupAPITests: XCTestCase {
) )
testStorage.mockData[.allV2OpenGroups] = [ 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) // Test Private key, not actually used (from here https://www.notion.so/oxen/SOGS-Authentication-dc64cc846cb24b2abbf7dd4bfd74abbb)
testStorage.mockData[.userKeyPair] = try! ECKeyPair( testStorage.mockData[.userKeyPair] = try! ECKeyPair(

View File

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