mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
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:
parent
8cc9caa0fd
commit
b655882cbd
30 changed files with 771 additions and 607 deletions
|
@ -791,22 +791,22 @@
|
|||
FDC4381527B329CE00C60D73 /* NonceGenerator16Byte.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381427B329CE00C60D73 /* NonceGenerator16Byte.swift */; };
|
||||
FDC4381727B32EC700C60D73 /* Personalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381627B32EC700C60D73 /* Personalization.swift */; };
|
||||
FDC4381A27B34EBA00C60D73 /* LegacyCompactPollBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381927B34EBA00C60D73 /* LegacyCompactPollBody.swift */; };
|
||||
FDC4381C27B354AC00C60D73 /* PublicKeyBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381B27B354AC00C60D73 /* PublicKeyBody.swift */; };
|
||||
FDC4381C27B354AC00C60D73 /* LegacyPublicKeyBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381B27B354AC00C60D73 /* LegacyPublicKeyBody.swift */; };
|
||||
FDC4382027B36ADC00C60D73 /* Endpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381F27B36ADC00C60D73 /* Endpoint.swift */; };
|
||||
FDC4382627B37F6900C60D73 /* DeletedMessagesResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4382527B37F6900C60D73 /* DeletedMessagesResponse.swift */; };
|
||||
FDC4382827B37FD300C60D73 /* ModeratorsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4382727B37FD300C60D73 /* ModeratorsResponse.swift */; };
|
||||
FDC4382627B37F6900C60D73 /* LegacyDeletedMessagesResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4382527B37F6900C60D73 /* LegacyDeletedMessagesResponse.swift */; };
|
||||
FDC4382827B37FD300C60D73 /* LegacyModeratorsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4382727B37FD300C60D73 /* LegacyModeratorsResponse.swift */; };
|
||||
FDC4382A27B3802D00C60D73 /* LegacyRoomsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4382927B3802D00C60D73 /* LegacyRoomsResponse.swift */; };
|
||||
FDC4382C27B380E300C60D73 /* MemberCountResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4382B27B380E300C60D73 /* MemberCountResponse.swift */; };
|
||||
FDC4382C27B380E300C60D73 /* LegacyMemberCountResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4382B27B380E300C60D73 /* LegacyMemberCountResponse.swift */; };
|
||||
FDC4382F27B383AF00C60D73 /* UnregisterResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4382E27B383AF00C60D73 /* UnregisterResponse.swift */; };
|
||||
FDC4383127B3841C00C60D73 /* RegisterResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4383027B3841C00C60D73 /* RegisterResponse.swift */; };
|
||||
FDC4383827B3863200C60D73 /* VersionResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4383727B3863200C60D73 /* VersionResponse.swift */; };
|
||||
FDC4383A27B4696200C60D73 /* AuthTokenResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4383927B4696200C60D73 /* AuthTokenResponse.swift */; };
|
||||
FDC4383A27B4696200C60D73 /* LegacyAuthTokenResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4383927B4696200C60D73 /* LegacyAuthTokenResponse.swift */; };
|
||||
FDC4383E27B4708600C60D73 /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4383D27B4708600C60D73 /* Atomic.swift */; };
|
||||
FDC4384027B4746D00C60D73 /* LegacyGetInfoResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4383F27B4746D00C60D73 /* LegacyGetInfoResponse.swift */; };
|
||||
FDC4384727B47F4D00C60D73 /* OpenGroupMessageV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4384327B47F4D00C60D73 /* OpenGroupMessageV2.swift */; };
|
||||
FDC4384727B47F4D00C60D73 /* LegacyOpenGroupMessageV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4384327B47F4D00C60D73 /* LegacyOpenGroupMessageV2.swift */; };
|
||||
FDC4384827B47F4D00C60D73 /* LegacyRoomInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4384427B47F4D00C60D73 /* LegacyRoomInfo.swift */; };
|
||||
FDC4384927B47F4D00C60D73 /* LegacyCompactPollResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4384527B47F4D00C60D73 /* LegacyCompactPollResponse.swift */; };
|
||||
FDC4384A27B47F4D00C60D73 /* Deletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4384627B47F4D00C60D73 /* Deletion.swift */; };
|
||||
FDC4384A27B47F4D00C60D73 /* LegacyDeletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4384627B47F4D00C60D73 /* LegacyDeletion.swift */; };
|
||||
FDC4384C27B47F7700C60D73 /* OpenGroupV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4384B27B47F7700C60D73 /* OpenGroupV2.swift */; };
|
||||
FDC4384F27B4804F00C60D73 /* Header.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4384E27B4804F00C60D73 /* Header.swift */; };
|
||||
FDC4385127B4807400C60D73 /* QueryParam.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4385027B4807400C60D73 /* QueryParam.swift */; };
|
||||
|
@ -846,6 +846,8 @@
|
|||
FDC438C327BB512200C60D73 /* SodiumProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C227BB512200C60D73 /* SodiumProtocols.swift */; };
|
||||
FDC438C727BB6DF000C60D73 /* DirectMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C627BB6DF000C60D73 /* DirectMessage.swift */; };
|
||||
FDC438C927BB706500C60D73 /* SendDirectMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C827BB706500C60D73 /* SendDirectMessageRequest.swift */; };
|
||||
FDC438CB27BB7DB100C60D73 /* UpdateMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438CA27BB7DB100C60D73 /* UpdateMessageRequest.swift */; };
|
||||
FDC438CD27BC641200C60D73 /* Set+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438CC27BC641200C60D73 /* Set+Utilities.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
@ -1925,22 +1927,22 @@
|
|||
FDC4381427B329CE00C60D73 /* NonceGenerator16Byte.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonceGenerator16Byte.swift; sourceTree = "<group>"; };
|
||||
FDC4381627B32EC700C60D73 /* Personalization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Personalization.swift; sourceTree = "<group>"; };
|
||||
FDC4381927B34EBA00C60D73 /* LegacyCompactPollBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyCompactPollBody.swift; sourceTree = "<group>"; };
|
||||
FDC4381B27B354AC00C60D73 /* PublicKeyBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicKeyBody.swift; sourceTree = "<group>"; };
|
||||
FDC4381B27B354AC00C60D73 /* LegacyPublicKeyBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyPublicKeyBody.swift; sourceTree = "<group>"; };
|
||||
FDC4381F27B36ADC00C60D73 /* Endpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Endpoint.swift; sourceTree = "<group>"; };
|
||||
FDC4382527B37F6900C60D73 /* DeletedMessagesResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedMessagesResponse.swift; sourceTree = "<group>"; };
|
||||
FDC4382727B37FD300C60D73 /* ModeratorsResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModeratorsResponse.swift; sourceTree = "<group>"; };
|
||||
FDC4382527B37F6900C60D73 /* LegacyDeletedMessagesResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyDeletedMessagesResponse.swift; sourceTree = "<group>"; };
|
||||
FDC4382727B37FD300C60D73 /* LegacyModeratorsResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyModeratorsResponse.swift; sourceTree = "<group>"; };
|
||||
FDC4382927B3802D00C60D73 /* LegacyRoomsResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyRoomsResponse.swift; sourceTree = "<group>"; };
|
||||
FDC4382B27B380E300C60D73 /* MemberCountResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberCountResponse.swift; sourceTree = "<group>"; };
|
||||
FDC4382B27B380E300C60D73 /* LegacyMemberCountResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyMemberCountResponse.swift; sourceTree = "<group>"; };
|
||||
FDC4382E27B383AF00C60D73 /* UnregisterResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnregisterResponse.swift; sourceTree = "<group>"; };
|
||||
FDC4383027B3841C00C60D73 /* RegisterResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterResponse.swift; sourceTree = "<group>"; };
|
||||
FDC4383727B3863200C60D73 /* VersionResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionResponse.swift; sourceTree = "<group>"; };
|
||||
FDC4383927B4696200C60D73 /* AuthTokenResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthTokenResponse.swift; sourceTree = "<group>"; };
|
||||
FDC4383927B4696200C60D73 /* LegacyAuthTokenResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyAuthTokenResponse.swift; sourceTree = "<group>"; };
|
||||
FDC4383D27B4708600C60D73 /* Atomic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Atomic.swift; sourceTree = "<group>"; };
|
||||
FDC4383F27B4746D00C60D73 /* LegacyGetInfoResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyGetInfoResponse.swift; sourceTree = "<group>"; };
|
||||
FDC4384327B47F4D00C60D73 /* OpenGroupMessageV2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenGroupMessageV2.swift; sourceTree = "<group>"; };
|
||||
FDC4384327B47F4D00C60D73 /* LegacyOpenGroupMessageV2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyOpenGroupMessageV2.swift; sourceTree = "<group>"; };
|
||||
FDC4384427B47F4D00C60D73 /* LegacyRoomInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyRoomInfo.swift; sourceTree = "<group>"; };
|
||||
FDC4384527B47F4D00C60D73 /* LegacyCompactPollResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyCompactPollResponse.swift; sourceTree = "<group>"; };
|
||||
FDC4384627B47F4D00C60D73 /* Deletion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Deletion.swift; sourceTree = "<group>"; };
|
||||
FDC4384627B47F4D00C60D73 /* LegacyDeletion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyDeletion.swift; sourceTree = "<group>"; };
|
||||
FDC4384B27B47F7700C60D73 /* OpenGroupV2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenGroupV2.swift; sourceTree = "<group>"; };
|
||||
FDC4384E27B4804F00C60D73 /* Header.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Header.swift; sourceTree = "<group>"; };
|
||||
FDC4385027B4807400C60D73 /* QueryParam.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryParam.swift; sourceTree = "<group>"; };
|
||||
|
@ -1978,6 +1980,8 @@
|
|||
FDC438C227BB512200C60D73 /* SodiumProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SodiumProtocols.swift; sourceTree = "<group>"; };
|
||||
FDC438C627BB6DF000C60D73 /* DirectMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectMessage.swift; sourceTree = "<group>"; };
|
||||
FDC438C827BB706500C60D73 /* SendDirectMessageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendDirectMessageRequest.swift; sourceTree = "<group>"; };
|
||||
FDC438CA27BB7DB100C60D73 /* UpdateMessageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateMessageRequest.swift; sourceTree = "<group>"; };
|
||||
FDC438CC27BC641200C60D73 /* Set+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Set+Utilities.swift"; sourceTree = "<group>"; };
|
||||
FEDBAE1B98C49BBE8C87F575 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
FF9BA33D021B115B1F5B4E46 /* Pods-SessionMessagingKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionMessagingKit.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SessionMessagingKit/Pods-SessionMessagingKit.app store release.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
@ -2500,6 +2504,7 @@
|
|||
C33FDB8A255A581200E217F9 /* AppContext.h */,
|
||||
C33FDB85255A581100E217F9 /* AppContext.m */,
|
||||
C3C2A5D12553860800C340D1 /* Array+Utilities.swift */,
|
||||
FDC438CC27BC641200C60D73 /* Set+Utilities.swift */,
|
||||
C33FDAA8255A57FF00E217F9 /* BuildConfiguration.swift */,
|
||||
B8F5F58225EC94A6003BF8D4 /* Collection+Subscripting.swift */,
|
||||
B8AE75A325A6C6A6001A84D2 /* Data+Utilities.swift */,
|
||||
|
@ -3836,7 +3841,6 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
FDC4384B27B47F7700C60D73 /* OpenGroupV2.swift */,
|
||||
FDC4381B27B354AC00C60D73 /* PublicKeyBody.swift */,
|
||||
FDC4386A27B4E88F00C60D73 /* BatchRequestInfo.swift */,
|
||||
FDC4386627B4E10E00C60D73 /* Capabilities.swift */,
|
||||
FDC4385C27B4C18900C60D73 /* Room.swift */,
|
||||
|
@ -3844,6 +3848,7 @@
|
|||
FDC4385E27B4C4A200C60D73 /* PinnedMessage.swift */,
|
||||
FDC4386027B4CDDF00C60D73 /* FileResponse.swift */,
|
||||
FDC4387727B5C35400C60D73 /* SendMessageRequest.swift */,
|
||||
FDC438CA27BB7DB100C60D73 /* UpdateMessageRequest.swift */,
|
||||
FDC4386227B4D94E00C60D73 /* OGMessage.swift */,
|
||||
FDC438C627BB6DF000C60D73 /* DirectMessage.swift */,
|
||||
FDC438C827BB706500C60D73 /* SendDirectMessageRequest.swift */,
|
||||
|
@ -3853,17 +3858,18 @@
|
|||
FDC438A927BB12BB00C60D73 /* UserModeratorRequest.swift */,
|
||||
FDC438AB27BB145200C60D73 /* UserDeleteMessagesRequest.swift */,
|
||||
FDC438AD27BB148700C60D73 /* UserDeleteMessagesResponse.swift */,
|
||||
FDC4382B27B380E300C60D73 /* MemberCountResponse.swift */,
|
||||
FDC4382527B37F6900C60D73 /* DeletedMessagesResponse.swift */,
|
||||
FDC4384627B47F4D00C60D73 /* Deletion.swift */,
|
||||
FDC4382727B37FD300C60D73 /* ModeratorsResponse.swift */,
|
||||
FDC4381B27B354AC00C60D73 /* LegacyPublicKeyBody.swift */,
|
||||
FDC4383927B4696200C60D73 /* LegacyAuthTokenResponse.swift */,
|
||||
FDC4381927B34EBA00C60D73 /* LegacyCompactPollBody.swift */,
|
||||
FDC4384527B47F4D00C60D73 /* LegacyCompactPollResponse.swift */,
|
||||
FDC4383F27B4746D00C60D73 /* LegacyGetInfoResponse.swift */,
|
||||
FDC4382927B3802D00C60D73 /* LegacyRoomsResponse.swift */,
|
||||
FDC4384427B47F4D00C60D73 /* LegacyRoomInfo.swift */,
|
||||
FDC4384327B47F4D00C60D73 /* OpenGroupMessageV2.swift */,
|
||||
FDC4383927B4696200C60D73 /* AuthTokenResponse.swift */,
|
||||
FDC4382B27B380E300C60D73 /* LegacyMemberCountResponse.swift */,
|
||||
FDC4382727B37FD300C60D73 /* LegacyModeratorsResponse.swift */,
|
||||
FDC4382527B37F6900C60D73 /* LegacyDeletedMessagesResponse.swift */,
|
||||
FDC4384627B47F4D00C60D73 /* LegacyDeletion.swift */,
|
||||
FDC4384327B47F4D00C60D73 /* LegacyOpenGroupMessageV2.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
|
@ -5048,6 +5054,7 @@
|
|||
C3D9E35E25675F640040E4F3 /* OWSFileSystem.m in Sources */,
|
||||
C3C2AC2E2553CBEB00C340D1 /* String+Trimming.swift in Sources */,
|
||||
C32C5B48256DC211003C73A2 /* NSNotificationCenter+OWS.m in Sources */,
|
||||
FDC438CD27BC641200C60D73 /* Set+Utilities.swift in Sources */,
|
||||
C3D9E3C925676AF30040E4F3 /* TSYapDatabaseObject.m in Sources */,
|
||||
FD705A92278D051200F16121 /* ReusableView.swift in Sources */,
|
||||
B8AE75A425A6C6A6001A84D2 /* Data+Utilities.swift in Sources */,
|
||||
|
@ -5151,14 +5158,14 @@
|
|||
B8F5F60325EDE16F003BF8D4 /* DataExtractionNotification.swift in Sources */,
|
||||
C32C5D24256DD4C0003C73A2 /* MentionsManager.swift in Sources */,
|
||||
C3A71D1E25589AC30043A11F /* WebSocketProto.swift in Sources */,
|
||||
FDC4384A27B47F4D00C60D73 /* Deletion.swift in Sources */,
|
||||
FDC4384A27B47F4D00C60D73 /* LegacyDeletion.swift in Sources */,
|
||||
C3C2A7852553AAF300C340D1 /* SessionProtos.pb.swift in Sources */,
|
||||
B8566C63256F55930045A0B9 /* OWSLinkPreview+Conversion.swift in Sources */,
|
||||
FDC4382827B37FD300C60D73 /* ModeratorsResponse.swift in Sources */,
|
||||
FDC4382827B37FD300C60D73 /* LegacyModeratorsResponse.swift in Sources */,
|
||||
C32C5B3F256DC1DF003C73A2 /* TSQuotedMessage+Conversion.swift in Sources */,
|
||||
B8EB20EE2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift in Sources */,
|
||||
FD5D202227B1D74F00FEA984 /* ECKeyPair+Conversion.swift in Sources */,
|
||||
FDC4381C27B354AC00C60D73 /* PublicKeyBody.swift in Sources */,
|
||||
FDC4381C27B354AC00C60D73 /* LegacyPublicKeyBody.swift in Sources */,
|
||||
FDC4382F27B383AF00C60D73 /* UnregisterResponse.swift in Sources */,
|
||||
FDC4386327B4D94E00C60D73 /* OGMessage.swift in Sources */,
|
||||
C3C2A7712553A41E00C340D1 /* ControlMessage.swift in Sources */,
|
||||
|
@ -5173,7 +5180,7 @@
|
|||
C32C5D9C256DD6DC003C73A2 /* OWSOutgoingReceiptManager.m in Sources */,
|
||||
B8AE760B25ABFB5A001A84D2 /* GeneralUtilities.m in Sources */,
|
||||
C32C5C4F256DCC36003C73A2 /* Storage+OpenGroups.swift in Sources */,
|
||||
FDC4384727B47F4D00C60D73 /* OpenGroupMessageV2.swift in Sources */,
|
||||
FDC4384727B47F4D00C60D73 /* LegacyOpenGroupMessageV2.swift in Sources */,
|
||||
FDC4384F27B4804F00C60D73 /* Header.swift in Sources */,
|
||||
C3DA9C0725AE7396008F7C7E /* ConfigurationMessage.swift in Sources */,
|
||||
B8856CEE256F1054001CE70E /* OWSAudioPlayer.m in Sources */,
|
||||
|
@ -5185,7 +5192,7 @@
|
|||
C3BBE0762554CDA60050F1E3 /* Configuration.swift in Sources */,
|
||||
FDC4387627B5BEF300C60D73 /* FileDownloadResponse.swift in Sources */,
|
||||
C35D76DB26606304009AA5FB /* ThreadUpdateBatcher.swift in Sources */,
|
||||
FDC4382C27B380E300C60D73 /* MemberCountResponse.swift in Sources */,
|
||||
FDC4382C27B380E300C60D73 /* LegacyMemberCountResponse.swift in Sources */,
|
||||
B8B32033258B235D0020074B /* Storage+Contacts.swift in Sources */,
|
||||
B8856D69256F141F001CE70E /* OWSWindowManager.m in Sources */,
|
||||
C3D9E3BE25676AD70040E4F3 /* TSAttachmentPointer.m in Sources */,
|
||||
|
@ -5205,7 +5212,7 @@
|
|||
C32C5BEF256DC8EE003C73A2 /* OWSDisappearingMessagesJob.m in Sources */,
|
||||
C34A977425A3E34A00852C71 /* ClosedGroupControlMessage.swift in Sources */,
|
||||
FDC4384C27B47F7700C60D73 /* OpenGroupV2.swift in Sources */,
|
||||
FDC4382627B37F6900C60D73 /* DeletedMessagesResponse.swift in Sources */,
|
||||
FDC4382627B37F6900C60D73 /* LegacyDeletedMessagesResponse.swift in Sources */,
|
||||
B88FA7B826045D100049422F /* OpenGroupAPI.swift in Sources */,
|
||||
C32C5E97256DE0CB003C73A2 /* OWSPrimaryStorage.m in Sources */,
|
||||
FDC438C927BB706500C60D73 /* SendDirectMessageRequest.swift in Sources */,
|
||||
|
@ -5214,7 +5221,7 @@
|
|||
B8856E94256F1C37001CE70E /* OWSSounds.m in Sources */,
|
||||
C32C5BCC256DC830003C73A2 /* Storage+ClosedGroups.swift in Sources */,
|
||||
C3A3A0EC256E1949004D228D /* OWSRecipientIdentity.m in Sources */,
|
||||
FDC4383A27B4696200C60D73 /* AuthTokenResponse.swift in Sources */,
|
||||
FDC4383A27B4696200C60D73 /* LegacyAuthTokenResponse.swift in Sources */,
|
||||
B8F5F56525EC8453003BF8D4 /* Notification+Contacts.swift in Sources */,
|
||||
C32C5AB2256DBE8F003C73A2 /* TSMessage.m in Sources */,
|
||||
C3A3A0FE256E1A3C004D228D /* TSDatabaseSecondaryIndexes.m in Sources */,
|
||||
|
@ -5227,6 +5234,7 @@
|
|||
C3D9E3C025676AD70040E4F3 /* TSAttachment.m in Sources */,
|
||||
FDC4384827B47F4D00C60D73 /* LegacyRoomInfo.swift in Sources */,
|
||||
C32C598A256D0664003C73A2 /* SNProtoEnvelope+Conversion.swift in Sources */,
|
||||
FDC438CB27BB7DB100C60D73 /* UpdateMessageRequest.swift in Sources */,
|
||||
C352A2FF25574B6300338F3E /* MessageSendJob.swift in Sources */,
|
||||
FDC438C327BB512200C60D73 /* SodiumProtocols.swift in Sources */,
|
||||
B8856D11256F112A001CE70E /* OWSAudioSession.swift in Sources */,
|
||||
|
|
|
@ -26,7 +26,9 @@
|
|||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "NO">
|
||||
shouldUseLaunchSchemeArgsEnv = "NO"
|
||||
codeCoverageEnabled = "YES"
|
||||
onlyGenerateCoverageForSpecifiedTargets = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
|
@ -43,6 +45,64 @@
|
|||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
</EnvironmentVariables>
|
||||
<CodeCoverageTargets>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D221A088169C9E5E00537ABF"
|
||||
BuildableName = "Session.app"
|
||||
BlueprintName = "Session"
|
||||
ReferencedContainer = "container:Session.xcodeproj">
|
||||
</BuildableReference>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "7BC01A3A241F40AB00BC7C55"
|
||||
BuildableName = "SessionNotificationServiceExtension.appex"
|
||||
BlueprintName = "SessionNotificationServiceExtension"
|
||||
ReferencedContainer = "container:Session.xcodeproj">
|
||||
</BuildableReference>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "453518671FC635DD00210559"
|
||||
BuildableName = "SessionShareExtension.appex"
|
||||
BlueprintName = "SessionShareExtension"
|
||||
ReferencedContainer = "container:Session.xcodeproj">
|
||||
</BuildableReference>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "C3C2A6EF25539DE700C340D1"
|
||||
BuildableName = "SessionMessagingKit.framework"
|
||||
BlueprintName = "SessionMessagingKit"
|
||||
ReferencedContainer = "container:Session.xcodeproj">
|
||||
</BuildableReference>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "C3C2A59E255385C100C340D1"
|
||||
BuildableName = "SessionSnodeKit.framework"
|
||||
BlueprintName = "SessionSnodeKit"
|
||||
ReferencedContainer = "container:Session.xcodeproj">
|
||||
</BuildableReference>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "C331FF1A2558F9D300070591"
|
||||
BuildableName = "SessionUIKit.framework"
|
||||
BlueprintName = "SessionUIKit"
|
||||
ReferencedContainer = "container:Session.xcodeproj">
|
||||
</BuildableReference>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "C3C2A678255388CC00C340D1"
|
||||
BuildableName = "SessionUtilitiesKit.framework"
|
||||
BlueprintName = "SessionUtilitiesKit"
|
||||
ReferencedContainer = "container:Session.xcodeproj">
|
||||
</BuildableReference>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "C33FD9AA255A548A00E217F9"
|
||||
BuildableName = "SignalUtilitiesKit.framework"
|
||||
BlueprintName = "SignalUtilitiesKit"
|
||||
ReferencedContainer = "container:Session.xcodeproj">
|
||||
</BuildableReference>
|
||||
</CodeCoverageTargets>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
|
|
|
@ -1006,7 +1006,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
|||
// If it's an incoming message the user must have moderator status
|
||||
if (self.interaction.interactionType == OWSInteractionType_IncomingMessage) {
|
||||
NSString *userPublicKey = [LKStorage.shared getUserPublicKey];
|
||||
if (![SNOpenGroupAPI isUserModerator:userPublicKey forRoom:openGroupV2.room onServer:openGroupV2.server]) { return; }
|
||||
if (![SNOpenGroupManager isUserModerator:userPublicKey forRoom:openGroupV2.room onServer:openGroupV2.server]) { return; }
|
||||
}
|
||||
|
||||
// Delete the message
|
||||
|
@ -1060,7 +1060,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
|||
if (self.interaction.interactionType == OWSInteractionType_IncomingMessage) {
|
||||
NSString *userPublicKey = [LKStorage.shared getUserPublicKey];
|
||||
if (openGroupV2 != nil) {
|
||||
if (![SNOpenGroupAPI isUserModerator:userPublicKey forRoom:openGroupV2.room onServer:openGroupV2.server]) { return; }
|
||||
if (![SNOpenGroupManager isUserModerator:userPublicKey forRoom:openGroupV2.room onServer:openGroupV2.server]) { return; }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1133,7 +1133,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
|||
if (interationType == OWSInteractionType_IncomingMessage) {
|
||||
// Only allow deletion on incoming messages if the user has moderation permission
|
||||
if (openGroupV2 != nil) {
|
||||
return [SNOpenGroupAPI isUserModerator:[SNGeneralUtilities getUserPublicKey] forRoom:openGroupV2.room onServer:openGroupV2.server];
|
||||
return [SNOpenGroupManager isUserModerator:[SNGeneralUtilities getUserPublicKey] forRoom:openGroupV2.room onServer:openGroupV2.server];
|
||||
}
|
||||
} else {
|
||||
return YES;
|
||||
|
@ -1155,7 +1155,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
|||
|
||||
// Check that we're a moderator
|
||||
if (openGroupV2 != nil) {
|
||||
return [SNOpenGroupAPI isUserModerator:[SNGeneralUtilities getUserPublicKey] forRoom:openGroupV2.room onServer:openGroupV2.server];
|
||||
return [SNOpenGroupManager isUserModerator:[SNGeneralUtilities getUserPublicKey] forRoom:openGroupV2.room onServer:openGroupV2.server];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -163,7 +163,7 @@ private extension MentionSelectionView {
|
|||
profilePictureView.publicKey = mentionCandidate.publicKey
|
||||
profilePictureView.update()
|
||||
if let server = openGroupServer, let room = openGroupRoom {
|
||||
let isUserModerator = OpenGroupAPI.isUserModerator(mentionCandidate.publicKey, for: room, on: server)
|
||||
let isUserModerator = OpenGroupManager.isUserModerator(mentionCandidate.publicKey, for: room, on: server)
|
||||
moderatorIconImageView.isHidden = !isUserModerator
|
||||
} else {
|
||||
moderatorIconImageView.isHidden = true
|
||||
|
|
|
@ -219,7 +219,7 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate {
|
|||
}
|
||||
if let senderSessionID = senderSessionID, message.isOpenGroupMessage {
|
||||
if let openGroupV2 = Storage.shared.getV2OpenGroup(for: message.uniqueThreadId) {
|
||||
let isUserModerator = OpenGroupAPI.isUserModerator(senderSessionID, for: openGroupV2.room, on: openGroupV2.server)
|
||||
let isUserModerator = OpenGroupManager.isUserModerator(senderSessionID, for: openGroupV2.room, on: openGroupV2.server)
|
||||
moderatorIconImageView.isHidden = !isUserModerator || profilePictureView.isHidden
|
||||
} else {
|
||||
moderatorIconImageView.isHidden = true
|
||||
|
|
|
@ -69,9 +69,10 @@ final class JoinOpenGroupModal : Modal {
|
|||
return presentingViewController!.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
presentingViewController!.dismiss(animated: true, completion: nil)
|
||||
|
||||
Storage.shared.write { [presentingViewController = self.presentingViewController!] transaction in
|
||||
OpenGroupManager.shared
|
||||
.add(room: room, server: server, publicKey: publicKey, using: transaction)
|
||||
.add(roomToken: room, server: server, publicKey: publicKey, using: transaction)
|
||||
.done(on: DispatchQueue.main) { _ in
|
||||
let appDelegate = UIApplication.shared.delegate as! AppDelegate
|
||||
appDelegate.forceSyncConfigurationNowIfNeeded().retainUntilComplete() // FIXME: It's probably cleaner to do this inside addOpenGroup(...)
|
||||
|
|
|
@ -128,7 +128,7 @@ final class JoinOpenGroupVC : BaseVC, UIPageViewControllerDataSource, UIPageView
|
|||
// A V2 open group URL will look like: <optional scheme> + <host> + <optional port> + <room> + <public key>
|
||||
// The host doesn't parse if no explicit scheme is provided
|
||||
if let (room, server, publicKey) = OpenGroupManager.parseV2OpenGroup(from: string) {
|
||||
joinV2OpenGroup(room: room, server: server, publicKey: publicKey)
|
||||
joinV2OpenGroup(roomToken: room, server: server, publicKey: publicKey)
|
||||
} else {
|
||||
let title = NSLocalizedString("invalid_url", comment: "")
|
||||
let message = "Please check the URL you entered and try again."
|
||||
|
@ -136,24 +136,25 @@ final class JoinOpenGroupVC : BaseVC, UIPageViewControllerDataSource, UIPageView
|
|||
}
|
||||
}
|
||||
|
||||
fileprivate func joinV2OpenGroup(room: String, server: String, publicKey: String) {
|
||||
fileprivate func joinV2OpenGroup(roomToken: String, server: String, publicKey: String) {
|
||||
guard !isJoining else { return }
|
||||
isJoining = true
|
||||
ModalActivityIndicatorViewController.present(fromViewController: navigationController!, canCancel: false) { [weak self] _ in
|
||||
Storage.shared.write { transaction in
|
||||
OpenGroupManager.shared.add(room: room, server: server, publicKey: publicKey, using: transaction)
|
||||
.done(on: DispatchQueue.main) { [weak self] _ in
|
||||
self?.presentingViewController?.dismiss(animated: true, completion: nil)
|
||||
let appDelegate = UIApplication.shared.delegate as! AppDelegate
|
||||
appDelegate.forceSyncConfigurationNowIfNeeded().retainUntilComplete() // FIXME: It's probably cleaner to do this inside addOpenGroup(...)
|
||||
}
|
||||
.catch(on: DispatchQueue.main) { [weak self] error in
|
||||
self?.dismiss(animated: true, completion: nil) // Dismiss the loader
|
||||
let title = "Couldn't Join"
|
||||
let message = error.localizedDescription
|
||||
self?.isJoining = false
|
||||
self?.showError(title: title, message: message)
|
||||
}
|
||||
OpenGroupManager.shared
|
||||
.add(roomToken: roomToken, server: server, publicKey: publicKey, using: transaction)
|
||||
.done(on: DispatchQueue.main) { [weak self] _ in
|
||||
self?.presentingViewController?.dismiss(animated: true, completion: nil)
|
||||
let appDelegate = UIApplication.shared.delegate as! AppDelegate
|
||||
appDelegate.forceSyncConfigurationNowIfNeeded().retainUntilComplete() // FIXME: It's probably cleaner to do this inside addOpenGroup(...)
|
||||
}
|
||||
.catch(on: DispatchQueue.main) { [weak self] error in
|
||||
self?.dismiss(animated: true, completion: nil) // Dismiss the loader
|
||||
let title = "Couldn't Join"
|
||||
let message = error.localizedDescription
|
||||
self?.isJoining = false
|
||||
self?.showError(title: title, message: message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -166,10 +167,11 @@ final class JoinOpenGroupVC : BaseVC, UIPageViewControllerDataSource, UIPageView
|
|||
}
|
||||
}
|
||||
|
||||
private final class EnterURLVC : UIViewController, UIGestureRecognizerDelegate, OpenGroupSuggestionGridDelegate {
|
||||
private final class EnterURLVC: UIViewController, UIGestureRecognizerDelegate, OpenGroupSuggestionGridDelegate {
|
||||
weak var joinOpenGroupVC: JoinOpenGroupVC!
|
||||
|
||||
// MARK: Components
|
||||
// MARK: - Components
|
||||
|
||||
private lazy var urlTextView: TextView = {
|
||||
let result = TextView(placeholder: NSLocalizedString("vc_enter_chat_url_text_field_hint", comment: ""))
|
||||
result.keyboardType = .URL
|
||||
|
@ -185,7 +187,8 @@ private final class EnterURLVC : UIViewController, UIGestureRecognizerDelegate,
|
|||
return result
|
||||
}()
|
||||
|
||||
// MARK: Lifecycle
|
||||
// MARK: - Lifecycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
// Remove background color
|
||||
view.backgroundColor = .clear
|
||||
|
@ -223,7 +226,8 @@ private final class EnterURLVC : UIViewController, UIGestureRecognizerDelegate,
|
|||
view.addGestureRecognizer(tapGestureRecognizer)
|
||||
}
|
||||
|
||||
// MARK: General
|
||||
// MARK: - General
|
||||
|
||||
func constrainHeight(to height: CGFloat) {
|
||||
view.set(.height, to: height)
|
||||
}
|
||||
|
@ -232,14 +236,15 @@ private final class EnterURLVC : UIViewController, UIGestureRecognizerDelegate,
|
|||
urlTextView.resignFirstResponder()
|
||||
}
|
||||
|
||||
// MARK: Interaction
|
||||
// MARK: - Interaction
|
||||
|
||||
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
let location = gestureRecognizer.location(in: view)
|
||||
return !suggestionGrid.frame.contains(location)
|
||||
}
|
||||
|
||||
func join(_ room: OpenGroupAPI.LegacyRoomInfo) {
|
||||
joinOpenGroupVC.joinV2OpenGroup(room: room.id, server: OpenGroupAPI.defaultServer, publicKey: OpenGroupAPI.defaultServerPublicKey)
|
||||
func join(_ room: OpenGroupAPI.Room) {
|
||||
joinOpenGroupVC.joinV2OpenGroup(roomToken: room.token, server: OpenGroupAPI.defaultServer, publicKey: OpenGroupAPI.defaultServerPublicKey)
|
||||
}
|
||||
|
||||
@objc private func joinOpenGroup() {
|
||||
|
|
|
@ -3,11 +3,12 @@ import NVActivityIndicatorView
|
|||
|
||||
final class OpenGroupSuggestionGrid : UIView, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
|
||||
private let maxWidth: CGFloat
|
||||
private var rooms: [OpenGroupAPI.LegacyRoomInfo] = [] { didSet { update() } }
|
||||
private var rooms: [OpenGroupAPI.Room] = [] { didSet { update() } }
|
||||
private var heightConstraint: NSLayoutConstraint!
|
||||
var delegate: OpenGroupSuggestionGridDelegate?
|
||||
|
||||
// MARK: UI Components
|
||||
// MARK: - UI
|
||||
|
||||
private lazy var layout: UICollectionViewFlowLayout = {
|
||||
let result = UICollectionViewFlowLayout()
|
||||
result.minimumLineSpacing = 0
|
||||
|
@ -32,11 +33,13 @@ final class OpenGroupSuggestionGrid : UIView, UICollectionViewDataSource, UIColl
|
|||
return result
|
||||
}()
|
||||
|
||||
// MARK: Settings
|
||||
// MARK: - Settings
|
||||
|
||||
private static let cellHeight: CGFloat = 40
|
||||
private static let separatorWidth = 1 / UIScreen.main.scale
|
||||
|
||||
// MARK: Initialization
|
||||
// MARK: - Initialization
|
||||
|
||||
init(maxWidth: CGFloat) {
|
||||
self.maxWidth = maxWidth
|
||||
super.init(frame: CGRect.zero)
|
||||
|
@ -59,16 +62,15 @@ final class OpenGroupSuggestionGrid : UIView, UICollectionViewDataSource, UIColl
|
|||
spinner.startAnimating()
|
||||
heightConstraint = set(.height, to: OpenGroupSuggestionGrid.cellHeight)
|
||||
widthAnchor.constraint(greaterThanOrEqualToConstant: OpenGroupSuggestionGrid.cellHeight).isActive = true
|
||||
if OpenGroupAPI.defaultRoomsPromise == nil {
|
||||
OpenGroupAPI.legacyGetDefaultRoomsIfNeeded()
|
||||
}
|
||||
let _ = OpenGroupAPI.legacyDefaultRoomsPromise?.done { [weak self] rooms in
|
||||
// TODO: Update this for the new rooms API
|
||||
|
||||
OpenGroupManager.getDefaultRoomsIfNeeded()
|
||||
_ = OpenGroupManager.defaultRoomsPromise?.done { [weak self] rooms in
|
||||
self?.rooms = rooms
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Updating
|
||||
// MARK: - Updating
|
||||
|
||||
private func update() {
|
||||
spinner.stopAnimating()
|
||||
spinner.isHidden = true
|
||||
|
@ -78,12 +80,14 @@ final class OpenGroupSuggestionGrid : UIView, UICollectionViewDataSource, UIColl
|
|||
collectionView.reloadData()
|
||||
}
|
||||
|
||||
// MARK: Layout
|
||||
// MARK: - Layout
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
|
||||
return CGSize(width: maxWidth / 2, height: OpenGroupSuggestionGrid.cellHeight)
|
||||
}
|
||||
|
||||
// MARK: Data Source
|
||||
// MARK: - Data Source
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return min(rooms.count, 8) // Cap to a maximum of 8 (4 rows of 2)
|
||||
}
|
||||
|
@ -94,18 +98,20 @@ final class OpenGroupSuggestionGrid : UIView, UICollectionViewDataSource, UIColl
|
|||
return cell
|
||||
}
|
||||
|
||||
// MARK: Interaction
|
||||
// MARK: - Interaction
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
let room = rooms[indexPath.item]
|
||||
delegate?.join(room)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Cell
|
||||
// MARK: - Cell
|
||||
|
||||
extension OpenGroupSuggestionGrid {
|
||||
|
||||
fileprivate final class Cell : UICollectionViewCell {
|
||||
var room: OpenGroupAPI.LegacyRoomInfo? { didSet { update() } }
|
||||
var room: OpenGroupAPI.Room? { didSet { update() } }
|
||||
|
||||
static let identifier = "OpenGroupSuggestionGridCell"
|
||||
|
||||
|
@ -172,17 +178,24 @@ extension OpenGroupSuggestionGrid {
|
|||
}
|
||||
|
||||
private func update() {
|
||||
guard let room = room else { return }
|
||||
let promise = OpenGroupAPI.legacyGetGroupImage(for: room.id, on: OpenGroupAPI.defaultServer)
|
||||
imageView.image = given(promise.value) { UIImage(data: $0)! }
|
||||
imageView.isHidden = (imageView.image == nil)
|
||||
guard let room: OpenGroupAPI.Room = room else { return }
|
||||
|
||||
label.text = room.name
|
||||
|
||||
if let imageId: Int64 = room.imageId {
|
||||
let promise = OpenGroupManager.roomImage(imageId, for: room.token, on: OpenGroupAPI.defaultServer)
|
||||
imageView.image = given(promise.value) { UIImage(data: $0)! }
|
||||
imageView.isHidden = (imageView.image == nil)
|
||||
}
|
||||
else {
|
||||
imageView.isHidden = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Delegate
|
||||
// MARK: - Delegate
|
||||
|
||||
protocol OpenGroupSuggestionGridDelegate {
|
||||
|
||||
func join(_ room: OpenGroupAPI.LegacyRoomInfo)
|
||||
func join(_ room: OpenGroupAPI.Room)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,12 @@
|
|||
public protocol SessionMessagingKitOpenGroupStorageProtocol {
|
||||
func getOpenGroupImage(for room: String, on server: String) -> Data?
|
||||
func setOpenGroupImage(to data: Data, for room: String, on server: String, using transaction: Any)
|
||||
|
||||
func getV2OpenGroup(for threadID: String) -> OpenGroupV2?
|
||||
func setV2OpenGroup(_ openGroup: OpenGroupV2, for threadID: String, using transaction: Any)
|
||||
|
||||
func getUserCount(forV2OpenGroupWithID openGroupID: String) -> UInt64?
|
||||
func setUserCount(to newValue: UInt64, forV2OpenGroupWithID openGroupID: String, using transaction: Any)
|
||||
}
|
||||
|
||||
extension Storage: SessionMessagingKitOpenGroupStorageProtocol {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import Foundation
|
||||
|
||||
extension OpenGroupAPI {
|
||||
struct AuthTokenResponse: Codable {
|
||||
struct LegacyAuthTokenResponse: Codable {
|
||||
struct Challenge: Codable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case ciphertext = "ciphertext"
|
||||
|
@ -20,7 +20,7 @@ extension OpenGroupAPI {
|
|||
|
||||
// MARK: - Codable
|
||||
|
||||
extension OpenGroupAPI.AuthTokenResponse.Challenge {
|
||||
extension OpenGroupAPI.LegacyAuthTokenResponse.Challenge {
|
||||
init(from decoder: Decoder) throws {
|
||||
let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
|
@ -31,7 +31,7 @@ extension OpenGroupAPI.AuthTokenResponse.Challenge {
|
|||
throw OpenGroupAPI.Error.parsingFailed
|
||||
}
|
||||
|
||||
self = OpenGroupAPI.AuthTokenResponse.Challenge(
|
||||
self = OpenGroupAPI.LegacyAuthTokenResponse.Challenge(
|
||||
ciphertext: ciphertext,
|
||||
ephemeralPublicKey: ephemeralPublicKey
|
||||
)
|
|
@ -15,8 +15,8 @@ extension OpenGroupAPI {
|
|||
|
||||
public let room: String
|
||||
public let statusCode: UInt
|
||||
public let messages: [OpenGroupMessageV2]?
|
||||
public let deletions: [Deletion]?
|
||||
public let messages: [LegacyOpenGroupMessageV2]?
|
||||
public let deletions: [LegacyDeletion]?
|
||||
public let moderators: [String]?
|
||||
}
|
||||
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
import Foundation
|
||||
|
||||
extension OpenGroupAPI {
|
||||
struct DeletedMessagesResponse: Codable {
|
||||
struct LegacyDeletedMessagesResponse: Codable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case deletions = "ids"
|
||||
}
|
||||
|
||||
let deletions: [Deletion]
|
||||
let deletions: [LegacyDeletion]
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
import Foundation
|
||||
|
||||
extension OpenGroupAPI {
|
||||
public struct Deletion: Codable {
|
||||
public struct LegacyDeletion: Codable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case deletedMessageID = "deleted_message_id"
|
||||
|
@ -12,12 +12,12 @@ extension OpenGroupAPI {
|
|||
let id: Int64
|
||||
let deletedMessageID: Int64
|
||||
|
||||
public static func from(_ json: JSON) -> Deletion? {
|
||||
public static func from(_ json: JSON) -> LegacyDeletion? {
|
||||
guard let id = json["id"] as? Int64, let deletedMessageID = json["deleted_message_id"] as? Int64 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return Deletion(id: id, deletedMessageID: deletedMessageID)
|
||||
return LegacyDeletion(id: id, deletedMessageID: deletedMessageID)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
import Foundation
|
||||
|
||||
extension OpenGroupAPI {
|
||||
struct MemberCountResponse: Codable {
|
||||
struct LegacyMemberCountResponse: Codable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case memberCount = "member_count"
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
import Foundation
|
||||
|
||||
extension OpenGroupAPI {
|
||||
struct ModeratorsResponse: Codable {
|
||||
struct LegacyModeratorsResponse: Codable {
|
||||
let moderators: [String]
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import Foundation
|
||||
import SessionUtilitiesKit
|
||||
|
||||
public struct OpenGroupMessageV2: Codable {
|
||||
public struct LegacyOpenGroupMessageV2: Codable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case serverID = "server_id"
|
||||
case sender = "public_key"
|
||||
|
@ -19,7 +19,7 @@ public struct OpenGroupMessageV2: Codable {
|
|||
/// a receiving user can verify that the message wasn't tampered with.
|
||||
public let base64EncodedSignature: String?
|
||||
|
||||
public func sign(with publicKey: String) -> OpenGroupMessageV2? {
|
||||
public func sign(with publicKey: String) -> LegacyOpenGroupMessageV2? {
|
||||
guard let userKeyPair = SNMessagingKitConfiguration.shared.storage.getUserKeyPair() else { return nil }
|
||||
guard let data = Data(base64Encoded: base64EncodedData) else { return nil }
|
||||
guard let signature = try? Ed25519.sign(data, with: userKeyPair) else {
|
||||
|
@ -27,7 +27,7 @@ public struct OpenGroupMessageV2: Codable {
|
|||
return nil
|
||||
}
|
||||
|
||||
return OpenGroupMessageV2(
|
||||
return LegacyOpenGroupMessageV2(
|
||||
serverID: serverID,
|
||||
sender: sender,
|
||||
sentTimestamp: sentTimestamp,
|
||||
|
@ -39,7 +39,7 @@ public struct OpenGroupMessageV2: Codable {
|
|||
|
||||
// MARK: - Decoder
|
||||
|
||||
extension OpenGroupMessageV2 {
|
||||
extension LegacyOpenGroupMessageV2 {
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
|
@ -60,7 +60,7 @@ extension OpenGroupMessageV2 {
|
|||
throw OpenGroupAPI.Error.parsingFailed
|
||||
}
|
||||
|
||||
self = OpenGroupMessageV2(
|
||||
self = LegacyOpenGroupMessageV2(
|
||||
serverID: try? container.decode(Int64.self, forKey: .serverID),
|
||||
sender: sender,
|
||||
sentTimestamp: try container.decode(UInt64.self, forKey: .sentTimestamp),
|
|
@ -3,7 +3,7 @@
|
|||
import Foundation
|
||||
|
||||
extension OpenGroupAPI {
|
||||
struct PublicKeyBody: Codable {
|
||||
struct LegacyPublicKeyBody: Codable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case publicKey = "public_key"
|
||||
}
|
|
@ -71,3 +71,37 @@ extension OpenGroupAPI {
|
|||
public let details: Room?
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Convenience
|
||||
|
||||
extension OpenGroupAPI.RoomPollInfo {
|
||||
init(room: OpenGroupAPI.Room) {
|
||||
self.init(
|
||||
token: room.token,
|
||||
created: room.created,
|
||||
name: room.name,
|
||||
description: room.description,
|
||||
imageId: room.imageId,
|
||||
infoUpdates: room.infoUpdates,
|
||||
messageSequence: room.messageSequence,
|
||||
activeUsers: room.activeUsers,
|
||||
activeUsersCutoff: room.activeUsersCutoff,
|
||||
pinnedMessages: room.pinnedMessages,
|
||||
admin: room.admin,
|
||||
globalAdmin: room.globalAdmin,
|
||||
admins: room.admins,
|
||||
hiddenAdmins: room.hiddenAdmins,
|
||||
moderator: room.moderator,
|
||||
globalModerator: room.globalModerator,
|
||||
moderators: room.moderators,
|
||||
hiddenModerators: room.hiddenModerators,
|
||||
read: room.read,
|
||||
defaultRead: room.defaultRead,
|
||||
write: room.write,
|
||||
defaultWrite: room.defaultWrite,
|
||||
upload: room.upload,
|
||||
defaultUpload: room.defaultUpload,
|
||||
details: nil
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,23 +45,5 @@ extension OpenGroupAPI {
|
|||
try container.encode(whisperMods, forKey: .whisperMods)
|
||||
try container.encodeIfPresent(fileIds, forKey: .fileIds)
|
||||
}
|
||||
|
||||
// MARK: - Signing
|
||||
|
||||
public static func sign(message: Data, for idType: IdPrefix, with publicKey: String) -> (data: Data, signature: Data)? {
|
||||
guard let userKeyPair: ECKeyPair = SNMessagingKitConfiguration.shared.storage.getUserKeyPair() else {
|
||||
return nil
|
||||
}
|
||||
guard let targetKeyPair: ECKeyPair = try? userKeyPair.convert(to: idType, with: publicKey) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let signature = try? Ed25519.sign(message, with: targetKeyPair) else {
|
||||
SNLog("Failed to sign open group message.")
|
||||
return nil
|
||||
}
|
||||
|
||||
return (message, signature)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,11 +7,6 @@ extension OpenGroupAPI {
|
|||
// TODO: Upgrade this to use the non-legacy version.
|
||||
return AnyPromise.from(legacyDeleteMessage(with: serverID, from: room, on: server))
|
||||
}
|
||||
|
||||
@objc(isUserModerator:forRoom:onServer:)
|
||||
public static func objc_isUserModerator(_ publicKey: String, for room: String, on server: String) -> Bool {
|
||||
return isUserModerator(publicKey, for: room, on: server)
|
||||
}
|
||||
|
||||
@objc(legacyGetDefaultRoomsIfNeeded)
|
||||
public static func objc_legacyGetDefaultRoomsIfNeeded() {
|
||||
|
|
|
@ -11,42 +11,46 @@ public final class OpenGroupAPI: NSObject {
|
|||
public static let defaultServer = "http://116.203.70.33"
|
||||
public static let defaultServerPublicKey = "a03c383cf63c3c4efe67acc52112a6dd734b3a946b9545f488aaa93da7991238"
|
||||
|
||||
// MARK: - Cache
|
||||
|
||||
private static var authTokenPromises: Atomic<[String: Promise<String>]> = Atomic([:])
|
||||
private static var hasPerformedInitialPoll: [String: Bool] = [:]
|
||||
private static var hasUpdatedLastOpenDate = false
|
||||
public static let workQueue = DispatchQueue(label: "OpenGroupAPI.workQueue", qos: .userInitiated) // It's important that this is a serial queue
|
||||
public static var moderators: [String: [String: Set<String>]] = [:] // Server URL to room ID to set of moderator IDs
|
||||
public static var defaultRoomsPromise: Promise<[Room]>?
|
||||
public static var groupImagePromises: [String: Promise<Data>] = [:]
|
||||
|
||||
// MARK: - Polling State
|
||||
|
||||
private static var hasPerformedInitialPoll: [String: Bool] = [:]
|
||||
private static var timeSinceLastPoll: [String: TimeInterval] = [:]
|
||||
private static var lastPollTime: TimeInterval = .greatestFiniteMagnitude
|
||||
|
||||
private static let timeSinceLastOpen: TimeInterval = {
|
||||
guard let lastOpen = UserDefaults.standard[.lastOpen] else { return .greatestFiniteMagnitude }
|
||||
|
||||
return Date().timeIntervalSince(lastOpen)
|
||||
}()
|
||||
|
||||
|
||||
// TODO: Remove these
|
||||
private static var legacyAuthTokenPromises: Atomic<[String: Promise<String>]> = Atomic([:])
|
||||
private static var legacyHasUpdatedLastOpenDate = false
|
||||
private static var legacyGroupImagePromises: [String: Promise<Data>] = [:]
|
||||
|
||||
|
||||
// MARK: - Batching & Polling
|
||||
|
||||
/// This is a convenience method which calls `/batch` with a pre-defined set of requests used to update an Open Group
|
||||
/// This is a convenience method which calls `/batch` with a pre-defined set of requests used to update an Open
|
||||
/// Group, currently this will retrieve:
|
||||
/// - Capabilities for the server
|
||||
/// - For each room:
|
||||
/// - Poll Info
|
||||
/// - Messages (includes additions and deletions)
|
||||
public static func poll(_ server: String, using dependencies: Dependencies = Dependencies()) -> Promise<[Endpoint: (OnionRequestResponseInfoType, Codable)]> {
|
||||
// TODO: Remove comments
|
||||
// Capabilities
|
||||
// Fetch each room
|
||||
// Poll Info
|
||||
// /room/<token>/pollInfo/<id> instead?
|
||||
// Fetch messages for each room
|
||||
// /room/{roomToken}/messages/since/{messageSequence}:
|
||||
// Fetch deletions for each room (included in messages)
|
||||
// Store a local copy of the cached state for this server
|
||||
let hadPerformedInitialPoll: Bool = (hasPerformedInitialPoll[server] == true)
|
||||
let originalTimeSinceLastPoll: TimeInterval = (timeSinceLastPoll[server] ?? min(lastPollTime, timeSinceLastOpen))
|
||||
|
||||
// old compact_poll data
|
||||
// public let room: String
|
||||
// public let statusCode: UInt
|
||||
// public let messages: [OpenGroupMessageV2]?
|
||||
// public let deletions: [Deletion]?
|
||||
// public let moderators: [String]?
|
||||
// Update the cached state for this server
|
||||
hasPerformedInitialPoll[server] = true
|
||||
lastPollTime = min(lastPollTime, timeSinceLastOpen)
|
||||
UserDefaults.standard[.lastOpen] = Date()
|
||||
|
||||
// Generate the requests
|
||||
let requestResponseType: [BatchRequestInfo] = [
|
||||
BatchRequestInfo(
|
||||
request: Request(
|
||||
|
@ -59,27 +63,37 @@ public final class OpenGroupAPI: NSObject {
|
|||
]
|
||||
.appending(
|
||||
dependencies.storage.getAllV2OpenGroups().values
|
||||
.filter { $0.server == server.lowercased() } // Note: The `OpenGroupV2` converts the server value to lowercase during init
|
||||
.filter { $0.server == server.lowercased() } // Note: The `OpenGroupV2` type converts to lowercase in init
|
||||
.flatMap { openGroup -> [BatchRequestInfo] in
|
||||
let lastSeqNo: Int64? = dependencies.storage.getLastMessageServerID(for: openGroup.room, on: server)
|
||||
let targetSeqNo: Int64 = (lastSeqNo ?? 0)
|
||||
let shouldRetrieveRecentMessages: Bool = (
|
||||
lastSeqNo == nil || (
|
||||
// If it's the first poll for this launch and it's been longer than
|
||||
// 'maxInactivityPeriod' then just retrieve recent messages instead
|
||||
// of trying to get all messages since the last one retrieved
|
||||
!hadPerformedInitialPoll &&
|
||||
originalTimeSinceLastPoll > OpenGroupAPI.Poller.maxInactivityPeriod
|
||||
)
|
||||
)
|
||||
|
||||
return [
|
||||
BatchRequestInfo(
|
||||
request: Request(
|
||||
server: server,
|
||||
// TODO: Source the '0' from the open group (will need to add a new field and default to 0)
|
||||
endpoint: .roomPollInfo(openGroup.room, 0)
|
||||
endpoint: .roomPollInfo(openGroup.room, openGroup.infoUpdates)
|
||||
),
|
||||
responseType: RoomPollInfo.self
|
||||
),
|
||||
BatchRequestInfo(
|
||||
request: Request(
|
||||
server: server,
|
||||
endpoint: (lastSeqNo == nil ?
|
||||
endpoint: (shouldRetrieveRecentMessages ?
|
||||
.roomMessagesRecent(openGroup.room) :
|
||||
.roomMessagesSince(openGroup.room, seqNo: targetSeqNo)
|
||||
)
|
||||
// TODO: Limit?
|
||||
// queryParameters: [ .limit: 256 ]
|
||||
),
|
||||
responseType: [Message].self
|
||||
)
|
||||
|
@ -87,7 +101,6 @@ public final class OpenGroupAPI: NSObject {
|
|||
}
|
||||
)
|
||||
|
||||
// TODO: Handle response (maybe in the poller or the OpenGroupManager?)
|
||||
return batch(server, requests: requestResponseType, using: dependencies)
|
||||
}
|
||||
|
||||
|
@ -121,64 +134,35 @@ public final class OpenGroupAPI: NSObject {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: `/sequence` request.
|
||||
|
||||
public static func compactPoll(_ server: String, using dependencies: Dependencies = Dependencies()) -> Promise<LegacyCompactPollResponse> {
|
||||
let rooms: [String] = dependencies.storage.getAllV2OpenGroups().values
|
||||
.filter { $0.server == server }
|
||||
.map { $0.room }
|
||||
let useMessageLimit = (hasPerformedInitialPoll[server] != true && timeSinceLastOpen > OpenGroupAPI.Poller.maxInactivityPeriod)
|
||||
|
||||
hasPerformedInitialPoll[server] = true
|
||||
|
||||
if !hasUpdatedLastOpenDate {
|
||||
UserDefaults.standard[.lastOpen] = dependencies.date
|
||||
hasUpdatedLastOpenDate = true
|
||||
}
|
||||
|
||||
let requestBody: LegacyCompactPollBody = LegacyCompactPollBody(
|
||||
requests: rooms
|
||||
.map { roomId -> LegacyCompactPollBody.Room in
|
||||
LegacyCompactPollBody.Room(
|
||||
id: roomId,
|
||||
fromMessageServerId: (useMessageLimit ? nil :
|
||||
dependencies.storage.getLastMessageServerID(for: roomId, on: server)
|
||||
),
|
||||
fromDeletionServerId: (useMessageLimit ? nil :
|
||||
dependencies.storage.getLastDeletionServerID(for: roomId, on: server)
|
||||
),
|
||||
legacyAuthToken: nil
|
||||
)
|
||||
}
|
||||
)
|
||||
/// The requests are guaranteed to be performed sequentially in the order given in the request and will abort if any request does not return a status-`2xx` response.
|
||||
///
|
||||
/// For example, this can be used to ban and delete all of a user's messages by sequencing the ban followed by the `delete_all`: if the ban fails (e.g. because
|
||||
/// permission is denied) then the `delete_all` will not occur. The batch body and response are identical to the `/batch` endpoint; requests that are not
|
||||
/// carried out because of an earlier failure will have a response code of `412` (Precondition Failed)."
|
||||
private static func sequence(_ server: String, requests: [BatchRequestInfo], using dependencies: Dependencies = Dependencies()) -> Promise<[Endpoint: (OnionRequestResponseInfoType, Codable)]> {
|
||||
let requestBody: BatchRequest = requests.map { BatchSubRequest(request: $0.request) }
|
||||
let responseTypes = requests.map { $0.responseType }
|
||||
|
||||
guard let body: Data = try? JSONEncoder().encode(requestBody) else {
|
||||
return Promise(error: HTTP.Error.invalidJSON)
|
||||
}
|
||||
|
||||
let request = Request(
|
||||
|
||||
let request: Request = Request(
|
||||
method: .post,
|
||||
server: server,
|
||||
endpoint: .legacyCompactPoll(legacyAuth: false),
|
||||
endpoint: .sequence,
|
||||
body: body
|
||||
)
|
||||
|
||||
// TODO: Handle a `412` response (ie. a required capability isn't supported)
|
||||
return send(request, using: dependencies)
|
||||
.then(on: OpenGroupAPI.workQueue) { _, maybeData -> Promise<LegacyCompactPollResponse> in
|
||||
guard let data: Data = maybeData else { throw Error.parsingFailed }
|
||||
|
||||
let response: LegacyCompactPollResponse = try data.decoded(as: LegacyCompactPollResponse.self, customError: Error.parsingFailed)
|
||||
|
||||
return when(
|
||||
fulfilled: response.results
|
||||
.map { (result: LegacyCompactPollResponse.Result) in
|
||||
legacyProcess(messages: result.messages, for: result.room, on: server)
|
||||
.then(on: OpenGroupAPI.workQueue) { _ in
|
||||
process(deletions: result.deletions, for: result.room, on: server)
|
||||
}
|
||||
}
|
||||
).then(on: OpenGroupAPI.workQueue) { _ in Promise.value(response) }
|
||||
}
|
||||
.decoded(as: responseTypes, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
|
||||
.map { result in
|
||||
result.enumerated()
|
||||
.reduce(into: [:]) { prev, next in
|
||||
prev[requests[next.offset].request.endpoint] = next.element
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Capabilities
|
||||
|
@ -239,13 +223,13 @@ public final class OpenGroupAPI: NSObject {
|
|||
using dependencies: Dependencies = Dependencies()
|
||||
) -> Promise<(OnionRequestResponseInfoType, Message)> {
|
||||
// TODO: Change this to use '.blinded' once it's working.
|
||||
guard let signedRequest: (data: Data, signature: Data) = SendMessageRequest.sign(message: plaintext, for: .standard, with: serverPublicKey) else {
|
||||
guard let signedMessage: (data: Data, signature: Data) = sign(message: plaintext, for: .standard, with: serverPublicKey) else {
|
||||
return Promise(error: Error.signingFailed)
|
||||
}
|
||||
|
||||
let requestBody: SendMessageRequest = SendMessageRequest(
|
||||
data: signedRequest.data,
|
||||
signature: signedRequest.signature,
|
||||
data: signedMessage.data,
|
||||
signature: signedMessage.signature,
|
||||
whisperTo: whisperTo,
|
||||
whisperMods: whisperMods,
|
||||
fileIds: nil // TODO: Add support for 'fileIds'.
|
||||
|
@ -265,62 +249,97 @@ public final class OpenGroupAPI: NSObject {
|
|||
return send(request, using: dependencies)
|
||||
.decoded(as: Message.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
|
||||
}
|
||||
|
||||
public static func message(_ id: Int64, in roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, Message)> {
|
||||
let request: Request = Request(
|
||||
server: server,
|
||||
endpoint: .roomMessageIndividual(roomToken, id: id)
|
||||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: Message.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
|
||||
}
|
||||
|
||||
public static func messageUpdate(
|
||||
_ id: Int64,
|
||||
plaintext: Data,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
with serverPublicKey: String,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> Promise<(OnionRequestResponseInfoType, Data?)> {
|
||||
// TODO: Change this to use '.blinded' once it's working.
|
||||
guard let signedMessage: (data: Data, signature: Data) = sign(message: plaintext, for: .standard, with: serverPublicKey) else {
|
||||
return Promise(error: Error.signingFailed)
|
||||
}
|
||||
|
||||
let requestBody: UpdateMessageRequest = UpdateMessageRequest(
|
||||
data: signedMessage.data,
|
||||
signature: signedMessage.signature
|
||||
)
|
||||
|
||||
guard let body: Data = try? JSONEncoder().encode(requestBody) else {
|
||||
return Promise(error: Error.parsingFailed)
|
||||
}
|
||||
|
||||
let request: Request = Request(
|
||||
method: .put,
|
||||
server: server,
|
||||
endpoint: .roomMessageIndividual(roomToken, id: id),
|
||||
body: body
|
||||
)
|
||||
|
||||
// TODO: Handle custom response info?
|
||||
return send(request, using: dependencies)
|
||||
}
|
||||
|
||||
/// This is the direct request to retrieve recent messages from an Open Group so should be retrieved automatically from the `poll()`
|
||||
/// method, if the logic should change then remove the `@available` line and make sure to route the response of this method to
|
||||
/// the `OpenGroupManager` `handleMessages` method (otherwise the logic may not work correctly)
|
||||
@available(*, unavailable, message: "Avoid using this directly, use the pre-build `poll()` method instead")
|
||||
public static func recentMessages(in roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, [Message])> {
|
||||
// TODO: Recent vs. Since?.
|
||||
let request: Request = Request(
|
||||
server: server,
|
||||
endpoint: .roomMessagesRecent(roomToken)
|
||||
// TODO: Limit?.
|
||||
// queryParameters: [
|
||||
// .fromServerId: storage.getLastMessageServerID(for: room, on: server).map { String($0) }
|
||||
// ].compactMapValues { $0 }
|
||||
// queryParameters: [ .limit: 50 ]
|
||||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: [Message].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
|
||||
.then(on: OpenGroupAPI.workQueue) { responseInfo, messages -> Promise<(OnionRequestResponseInfoType, [Message])> in
|
||||
process(messages: messages, for: roomToken, on: server, using: dependencies)
|
||||
.map { processedMessages in (responseInfo, processedMessages) }
|
||||
}
|
||||
}
|
||||
|
||||
/// This is the direct request to retrieve recent messages from an Open Group so should be retrieved automatically from the `poll()`
|
||||
/// method, if the logic should change then remove the `@available` line and make sure to route the response of this method to
|
||||
/// the `OpenGroupManager` `handleMessages` method (otherwise the logic may not work correctly)
|
||||
@available(*, unavailable, message: "Avoid using this directly, use the pre-build `poll()` method instead")
|
||||
public static func messagesBefore(messageId: Int64, in roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, [Message])> {
|
||||
// TODO: Recent vs. Since?.
|
||||
// TODO: Do we need to be able to load old messages?
|
||||
let request: Request = Request(
|
||||
server: server,
|
||||
endpoint: .roomMessagesBefore(roomToken, id: messageId)
|
||||
// TODO: Limit?.
|
||||
// queryParameters: [
|
||||
// .fromServerId: storage.getLastMessageServerID(for: room, on: server).map { String($0) }
|
||||
// ].compactMapValues { $0 }
|
||||
// queryParameters: [ .limit: 50 ]
|
||||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: [Message].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
|
||||
.then(on: OpenGroupAPI.workQueue) { responseInfo, messages -> Promise<(OnionRequestResponseInfoType, [Message])> in
|
||||
process(messages: messages, for: roomToken, on: server, using: dependencies)
|
||||
.map { processedMessages in (responseInfo, processedMessages) }
|
||||
}
|
||||
}
|
||||
|
||||
/// This is the direct request to retrieve recent messages from an Open Group so should be retrieved automatically from the `poll()`
|
||||
/// method, if the logic should change then remove the `@available` line and make sure to route the response of this method to
|
||||
/// the `OpenGroupManager` `handleMessages` method (otherwise the logic may not work correctly)
|
||||
@available(*, unavailable, message: "Avoid using this directly, use the pre-build `poll()` method instead")
|
||||
public static func messagesSince(seqNo: Int64, in roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, [Message])> {
|
||||
// TODO: Recent vs. Since?.
|
||||
let request: Request = Request(
|
||||
server: server,
|
||||
endpoint: .roomMessagesSince(roomToken, seqNo: seqNo)
|
||||
// TODO: Limit?.
|
||||
// queryParameters: [
|
||||
// .fromServerId: storage.getLastMessageServerID(for: room, on: server).map { String($0) }
|
||||
// ].compactMapValues { $0 }
|
||||
// queryParameters: [ .limit: 50 ]
|
||||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: [Message].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
|
||||
.then(on: OpenGroupAPI.workQueue) { responseInfo, messages -> Promise<(OnionRequestResponseInfoType, [Message])> in
|
||||
process(messages: messages, for: roomToken, on: server, using: dependencies)
|
||||
.map { processedMessages in (responseInfo, processedMessages) }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Pinning
|
||||
|
@ -360,45 +379,6 @@ public final class OpenGroupAPI: NSObject {
|
|||
|
||||
// MARK: - Files
|
||||
|
||||
// TODO: Shift this logic to the `OpenGroupManager` (makes more sense since it's not API logic).
|
||||
public static func roomImage(_ fileId: Int64, for roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<Data> {
|
||||
// Normally the image for a given group is stored with the group thread, so it's only
|
||||
// fetched once. However, on the join open group screen we show images for groups the
|
||||
// user * hasn't * joined yet. We don't want to re-fetch these images every time the
|
||||
// user opens the app because that could slow the app down or be data-intensive. So
|
||||
// instead we assume that these images don't change that often and just fetch them once
|
||||
// a week. We also assume that they're all fetched at the same time as well, so that
|
||||
// we only need to maintain one date in user defaults. On top of all of this we also
|
||||
// don't double up on fetch requests by storing the existing request as a promise if
|
||||
// there is one.
|
||||
let lastOpenGroupImageUpdate: Date? = UserDefaults.standard[.lastOpenGroupImageUpdate]
|
||||
let now: Date = dependencies.date
|
||||
let timeSinceLastUpdate: TimeInterval = (given(lastOpenGroupImageUpdate) { now.timeIntervalSince($0) } ?? .greatestFiniteMagnitude)
|
||||
let updateInterval: TimeInterval = (7 * 24 * 60 * 60)
|
||||
|
||||
if let data = dependencies.storage.getOpenGroupImage(for: roomToken, on: server), server == defaultServer, timeSinceLastUpdate < updateInterval {
|
||||
return Promise.value(data)
|
||||
}
|
||||
|
||||
if let promise = groupImagePromises["\(server).\(roomToken)"] {
|
||||
return promise
|
||||
}
|
||||
|
||||
let promise: Promise<Data> = downloadFile(fileId, from: roomToken, on: server, using: dependencies)
|
||||
.map { _, data in data }
|
||||
_ = promise.done(on: OpenGroupAPI.workQueue) { imageData in
|
||||
if server == defaultServer {
|
||||
dependencies.storage.write { transaction in
|
||||
dependencies.storage.setOpenGroupImage(to: imageData, for: roomToken, on: server, using: transaction)
|
||||
}
|
||||
UserDefaults.standard[.lastOpenGroupImageUpdate] = now
|
||||
}
|
||||
}
|
||||
groupImagePromises["\(server).\(roomToken)"] = promise
|
||||
|
||||
return promise
|
||||
}
|
||||
|
||||
public static func uploadFile(_ bytes: [UInt8], fileName: String? = nil, to roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, FileUploadResponse)> {
|
||||
let request: Request = Request(
|
||||
method: .post,
|
||||
|
@ -475,13 +455,13 @@ public final class OpenGroupAPI: NSObject {
|
|||
|
||||
public static func sendMessageRequest(_ plaintext: Data, to sessionId: String, on server: String, with serverPublicKey: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, [DirectMessage])> {
|
||||
// TODO: Change this to use '.blinded' once it's working
|
||||
guard let signedRequest: (data: Data, signature: Data) = SendMessageRequest.sign(message: plaintext, for: .standard, with: serverPublicKey) else {
|
||||
guard let signedMessage: (data: Data, signature: Data) = sign(message: plaintext, for: .standard, with: serverPublicKey) else {
|
||||
return Promise(error: Error.signingFailed)
|
||||
}
|
||||
|
||||
let requestBody: SendDirectMessageRequest = SendDirectMessageRequest(
|
||||
data: signedRequest.data,
|
||||
signature: signedRequest.signature
|
||||
data: signedMessage.data,
|
||||
signature: signedMessage.signature
|
||||
)
|
||||
|
||||
guard let body: Data = try? JSONEncoder().encode(requestBody) else {
|
||||
|
@ -609,96 +589,24 @@ public final class OpenGroupAPI: NSObject {
|
|||
.decoded(as: UserDeleteMessagesResponse.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
|
||||
}
|
||||
|
||||
// MARK: - Processing
|
||||
// TODO: Move these methods to the OpenGroupManager? (seems odd for them to be in the API).
|
||||
|
||||
private static func process(messages: [Message]?, for room: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<[Message]> {
|
||||
guard let messages: [Message] = messages, !messages.isEmpty else { return Promise.value([]) }
|
||||
|
||||
let seqNo: Int64 = (messages.compactMap { $0.seqNo }.max() ?? 0)
|
||||
let lastMessageSeqNo: Int64 = (dependencies.storage.getLastMessageServerID(for: room, on: server) ?? 0)
|
||||
|
||||
if seqNo > lastMessageSeqNo {
|
||||
let (promise, seal) = Promise<[Message]>.pending()
|
||||
|
||||
dependencies.storage.write(
|
||||
with: { transaction in
|
||||
dependencies.storage.setLastMessageServerID(for: room, on: server, to: seqNo, using: transaction)
|
||||
},
|
||||
completion: {
|
||||
seal.fulfill(messages)
|
||||
}
|
||||
)
|
||||
|
||||
return promise
|
||||
}
|
||||
|
||||
return Promise.value(messages)
|
||||
}
|
||||
|
||||
private static func process(deletions: [Deletion]?, for room: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<[Deletion]> {
|
||||
guard let deletions: [Deletion] = deletions else { return Promise.value([]) }
|
||||
|
||||
let serverID: Int64 = (deletions.compactMap { $0.id }.max() ?? 0)
|
||||
let lastDeletionServerID: Int64 = (dependencies.storage.getLastDeletionServerID(for: room, on: server) ?? 0)
|
||||
|
||||
if serverID > lastDeletionServerID {
|
||||
let (promise, seal) = Promise<[Deletion]>.pending()
|
||||
|
||||
dependencies.storage.write(
|
||||
with: { transaction in
|
||||
dependencies.storage.setLastDeletionServerID(for: room, on: server, to: serverID, using: transaction)
|
||||
},
|
||||
completion: {
|
||||
seal.fulfill(deletions)
|
||||
}
|
||||
)
|
||||
|
||||
return promise
|
||||
}
|
||||
|
||||
return Promise.value(deletions)
|
||||
}
|
||||
|
||||
public static func isUserModerator(_ publicKey: String, for room: String, on server: String) -> Bool {
|
||||
return moderators[server]?[room]?.contains(publicKey) ?? false
|
||||
}
|
||||
|
||||
// MARK: - General
|
||||
|
||||
// TODO: Shift this to the OpenGroupManager? (seems more at place there than in the API)
|
||||
public static func getDefaultRoomsIfNeeded(using dependencies: Dependencies = Dependencies()) {
|
||||
Storage.shared.write(
|
||||
with: { transaction in
|
||||
dependencies.storage.setOpenGroupPublicKey(for: defaultServer, to: defaultServerPublicKey, using: transaction)
|
||||
},
|
||||
completion: {
|
||||
let promise = attempt(maxRetryCount: 8, recoveringOn: DispatchQueue.main) {
|
||||
OpenGroupAPI.rooms(for: defaultServer, using: dependencies)
|
||||
.map { _, data in data }
|
||||
}
|
||||
_ = promise.done(on: OpenGroupAPI.workQueue) { items in
|
||||
items
|
||||
.compactMap { room -> (Int64, String)? in
|
||||
guard let imageId: Int64 = room.imageId else { return nil}
|
||||
|
||||
return (imageId, room.token)
|
||||
}
|
||||
.forEach { imageId, roomToken in
|
||||
roomImage(imageId, for: roomToken, on: defaultServer, using: dependencies)
|
||||
.retainUntilComplete()
|
||||
}
|
||||
}
|
||||
promise.catch(on: OpenGroupAPI.workQueue) { _ in
|
||||
OpenGroupAPI.defaultRoomsPromise = nil
|
||||
}
|
||||
defaultRoomsPromise = promise
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Authentication
|
||||
|
||||
public static func sign(message: Data, for idType: IdPrefix, with publicKey: String, using dependencies: Dependencies = Dependencies()) -> (data: Data, signature: Data)? {
|
||||
guard let userKeyPair: ECKeyPair = dependencies.storage.getUserKeyPair() else {
|
||||
return nil
|
||||
}
|
||||
guard let targetKeyPair: ECKeyPair = try? userKeyPair.convert(to: idType, with: publicKey) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let signature = try? Ed25519.sign(message, with: targetKeyPair) else {
|
||||
SNLog("Failed to sign open group message.")
|
||||
return nil
|
||||
}
|
||||
|
||||
return (message, signature)
|
||||
}
|
||||
|
||||
private static func sign(_ request: URLRequest, with publicKey: String, using dependencies: Dependencies = Dependencies()) -> URLRequest? {
|
||||
guard let url: URL = request.url else { return nil }
|
||||
|
||||
|
@ -812,7 +720,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
return Promise.value(authToken)
|
||||
}
|
||||
|
||||
if let authTokenPromise: Promise<String> = authTokenPromises.wrappedValue["\(server).\(room)"] {
|
||||
if let authTokenPromise: Promise<String> = legacyAuthTokenPromises.wrappedValue["\(server).\(room)"] {
|
||||
return authTokenPromise
|
||||
}
|
||||
|
||||
|
@ -830,13 +738,13 @@ public final class OpenGroupAPI: NSObject {
|
|||
|
||||
promise
|
||||
.done(on: OpenGroupAPI.workQueue) { _ in
|
||||
authTokenPromises.wrappedValue["\(server).\(room)"] = nil
|
||||
legacyAuthTokenPromises.wrappedValue["\(server).\(room)"] = nil
|
||||
}
|
||||
.catch(on: OpenGroupAPI.workQueue) { _ in
|
||||
authTokenPromises.wrappedValue["\(server).\(room)"] = nil
|
||||
legacyAuthTokenPromises.wrappedValue["\(server).\(room)"] = nil
|
||||
}
|
||||
|
||||
authTokenPromises.wrappedValue["\(server).\(room)"] = promise
|
||||
legacyAuthTokenPromises.wrappedValue["\(server).\(room)"] = promise
|
||||
return promise
|
||||
}
|
||||
|
||||
|
@ -859,7 +767,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
|
||||
return legacySend(request).map(on: OpenGroupAPI.workQueue) { _, maybeData in
|
||||
guard let data: Data = maybeData else { throw Error.parsingFailed }
|
||||
let response = try data.decoded(as: AuthTokenResponse.self, customError: Error.parsingFailed)
|
||||
let response = try data.decoded(as: LegacyAuthTokenResponse.self, customError: Error.parsingFailed)
|
||||
let symmetricKey = try AESGCM.generateSymmetricKey(x25519PublicKey: response.challenge.ephemeralPublicKey, x25519PrivateKey: userKeyPair.privateKey)
|
||||
|
||||
guard let tokenAsData = try? AESGCM.decrypt(response.challenge.ciphertext, with: symmetricKey) else {
|
||||
|
@ -872,7 +780,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
|
||||
@available(*, deprecated, message: "Use request signing instead")
|
||||
public static func legacyClaimAuthToken(_ authToken: String, for room: String, on server: String) -> Promise<String> {
|
||||
let requestBody: PublicKeyBody = PublicKeyBody(publicKey: getUserHexEncodedPublicKey())
|
||||
let requestBody: LegacyPublicKeyBody = LegacyPublicKeyBody(publicKey: getUserHexEncodedPublicKey())
|
||||
|
||||
guard let body: Data = try? JSONEncoder().encode(requestBody) else {
|
||||
return Promise(error: HTTP.Error.invalidJSON)
|
||||
|
@ -926,9 +834,9 @@ public final class OpenGroupAPI: NSObject {
|
|||
|
||||
hasPerformedInitialPoll[server] = true
|
||||
|
||||
if !hasUpdatedLastOpenDate {
|
||||
if !legacyHasUpdatedLastOpenDate {
|
||||
UserDefaults.standard[.lastOpen] = Date()
|
||||
hasUpdatedLastOpenDate = true
|
||||
legacyHasUpdatedLastOpenDate = true
|
||||
}
|
||||
|
||||
for room in rooms {
|
||||
|
@ -985,7 +893,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
|
||||
return when(
|
||||
fulfilled: response.results
|
||||
.compactMap { (result: LegacyCompactPollResponse.Result) -> Promise<[Deletion]>? in
|
||||
.compactMap { (result: LegacyCompactPollResponse.Result) -> Promise<[LegacyDeletion]>? in
|
||||
// A 401 means that we didn't provide a (valid) auth token for a route that
|
||||
// required one. We use this as an indication that the token we're using has
|
||||
// expired. Note that a 403 has a different meaning; it means that we provided
|
||||
|
@ -1000,7 +908,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
}
|
||||
|
||||
return legacyProcess(messages: result.messages, for: result.room, on: server)
|
||||
.then(on: OpenGroupAPI.workQueue) { _ -> Promise<[Deletion]> in
|
||||
.then(on: OpenGroupAPI.workQueue) { _ -> Promise<[LegacyDeletion]> in
|
||||
legacyProcess(deletions: result.deletions, for: result.room, on: server)
|
||||
}
|
||||
}
|
||||
|
@ -1023,7 +931,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
items.forEach { legacyGetGroupImage(for: $0.id, on: defaultServer).retainUntilComplete() }
|
||||
}
|
||||
promise.catch(on: OpenGroupAPI.workQueue) { _ in
|
||||
OpenGroupAPI.defaultRoomsPromise = nil
|
||||
OpenGroupAPI.legacyDefaultRoomsPromise = nil
|
||||
}
|
||||
legacyDefaultRoomsPromise = promise
|
||||
}
|
||||
|
@ -1085,7 +993,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
return Promise.value(data)
|
||||
}
|
||||
|
||||
if let promise = groupImagePromises["\(server).\(room)"] {
|
||||
if let promise = legacyGroupImagePromises["\(server).\(room)"] {
|
||||
return promise
|
||||
}
|
||||
|
||||
|
@ -1109,7 +1017,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
|
||||
return response.data
|
||||
}
|
||||
groupImagePromises["\(server).\(room)"] = promise
|
||||
legacyGroupImagePromises["\(server).\(room)"] = promise
|
||||
|
||||
return promise
|
||||
}
|
||||
|
@ -1125,7 +1033,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
return legacySend(request)
|
||||
.map(on: OpenGroupAPI.workQueue) { _, maybeData in
|
||||
guard let data: Data = maybeData else { throw Error.parsingFailed }
|
||||
let response: MemberCountResponse = try data.decoded(as: MemberCountResponse.self, customError: Error.parsingFailed)
|
||||
let response: LegacyMemberCountResponse = try data.decoded(as: LegacyMemberCountResponse.self, customError: Error.parsingFailed)
|
||||
|
||||
let storage = SNMessagingKitConfiguration.shared.storage
|
||||
storage.write { transaction in
|
||||
|
@ -1171,7 +1079,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
// MARK: - Legacy Message Sending & Receiving
|
||||
|
||||
@available(*, deprecated, message: "Use send(_:to:on:whisperTo:whisperMods:with:) instead")
|
||||
public static func legacySend(_ message: OpenGroupMessageV2, to room: String, on server: String, with publicKey: String) -> Promise<OpenGroupMessageV2> {
|
||||
public static func legacySend(_ message: LegacyOpenGroupMessageV2, to room: String, on server: String, with publicKey: String) -> Promise<LegacyOpenGroupMessageV2> {
|
||||
guard let signedMessage = message.sign(with: publicKey) else { return Promise(error: Error.signingFailed) }
|
||||
guard let body: Data = try? JSONEncoder().encode(signedMessage) else {
|
||||
return Promise(error: Error.parsingFailed)
|
||||
|
@ -1180,7 +1088,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
|
||||
return legacySend(request).map(on: OpenGroupAPI.workQueue) { _, maybeData in
|
||||
guard let data: Data = maybeData else { throw Error.parsingFailed }
|
||||
let message: OpenGroupMessageV2 = try data.decoded(as: OpenGroupMessageV2.self, customError: Error.parsingFailed)
|
||||
let message: LegacyOpenGroupMessageV2 = try data.decoded(as: LegacyOpenGroupMessageV2.self, customError: Error.parsingFailed)
|
||||
Storage.shared.write { transaction in
|
||||
Storage.shared.addReceivedMessageTimestamp(message.sentTimestamp, using: transaction)
|
||||
}
|
||||
|
@ -1189,7 +1097,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
}
|
||||
|
||||
@available(*, deprecated, message: "Use recentMessages(in:on:) or messagesSince(seqNo:in:on:) instead")
|
||||
public static func legacyGetMessages(for room: String, on server: String) -> Promise<[OpenGroupMessageV2]> {
|
||||
public static func legacyGetMessages(for room: String, on server: String) -> Promise<[LegacyOpenGroupMessageV2]> {
|
||||
let storage = SNMessagingKitConfiguration.shared.storage
|
||||
let request: Request = Request(
|
||||
server: server,
|
||||
|
@ -1200,9 +1108,9 @@ public final class OpenGroupAPI: NSObject {
|
|||
].compactMapValues { $0 }
|
||||
)
|
||||
|
||||
return legacySend(request).then(on: OpenGroupAPI.workQueue) { _, maybeData -> Promise<[OpenGroupMessageV2]> in
|
||||
return legacySend(request).then(on: OpenGroupAPI.workQueue) { _, maybeData -> Promise<[LegacyOpenGroupMessageV2]> in
|
||||
guard let data: Data = maybeData else { throw Error.parsingFailed }
|
||||
let messages: [OpenGroupMessageV2] = try data.decoded(as: [OpenGroupMessageV2].self, customError: Error.parsingFailed)
|
||||
let messages: [LegacyOpenGroupMessageV2] = try data.decoded(as: [LegacyOpenGroupMessageV2].self, customError: Error.parsingFailed)
|
||||
|
||||
return legacyProcess(messages: messages, for: room, on: server)
|
||||
}
|
||||
|
@ -1224,7 +1132,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
}
|
||||
|
||||
@available(*, deprecated, message: "Use v4 endpoint instead")
|
||||
public static func legacyGetDeletedMessages(for room: String, on server: String) -> Promise<[Deletion]> {
|
||||
public static func legacyGetDeletedMessages(for room: String, on server: String) -> Promise<[LegacyDeletion]> {
|
||||
let storage = SNMessagingKitConfiguration.shared.storage
|
||||
|
||||
let request: Request = Request(
|
||||
|
@ -1236,11 +1144,11 @@ public final class OpenGroupAPI: NSObject {
|
|||
].compactMapValues { $0 }
|
||||
)
|
||||
|
||||
return legacySend(request).then(on: OpenGroupAPI.workQueue) { _, maybeData -> Promise<[Deletion]> in
|
||||
return legacySend(request).then(on: OpenGroupAPI.workQueue) { _, maybeData -> Promise<[LegacyDeletion]> in
|
||||
guard let data: Data = maybeData else { throw Error.parsingFailed }
|
||||
let response: DeletedMessagesResponse = try data.decoded(as: DeletedMessagesResponse.self, customError: Error.parsingFailed)
|
||||
let response: LegacyDeletedMessagesResponse = try data.decoded(as: LegacyDeletedMessagesResponse.self, customError: Error.parsingFailed)
|
||||
|
||||
return process(deletions: response.deletions, for: room, on: server)
|
||||
return legacyProcess(deletions: response.deletions, for: room, on: server)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1257,7 +1165,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
return legacySend(request)
|
||||
.map(on: OpenGroupAPI.workQueue) { _, maybeData in
|
||||
guard let data: Data = maybeData else { throw Error.parsingFailed }
|
||||
let response: ModeratorsResponse = try data.decoded(as: ModeratorsResponse.self, customError: Error.parsingFailed)
|
||||
let response: LegacyModeratorsResponse = try data.decoded(as: LegacyModeratorsResponse.self, customError: Error.parsingFailed)
|
||||
|
||||
if var x = self.moderators[server] {
|
||||
x[room] = Set(response.moderators)
|
||||
|
@ -1273,7 +1181,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
|
||||
@available(*, deprecated, message: "Use v4 endpoint instead")
|
||||
public static func legacyBan(_ publicKey: String, from room: String, on server: String) -> Promise<Void> {
|
||||
let requestBody: PublicKeyBody = PublicKeyBody(publicKey: getUserHexEncodedPublicKey())
|
||||
let requestBody: LegacyPublicKeyBody = LegacyPublicKeyBody(publicKey: getUserHexEncodedPublicKey())
|
||||
|
||||
guard let body: Data = try? JSONEncoder().encode(requestBody) else {
|
||||
return Promise(error: HTTP.Error.invalidJSON)
|
||||
|
@ -1292,7 +1200,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
|
||||
@available(*, deprecated, message: "Use v4 endpoint instead")
|
||||
public static func legacyBanAndDeleteAllMessages(_ publicKey: String, from room: String, on server: String) -> Promise<Void> {
|
||||
let requestBody: PublicKeyBody = PublicKeyBody(publicKey: getUserHexEncodedPublicKey())
|
||||
let requestBody: LegacyPublicKeyBody = LegacyPublicKeyBody(publicKey: getUserHexEncodedPublicKey())
|
||||
|
||||
guard let body: Data = try? JSONEncoder().encode(requestBody) else {
|
||||
return Promise(error: HTTP.Error.invalidJSON)
|
||||
|
@ -1325,15 +1233,15 @@ public final class OpenGroupAPI: NSObject {
|
|||
// TODO: Move these methods to the OpenGroupManager? (seems odd for them to be in the API)
|
||||
|
||||
@available(*, deprecated, message: "Use v4 endpoint instead")
|
||||
private static func legacyProcess(messages: [OpenGroupMessageV2]?, for room: String, on server: String) -> Promise<[OpenGroupMessageV2]> {
|
||||
guard let messages: [OpenGroupMessageV2] = messages, !messages.isEmpty else { return Promise.value([]) }
|
||||
private static func legacyProcess(messages: [LegacyOpenGroupMessageV2]?, for room: String, on server: String) -> Promise<[LegacyOpenGroupMessageV2]> {
|
||||
guard let messages: [LegacyOpenGroupMessageV2] = messages, !messages.isEmpty else { return Promise.value([]) }
|
||||
|
||||
let storage = SNMessagingKitConfiguration.shared.storage
|
||||
let serverID: Int64 = (messages.compactMap { $0.serverID }.max() ?? 0)
|
||||
let lastMessageServerID: Int64 = (storage.getLastMessageServerID(for: room, on: server) ?? 0)
|
||||
|
||||
if serverID > lastMessageServerID {
|
||||
let (promise, seal) = Promise<[OpenGroupMessageV2]>.pending()
|
||||
let (promise, seal) = Promise<[LegacyOpenGroupMessageV2]>.pending()
|
||||
|
||||
storage.write(
|
||||
with: { transaction in
|
||||
|
@ -1351,15 +1259,15 @@ public final class OpenGroupAPI: NSObject {
|
|||
}
|
||||
|
||||
@available(*, deprecated, message: "Use v4 endpoint instead")
|
||||
private static func legacyProcess(deletions: [Deletion]?, for room: String, on server: String) -> Promise<[Deletion]> {
|
||||
guard let deletions: [Deletion] = deletions else { return Promise.value([]) }
|
||||
private static func legacyProcess(deletions: [LegacyDeletion]?, for room: String, on server: String) -> Promise<[LegacyDeletion]> {
|
||||
guard let deletions: [LegacyDeletion] = deletions else { return Promise.value([]) }
|
||||
|
||||
let storage = SNMessagingKitConfiguration.shared.storage
|
||||
let serverID: Int64 = (deletions.compactMap { $0.id }.max() ?? 0)
|
||||
let lastDeletionServerID: Int64 = (storage.getLastDeletionServerID(for: room, on: server) ?? 0)
|
||||
|
||||
if serverID > lastDeletionServerID {
|
||||
let (promise, seal) = Promise<[Deletion]>.pending()
|
||||
let (promise, seal) = Promise<[LegacyDeletion]>.pending()
|
||||
|
||||
storage.write(
|
||||
with: { transaction in
|
||||
|
|
|
@ -6,8 +6,15 @@ public final class OpenGroupManager: NSObject {
|
|||
|
||||
private var pollers: [String: OpenGroupAPI.Poller] = [:] // One for each server
|
||||
private var isPolling = false
|
||||
|
||||
// MARK: - Cache
|
||||
|
||||
public static var defaultRoomsPromise: Promise<[OpenGroupAPI.Room]>?
|
||||
private static var groupImagePromises: [String: Promise<Data>] = [:]
|
||||
private static var moderators: [String: [String: Set<String>]] = [:] // Server URL to room ID to set of moderator IDs
|
||||
|
||||
// MARK: - Polling
|
||||
|
||||
@objc public func startPolling() {
|
||||
guard !isPolling else { return }
|
||||
|
||||
|
@ -30,13 +37,13 @@ public final class OpenGroupManager: NSObject {
|
|||
|
||||
// MARK: - Adding & Removing
|
||||
|
||||
public func add(room: String, server: String, publicKey: String, using transaction: Any) -> Promise<Void> {
|
||||
public func add(roomToken: String, server: String, publicKey: String, using transaction: Any) -> Promise<Void> {
|
||||
let storage = Storage.shared
|
||||
|
||||
// Clear any existing data if needed
|
||||
storage.removeLastMessageServerID(for: room, on: server, using: transaction)
|
||||
storage.removeLastDeletionServerID(for: room, on: server, using: transaction)
|
||||
storage.removeAuthToken(for: room, on: server, using: transaction)
|
||||
storage.removeLastMessageServerID(for: roomToken, on: server, using: transaction)
|
||||
storage.removeLastDeletionServerID(for: roomToken, on: server, using: transaction)
|
||||
storage.removeAuthToken(for: roomToken, on: server, using: transaction)
|
||||
|
||||
// Store the public key
|
||||
storage.setOpenGroupPublicKey(for: server, to: publicKey, using: transaction)
|
||||
|
@ -45,111 +52,17 @@ public final class OpenGroupManager: NSObject {
|
|||
let transaction = transaction as! YapDatabaseReadWriteTransaction
|
||||
|
||||
transaction.addCompletionQueue(DispatchQueue.global(qos: .userInitiated)) {
|
||||
// Get the group info
|
||||
// TODO: Remove this legacy method
|
||||
// OpenGroupAPI.legacyGetRoomInfo(for: room, on: server).done(on: DispatchQueue.global(qos: .userInitiated)) { info in
|
||||
// // Create the open group model and the thread
|
||||
// let openGroup = OpenGroupV2(server: server, room: room, name: info.name, publicKey: publicKey, imageID: info.imageID)
|
||||
// let groupID = LKGroupUtilities.getEncodedOpenGroupIDAsData(openGroup.id)
|
||||
// let model = TSGroupModel(title: openGroup.name, memberIds: [ getUserHexEncodedPublicKey() ], image: nil, groupId: groupID, groupType: .openGroup, adminIds: [])
|
||||
// // Store everything
|
||||
// storage.write(with: { transaction in
|
||||
// let transaction = transaction as! YapDatabaseReadWriteTransaction
|
||||
// let thread = TSGroupThread.getOrCreateThread(with: model, transaction: transaction)
|
||||
// thread.shouldBeVisible = true
|
||||
// thread.save(with: transaction)
|
||||
// storage.setV2OpenGroup(openGroup, for: thread.uniqueId!, using: transaction)
|
||||
// }, completion: {
|
||||
// // Start the poller if needed
|
||||
// if OpenGroupManager.shared.pollers[server] == nil {
|
||||
// let poller = OpenGroupPollerV2(for: server)
|
||||
// poller.startIfNeeded()
|
||||
// OpenGroupManager.shared.pollers[server] = poller
|
||||
// }
|
||||
// // Fetch the group image
|
||||
// OpenGroupAPI.legacyGetGroupImage(for: room, on: server).done(on: DispatchQueue.global(qos: .userInitiated)) { data in
|
||||
// storage.write { transaction in
|
||||
// // Update the thread
|
||||
// let transaction = transaction as! YapDatabaseReadWriteTransaction
|
||||
// let thread = TSGroupThread.getOrCreateThread(with: model, transaction: transaction)
|
||||
// thread.groupModel.groupImage = UIImage(data: data)
|
||||
// thread.save(with: transaction)
|
||||
// }
|
||||
// }.retainUntilComplete()
|
||||
// // Finish
|
||||
// seal.fulfill(())
|
||||
// })
|
||||
// }.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in
|
||||
// seal.reject(error)
|
||||
// }
|
||||
|
||||
OpenGroupAPI.room(for: room, on: server)
|
||||
OpenGroupAPI.room(for: roomToken, on: server)
|
||||
.done(on: DispatchQueue.global(qos: .userInitiated)) { _, room in
|
||||
// Create the open group model and the thread
|
||||
let openGroup: OpenGroupV2 = OpenGroupV2(
|
||||
server: server,
|
||||
room: room.token,
|
||||
name: room.name,
|
||||
OpenGroupManager.handleRoom(
|
||||
room,
|
||||
publicKey: publicKey,
|
||||
imageID: room.imageId.map { "\($0)" } // TODO: Update this?
|
||||
)
|
||||
|
||||
let groupID: Data = LKGroupUtilities.getEncodedOpenGroupIDAsData(openGroup.id)
|
||||
let model: TSGroupModel = TSGroupModel(
|
||||
title: openGroup.name,
|
||||
memberIds: [ getUserHexEncodedPublicKey() ],
|
||||
image: nil,
|
||||
groupId: groupID,
|
||||
groupType: .openGroup,
|
||||
adminIds: [] // TODO: This is part of the 'room' object
|
||||
)
|
||||
|
||||
// Store everything
|
||||
storage.write(
|
||||
with: { transaction in
|
||||
let transaction = transaction as! YapDatabaseReadWriteTransaction
|
||||
let thread = TSGroupThread.getOrCreateThread(with: model, transaction: transaction)
|
||||
thread.shouldBeVisible = true
|
||||
thread.save(with: transaction)
|
||||
storage.setV2OpenGroup(openGroup, for: thread.uniqueId!, using: transaction)
|
||||
},
|
||||
completion: {
|
||||
// Start the poller if needed
|
||||
if OpenGroupManager.shared.pollers[server] == nil {
|
||||
let poller = OpenGroupAPI.Poller(for: server)
|
||||
poller.startIfNeeded()
|
||||
OpenGroupManager.shared.pollers[server] = poller
|
||||
}
|
||||
|
||||
// Fetch the group image (if there is one)
|
||||
// TODO: Need to test this.
|
||||
// TODO: Clean this up (can we avoid the if/else with fancy promise wrangling?).
|
||||
if let imageId: Int64 = room.imageId {
|
||||
OpenGroupAPI.roomImage(imageId, for: room.token, on: server)
|
||||
.done(on: DispatchQueue.global(qos: .userInitiated)) { data in
|
||||
storage.write { transaction in
|
||||
// Update the thread
|
||||
let transaction = transaction as! YapDatabaseReadWriteTransaction
|
||||
let thread = TSGroupThread.getOrCreateThread(with: model, transaction: transaction)
|
||||
thread.groupModel.groupImage = UIImage(data: data)
|
||||
thread.save(with: transaction)
|
||||
}
|
||||
}
|
||||
.retainUntilComplete()
|
||||
}
|
||||
else {
|
||||
storage.write { transaction in
|
||||
// Update the thread
|
||||
let transaction = transaction as! YapDatabaseReadWriteTransaction
|
||||
let thread = TSGroupThread.getOrCreateThread(with: model, transaction: transaction)
|
||||
thread.save(with: transaction)
|
||||
}
|
||||
}
|
||||
|
||||
// Finish
|
||||
seal.fulfill(())
|
||||
}
|
||||
)
|
||||
for: roomToken,
|
||||
on: server,
|
||||
isBackgroundPoll: false
|
||||
) {
|
||||
seal.fulfill(())
|
||||
}
|
||||
}
|
||||
.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in
|
||||
seal.reject(error)
|
||||
|
@ -192,7 +105,279 @@ public final class OpenGroupManager: NSObject {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: Convenience
|
||||
// MARK: - Response Processing
|
||||
|
||||
internal static func handleMessages(
|
||||
_ messages: [OpenGroupAPI.Message],
|
||||
for roomToken: String,
|
||||
on server: String,
|
||||
isBackgroundPoll: Bool,
|
||||
using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()
|
||||
) {
|
||||
// Sorting the messages by server ID before importing them fixes an issue where messages that quote older messages can't find those older messages
|
||||
let openGroupID = "\(server).\(roomToken)"
|
||||
let sortedMessages: [OpenGroupAPI.Message] = messages
|
||||
.sorted { lhs, rhs in lhs.seqNo < rhs.seqNo }
|
||||
let seqNo: Int64 = (sortedMessages.last?.seqNo ?? 0)
|
||||
let lastMessageSeqNo: Int64 = (dependencies.storage.getLastMessageServerID(for: roomToken, on: server) ?? 0)
|
||||
|
||||
dependencies.storage.write { transaction in
|
||||
var messageServerIDsToRemove: [UInt64] = []
|
||||
|
||||
// Update the 'lastMessageServerId' value if we've gotten a newer message
|
||||
if seqNo > lastMessageSeqNo {
|
||||
dependencies.storage.setLastMessageServerID(for: roomToken, on: server, to: seqNo, using: transaction)
|
||||
}
|
||||
|
||||
// Process the messages
|
||||
sortedMessages.forEach { message in
|
||||
guard let base64EncodedString: String = message.base64EncodedData, let data = Data(base64Encoded: base64EncodedString), let sender: String = message.sender else {
|
||||
// A message with no data has been deleted so add it to the list to remove
|
||||
messageServerIDsToRemove.append(UInt64(message.seqNo))
|
||||
return
|
||||
}
|
||||
|
||||
let envelope = SNProtoEnvelope.builder(type: .sessionMessage, timestamp: UInt64(floor(message.posted)))
|
||||
envelope.setContent(data)
|
||||
envelope.setSource(sender)
|
||||
|
||||
do {
|
||||
let data = try envelope.buildSerializedData()
|
||||
let (message, proto) = try MessageReceiver.parse(data, openGroupMessageServerID: UInt64(message.seqNo), isRetry: false, using: transaction)
|
||||
try MessageReceiver.handle(message, associatedWithProto: proto, openGroupID: openGroupID, isBackgroundPoll: isBackgroundPoll, using: transaction)
|
||||
}
|
||||
catch {
|
||||
SNLog("Couldn't receive open group message due to error: \(error).")
|
||||
}
|
||||
}
|
||||
|
||||
// Handle any deletions that are needed
|
||||
guard !messageServerIDsToRemove.isEmpty else { return }
|
||||
guard let transaction: YapDatabaseReadWriteTransaction = transaction as? YapDatabaseReadWriteTransaction else { return }
|
||||
guard let threadID = dependencies.storage.v2GetThreadID(for: openGroupID), let thread = TSGroupThread.fetch(uniqueId: threadID, transaction: transaction) else {
|
||||
return
|
||||
}
|
||||
|
||||
var messagesToRemove: [TSMessage] = []
|
||||
|
||||
thread.enumerateInteractions(with: transaction) { interaction, stop in
|
||||
guard let message: TSMessage = interaction as? TSMessage, messageServerIDsToRemove.contains(message.openGroupServerMessageID) else { return }
|
||||
messagesToRemove.append(message)
|
||||
}
|
||||
|
||||
messagesToRemove.forEach { $0.remove(with: transaction) }
|
||||
}
|
||||
}
|
||||
|
||||
internal static func handleRoom(
|
||||
_ room: OpenGroupAPI.Room,
|
||||
publicKey: String,
|
||||
for roomToken: String,
|
||||
on server: String,
|
||||
isBackgroundPoll: Bool,
|
||||
using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies(),
|
||||
completion: (() -> ())? = nil
|
||||
) {
|
||||
OpenGroupManager.handlePollInfo(
|
||||
OpenGroupAPI.RoomPollInfo(room: room),
|
||||
publicKey: publicKey,
|
||||
for: roomToken,
|
||||
on: server,
|
||||
isBackgroundPoll: isBackgroundPoll,
|
||||
using: dependencies,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
internal static func handlePollInfo(
|
||||
_ pollInfo: OpenGroupAPI.RoomPollInfo,
|
||||
publicKey maybePublicKey: String?,
|
||||
for roomToken: String,
|
||||
on server: String,
|
||||
isBackgroundPoll: Bool,
|
||||
using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies(),
|
||||
completion: (() -> ())? = nil
|
||||
) {
|
||||
// Create the open group model and get or create the thread
|
||||
let groupId: Data = LKGroupUtilities.getEncodedOpenGroupIDAsData("\(server).\(roomToken)")
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey()
|
||||
let initialModel: TSGroupModel = TSGroupModel(
|
||||
title: pollInfo.name,
|
||||
memberIds: [ userPublicKey ],
|
||||
image: nil,
|
||||
groupId: groupId,
|
||||
groupType: .openGroup,
|
||||
adminIds: (pollInfo.admins ?? [])
|
||||
)
|
||||
|
||||
// Store/Update everything
|
||||
dependencies.storage.write(
|
||||
with: { transaction in
|
||||
let transaction = transaction as! YapDatabaseReadWriteTransaction
|
||||
let thread = TSGroupThread.getOrCreateThread(with: initialModel, transaction: transaction)
|
||||
let existingOpenGroup: OpenGroupV2? = thread.uniqueId.flatMap { uniqueId -> OpenGroupV2? in
|
||||
dependencies.storage.getV2OpenGroup(for: uniqueId)
|
||||
}
|
||||
|
||||
guard let threadUniqueId: String = thread.uniqueId else { return }
|
||||
guard let publicKey: String = (maybePublicKey ?? existingOpenGroup?.publicKey) else { return }
|
||||
|
||||
let updatedModel: TSGroupModel = TSGroupModel(
|
||||
title: (pollInfo.name ?? thread.groupModel.groupName),
|
||||
memberIds: Array(Set(thread.groupModel.groupMemberIds).inserting(userPublicKey)),
|
||||
image: thread.groupModel.groupImage,
|
||||
groupId: groupId,
|
||||
groupType: .openGroup,
|
||||
adminIds: (pollInfo.admins ?? thread.groupModel.groupAdminIds)
|
||||
)
|
||||
let updatedOpenGroup: OpenGroupV2 = OpenGroupV2(
|
||||
server: server,
|
||||
room: (pollInfo.token ?? roomToken),
|
||||
publicKey: publicKey,
|
||||
name: (pollInfo.name ?? thread.name()),
|
||||
groupDescription: (pollInfo.description ?? existingOpenGroup?.description),
|
||||
imageID: (pollInfo.imageId.map { "\($0)" } ?? existingOpenGroup?.imageID),
|
||||
infoUpdates: ((pollInfo.infoUpdates ?? existingOpenGroup?.infoUpdates) ?? 0)
|
||||
)
|
||||
let existingUserCount: UInt64? = dependencies.storage.getUserCount(forV2OpenGroupWithID: updatedOpenGroup.id)
|
||||
|
||||
// - Thread changes
|
||||
thread.shouldBeVisible = true
|
||||
thread.groupModel = updatedModel
|
||||
thread.save(with: transaction)
|
||||
|
||||
// - Open Group changes
|
||||
dependencies.storage.setV2OpenGroup(updatedOpenGroup, for: threadUniqueId, using: transaction)
|
||||
|
||||
// - User Count
|
||||
dependencies.storage.setUserCount(
|
||||
to: ((pollInfo.activeUsers.map { UInt64($0) } ?? existingUserCount) ?? 0),
|
||||
forV2OpenGroupWithID: updatedOpenGroup.id,
|
||||
using: transaction
|
||||
)
|
||||
},
|
||||
completion: {
|
||||
// Start the poller if needed
|
||||
if OpenGroupManager.shared.pollers[server] == nil {
|
||||
OpenGroupManager.shared.pollers[server] = OpenGroupAPI.Poller(for: server)
|
||||
OpenGroupManager.shared.pollers[server]?.startIfNeeded()
|
||||
}
|
||||
|
||||
// - Moderators
|
||||
if let moderators: [String] = pollInfo.moderators {
|
||||
OpenGroupManager.moderators[server] = (OpenGroupManager.moderators[server] ?? [:])
|
||||
.setting(roomToken, Set(moderators))
|
||||
}
|
||||
|
||||
// - Room image (if there is one)
|
||||
if let imageId: Int64 = pollInfo.imageId {
|
||||
OpenGroupManager.roomImage(imageId, for: roomToken, on: server)
|
||||
.done(on: DispatchQueue.global(qos: .userInitiated)) { data in
|
||||
dependencies.storage.write { transaction in
|
||||
// Update the thread
|
||||
let transaction = transaction as! YapDatabaseReadWriteTransaction
|
||||
let thread = TSGroupThread.getOrCreateThread(with: initialModel, transaction: transaction)
|
||||
thread.groupModel.groupImage = UIImage(data: data)
|
||||
thread.save(with: transaction)
|
||||
}
|
||||
}
|
||||
.retainUntilComplete()
|
||||
}
|
||||
|
||||
// Finish
|
||||
completion?()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Convenience
|
||||
|
||||
@objc(isUserModerator:forRoom:onServer:)
|
||||
public static func isUserModerator(_ publicKey: String, for room: String, on server: String) -> Bool {
|
||||
return (OpenGroupManager.moderators[server]?[room]?.contains(publicKey) ?? false)
|
||||
}
|
||||
|
||||
public static func getDefaultRoomsIfNeeded(using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()) {
|
||||
// Note: If we already have a 'defaultRoomsPromise' then there is no need to get it again
|
||||
guard OpenGroupManager.defaultRoomsPromise == nil else { return }
|
||||
|
||||
dependencies.storage.write(
|
||||
with: { transaction in
|
||||
dependencies.storage.setOpenGroupPublicKey(
|
||||
for: OpenGroupAPI.defaultServer,
|
||||
to: OpenGroupAPI.defaultServerPublicKey,
|
||||
using: transaction
|
||||
)
|
||||
},
|
||||
completion: {
|
||||
OpenGroupManager.defaultRoomsPromise = attempt(maxRetryCount: 8, recoveringOn: DispatchQueue.main) {
|
||||
OpenGroupAPI.rooms(for: OpenGroupAPI.defaultServer, using: dependencies)
|
||||
.map { _, data in data }
|
||||
}
|
||||
OpenGroupManager.defaultRoomsPromise?
|
||||
.done(on: OpenGroupAPI.workQueue) { items in
|
||||
items
|
||||
.compactMap { room -> (Int64, String)? in
|
||||
guard let imageId: Int64 = room.imageId else { return nil}
|
||||
|
||||
return (imageId, room.token)
|
||||
}
|
||||
.forEach { imageId, roomToken in
|
||||
roomImage(imageId, for: roomToken, on: OpenGroupAPI.defaultServer, using: dependencies)
|
||||
.retainUntilComplete()
|
||||
}
|
||||
}
|
||||
.catch(on: OpenGroupAPI.workQueue) { _ in
|
||||
OpenGroupManager.defaultRoomsPromise = nil
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
public static func roomImage(
|
||||
_ fileId: Int64,
|
||||
for roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()
|
||||
) -> Promise<Data> {
|
||||
// Normally the image for a given group is stored with the group thread, so it's only
|
||||
// fetched once. However, on the join open group screen we show images for groups the
|
||||
// user * hasn't * joined yet. We don't want to re-fetch these images every time the
|
||||
// user opens the app because that could slow the app down or be data-intensive. So
|
||||
// instead we assume that these images don't change that often and just fetch them once
|
||||
// a week. We also assume that they're all fetched at the same time as well, so that
|
||||
// we only need to maintain one date in user defaults. On top of all of this we also
|
||||
// don't double up on fetch requests by storing the existing request as a promise if
|
||||
// there is one.
|
||||
let lastOpenGroupImageUpdate: Date? = UserDefaults.standard[.lastOpenGroupImageUpdate]
|
||||
let now: Date = dependencies.date
|
||||
let timeSinceLastUpdate: TimeInterval = (lastOpenGroupImageUpdate.map { now.timeIntervalSince($0) } ?? .greatestFiniteMagnitude)
|
||||
let updateInterval: TimeInterval = (7 * 24 * 60 * 60)
|
||||
|
||||
if let data = dependencies.storage.getOpenGroupImage(for: roomToken, on: server), server == OpenGroupAPI.defaultServer, timeSinceLastUpdate < updateInterval {
|
||||
return Promise.value(data)
|
||||
}
|
||||
|
||||
if let promise = OpenGroupManager.groupImagePromises["\(server).\(roomToken)"] {
|
||||
return promise
|
||||
}
|
||||
|
||||
let promise: Promise<Data> = OpenGroupAPI
|
||||
.downloadFile(fileId, from: roomToken, on: server, using: dependencies)
|
||||
.map { _, data in data }
|
||||
_ = promise.done(on: OpenGroupAPI.workQueue) { imageData in
|
||||
if server == OpenGroupAPI.defaultServer {
|
||||
dependencies.storage.write { transaction in
|
||||
dependencies.storage.setOpenGroupImage(to: imageData, for: roomToken, on: server, using: transaction)
|
||||
}
|
||||
UserDefaults.standard[.lastOpenGroupImageUpdate] = now
|
||||
}
|
||||
}
|
||||
OpenGroupManager.groupImagePromises["\(server).\(roomToken)"] = promise
|
||||
|
||||
return promise
|
||||
}
|
||||
|
||||
public static func parseV2OpenGroup(from string: String) -> (room: String, server: String, publicKey: String)? {
|
||||
guard let url = URL(string: string), let host = url.host ?? given(string.split(separator: "/").first, { String($0) }), let query = url.query else { return nil }
|
||||
// Inputs that should work:
|
||||
|
|
|
@ -20,7 +20,7 @@ extension OpenGroupAPI {
|
|||
// Messages
|
||||
|
||||
case roomMessage(String)
|
||||
case roomMessageIndividual(String, String)
|
||||
case roomMessageIndividual(String, id: Int64)
|
||||
case roomMessagesRecent(String)
|
||||
case roomMessagesBefore(String, id: Int64)
|
||||
case roomMessagesSince(String, seqNo: Int64)
|
||||
|
|
|
@ -234,7 +234,7 @@ extension MessageReceiver {
|
|||
// Open groups
|
||||
for openGroupURL in message.openGroups {
|
||||
if let (room, server, publicKey) = OpenGroupManager.parseV2OpenGroup(from: openGroupURL) {
|
||||
OpenGroupManager.shared.add(room: room, server: server, publicKey: publicKey, using: transaction).retainUntilComplete()
|
||||
OpenGroupManager.shared.add(roomToken: room, server: server, publicKey: publicKey, using: transaction).retainUntilComplete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,122 +80,40 @@ extension OpenGroupAPI {
|
|||
}
|
||||
|
||||
private func handlePollResponse(_ response: [OpenGroupAPI.Endpoint: (info: OnionRequestResponseInfoType, data: Codable)], isBackgroundPoll: Bool) {
|
||||
let storage = SNMessagingKitConfiguration.shared.storage
|
||||
|
||||
response.forEach { endpoint, response in
|
||||
switch endpoint {
|
||||
case .roomMessagesRecent(let roomToken), .roomMessagesBefore(let roomToken, _), .roomMessagesSince(let roomToken, _):
|
||||
guard let responseData: [OpenGroupAPI.Message] = response.data as? [OpenGroupAPI.Message] else {
|
||||
//SNLog("Open group polling failed due to error: \(error).")
|
||||
return // TODO: Throw error?
|
||||
SNLog("Open group polling failed due to invalid data.")
|
||||
return
|
||||
}
|
||||
|
||||
handleMessages(responseData, roomToken: roomToken, isBackgroundPoll: isBackgroundPoll, using: storage)
|
||||
OpenGroupManager.handleMessages(
|
||||
responseData,
|
||||
for: roomToken,
|
||||
on: server,
|
||||
isBackgroundPoll: isBackgroundPoll
|
||||
)
|
||||
|
||||
case .roomPollInfo(let roomToken, _):
|
||||
guard let responseData: OpenGroupAPI.RoomPollInfo = response.data as? OpenGroupAPI.RoomPollInfo else {
|
||||
//SNLog("Open group polling failed due to error: \(error).")
|
||||
return // TODO: Throw error?
|
||||
SNLog("Open group polling failed due to invalid data.")
|
||||
return
|
||||
}
|
||||
|
||||
handlePollInfo(responseData, roomToken: roomToken, isBackgroundPoll: isBackgroundPoll, using: storage)
|
||||
OpenGroupManager.handlePollInfo(
|
||||
responseData,
|
||||
publicKey: nil,
|
||||
for: roomToken,
|
||||
on: server,
|
||||
isBackgroundPoll: isBackgroundPoll
|
||||
)
|
||||
|
||||
default: break // No custom handling needed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Custom response handling
|
||||
// TODO: Shift this logic to the OpenGroupManager? (seems like the place it should belong?)
|
||||
|
||||
private func handleMessages(_ messages: [OpenGroupAPI.Message], roomToken: String, isBackgroundPoll: Bool, using storage: SessionMessagingKitStorageProtocol) {
|
||||
// Sorting the messages by server ID before importing them fixes an issue where messages that quote older messages can't find those older messages
|
||||
let openGroupID = "\(server).\(roomToken)"
|
||||
let sortedMessages: [OpenGroupAPI.Message] = messages
|
||||
.sorted { lhs, rhs in lhs.seqNo < rhs.seqNo }
|
||||
|
||||
storage.write { transaction in
|
||||
var messageServerIDsToRemove: [UInt64] = []
|
||||
|
||||
sortedMessages.forEach { message in
|
||||
guard let base64EncodedString: String = message.base64EncodedData, let data = Data(base64Encoded: base64EncodedString), let sender: String = message.sender else {
|
||||
// A message with no data has been deleted so add it to the list to remove
|
||||
messageServerIDsToRemove.append(UInt64(message.seqNo))
|
||||
return
|
||||
}
|
||||
|
||||
let envelope = SNProtoEnvelope.builder(type: .sessionMessage, timestamp: UInt64(floor(message.posted)))
|
||||
envelope.setContent(data)
|
||||
envelope.setSource(sender)
|
||||
|
||||
do {
|
||||
let data = try envelope.buildSerializedData()
|
||||
let (message, proto) = try MessageReceiver.parse(data, openGroupMessageServerID: UInt64(message.seqNo), isRetry: false, using: transaction)
|
||||
try MessageReceiver.handle(message, associatedWithProto: proto, openGroupID: openGroupID, isBackgroundPoll: isBackgroundPoll, using: transaction)
|
||||
}
|
||||
catch {
|
||||
SNLog("Couldn't receive open group message due to error: \(error).")
|
||||
}
|
||||
}
|
||||
|
||||
// Handle any deletions that are needed
|
||||
guard !messageServerIDsToRemove.isEmpty else { return }
|
||||
guard let transaction: YapDatabaseReadWriteTransaction = transaction as? YapDatabaseReadWriteTransaction else { return }
|
||||
guard let threadID = storage.v2GetThreadID(for: openGroupID), let thread = TSGroupThread.fetch(uniqueId: threadID, transaction: transaction) else {
|
||||
return
|
||||
}
|
||||
|
||||
var messagesToRemove: [TSMessage] = []
|
||||
|
||||
thread.enumerateInteractions(with: transaction) { interaction, stop in
|
||||
guard let message: TSMessage = interaction as? TSMessage, messageServerIDsToRemove.contains(message.openGroupServerMessageID) else { return }
|
||||
messagesToRemove.append(message)
|
||||
}
|
||||
|
||||
messagesToRemove.forEach { $0.remove(with: transaction) }
|
||||
}
|
||||
}
|
||||
|
||||
private func handlePollInfo(_ pollInfo: OpenGroupAPI.RoomPollInfo, roomToken: String, isBackgroundPoll: Bool, using storage: SessionMessagingKitStorageProtocol) {
|
||||
// TODO: Handle other properties???.
|
||||
|
||||
// public let token: String?
|
||||
// public let created: TimeInterval?
|
||||
// public let name: String?
|
||||
// public let description: String?
|
||||
// public let imageId: Int64?
|
||||
//
|
||||
// public let infoUpdates: Int64?
|
||||
// public let messageSequence: Int64?
|
||||
// public let activeUsers: Int64?
|
||||
// public let activeUsersCutoff: Int64?
|
||||
// public let pinnedMessages: [PinnedMessage]?
|
||||
//
|
||||
// public let admin: Bool?
|
||||
// public let globalAdmin: Bool?
|
||||
// public let admins: [String]?
|
||||
// public let hiddenAdmins: [String]?
|
||||
//
|
||||
// public let moderator: Bool?
|
||||
// public let globalModerator: Bool?
|
||||
// public let moderators: [String]?
|
||||
// public let hiddenModerators: [String]?
|
||||
|
||||
// - Moderators
|
||||
OpenGroupAPI.moderators[server] = (OpenGroupAPI.moderators[server] ?? [:])
|
||||
.setting(roomToken, Set(pollInfo.moderators ?? []))
|
||||
|
||||
// public let read: Bool?
|
||||
// public let defaultRead: Bool?
|
||||
// public let write: Bool?
|
||||
// public let defaultWrite: Bool?
|
||||
// public let upload: Bool?
|
||||
// public let defaultUpload: Bool?
|
||||
//
|
||||
// /// Only populated and different if the `info_updates` counter differs from the provided `info_updated` value
|
||||
// public let details: Room?
|
||||
}
|
||||
|
||||
// MARK: - Legacy Handling
|
||||
|
||||
private func handleCompactPollBody(_ body: OpenGroupAPI.LegacyCompactPollResponse.Result, isBackgroundPoll: Bool) {
|
||||
|
|
|
@ -67,10 +67,6 @@ public protocol SessionMessagingKitStorageProtocol: SessionMessagingKitOpenGroup
|
|||
func setLastDeletionServerID(for room: String, on server: String, to newValue: Int64, using transaction: Any)
|
||||
func removeLastDeletionServerID(for room: String, on server: String, using transaction: Any)
|
||||
|
||||
// MARK: - Open Group Metadata
|
||||
|
||||
func setUserCount(to newValue: UInt64, forV2OpenGroupWithID openGroupID: String, using transaction: Any)
|
||||
|
||||
// MARK: - Message Handling
|
||||
|
||||
func getReceivedMessageTimestamps(using transaction: Any) -> [UInt64]
|
||||
|
|
|
@ -81,9 +81,19 @@ class OpenGroupAPITests: XCTestCase {
|
|||
)
|
||||
|
||||
testStorage.mockData[.allV2OpenGroups] = [
|
||||
"0": OpenGroupV2(server: "testServer", room: "testRoom", name: "Test", publicKey: "7aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d", imageID: nil)
|
||||
"0": OpenGroupV2(
|
||||
server: "testServer",
|
||||
room: "testRoom",
|
||||
publicKey: "7aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d",
|
||||
name: "Test",
|
||||
groupDescription: nil,
|
||||
imageID: nil,
|
||||
infoUpdates: 0
|
||||
)
|
||||
]
|
||||
testStorage.mockData[.openGroupPublicKeys] = [
|
||||
"testServer": "7aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d"
|
||||
]
|
||||
testStorage.mockData[.openGroupPublicKeys] = ["testServer": "7aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d"]
|
||||
|
||||
// Test Private key, not actually used (from here https://www.notion.so/oxen/SOGS-Authentication-dc64cc846cb24b2abbf7dd4bfd74abbb)
|
||||
testStorage.mockData[.userKeyPair] = try! ECKeyPair(
|
||||
|
|
|
@ -13,7 +13,9 @@ class TestStorage: SessionMessagingKitStorageProtocol, Mockable {
|
|||
case allV2OpenGroups
|
||||
case openGroupPublicKeys
|
||||
case userKeyPair
|
||||
case openGroup
|
||||
case openGroupImage
|
||||
case openGroupUserCount
|
||||
}
|
||||
|
||||
typealias Key = DataKey
|
||||
|
@ -71,7 +73,7 @@ class TestStorage: SessionMessagingKitStorageProtocol, Mockable {
|
|||
// MARK: - Open Groups
|
||||
|
||||
func getAllV2OpenGroups() -> [String: OpenGroupV2] { return (mockData[.allV2OpenGroups] as! [String: OpenGroupV2]) }
|
||||
func getV2OpenGroup(for threadID: String) -> OpenGroupV2? { return nil }
|
||||
func getV2OpenGroup(for threadID: String) -> OpenGroupV2? { return (mockData[.openGroup] as? OpenGroupV2) }
|
||||
func v2GetThreadID(for v2OpenGroupID: String) -> String? { return nil }
|
||||
func updateMessageIDCollectionByPruningMessagesWithIDs(_ messageIDs: Set<String>, using transaction: Any) {}
|
||||
|
||||
|
@ -99,10 +101,6 @@ class TestStorage: SessionMessagingKitStorageProtocol, Mockable {
|
|||
func setLastDeletionServerID(for room: String, on server: String, to newValue: Int64, using transaction: Any) {}
|
||||
func removeLastDeletionServerID(for room: String, on server: String, using transaction: Any) {}
|
||||
|
||||
// MARK: - Open Group Metadata
|
||||
|
||||
func setUserCount(to newValue: UInt64, forV2OpenGroupWithID openGroupID: String, using transaction: Any) {}
|
||||
|
||||
// MARK: - Message Handling
|
||||
|
||||
func getReceivedMessageTimestamps(using transaction: Any) -> [UInt64] { return [] }
|
||||
|
@ -118,5 +116,19 @@ class TestStorage: SessionMessagingKitStorageProtocol, Mockable {
|
|||
|
||||
extension TestStorage: SessionMessagingKitOpenGroupStorageProtocol {
|
||||
func getOpenGroupImage(for room: String, on server: String) -> Data? { return (mockData[.openGroupImage] as? Data) }
|
||||
func setOpenGroupImage(to data: Data, for room: String, on server: String, using transaction: Any) {}
|
||||
func setOpenGroupImage(to data: Data, for room: String, on server: String, using transaction: Any) {
|
||||
mockData[.openGroupImage] = data
|
||||
}
|
||||
|
||||
func setV2OpenGroup(_ openGroup: OpenGroupV2, for threadID: String, using transaction: Any) {
|
||||
mockData[.openGroup] = openGroup
|
||||
}
|
||||
|
||||
func getUserCount(forV2OpenGroupWithID openGroupID: String) -> UInt64? {
|
||||
return (mockData[.openGroupUserCount] as? UInt64)
|
||||
}
|
||||
|
||||
func setUserCount(to newValue: UInt64, forV2OpenGroupWithID openGroupID: String, using transaction: Any) {
|
||||
mockData[.openGroupUserCount] = newValue
|
||||
}
|
||||
}
|
||||
|
|
12
SessionUtilitiesKit/General/Set+Utilities.swift
Normal file
12
SessionUtilitiesKit/General/Set+Utilities.swift
Normal 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
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue