File upload working, further code cleanup
Got the updated file upload working Removed the legacy 'room' header Consolidated a number of types between SOGS, FileServer and general requests Updated the OnionRequestAPI to deal with a Data payload (rather than encoding it to a string and then back to data)
This commit is contained in:
parent
8ca00ca578
commit
1c474955de
|
@ -793,6 +793,8 @@
|
|||
FD83B9C527CF3E2A005E1583 /* OpenGroupSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9C427CF3E2A005E1583 /* OpenGroupSpec.swift */; };
|
||||
FD83B9C727CF3F10005E1583 /* CapabilitiesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9C627CF3F10005E1583 /* CapabilitiesSpec.swift */; };
|
||||
FD83B9C927D0487A005E1583 /* SendDirectMessageResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9C827D0487A005E1583 /* SendDirectMessageResponse.swift */; };
|
||||
FD83B9CC27D179BC005E1583 /* FSEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9CB27D179BC005E1583 /* FSEndpoint.swift */; };
|
||||
FD83B9CE27D17A04005E1583 /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9CD27D17A04005E1583 /* Request.swift */; };
|
||||
FD859EF227BF6BA200510D0C /* Data+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF127BF6BA200510D0C /* Data+Utilities.swift */; };
|
||||
FD859EF427C2F49200510D0C /* TestSodium.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF327C2F49200510D0C /* TestSodium.swift */; };
|
||||
FD859EF627C2F52C00510D0C /* TestSign.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF527C2F52C00510D0C /* TestSign.swift */; };
|
||||
|
@ -802,13 +804,12 @@
|
|||
FD859F0027C4691300510D0C /* MockDataGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EFF27C4691300510D0C /* MockDataGenerator.swift */; };
|
||||
FD88BAD927A7439C00BBC442 /* MessageRequestsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD88BAD827A7439C00BBC442 /* MessageRequestsCell.swift */; };
|
||||
FD88BADB27A750F200BBC442 /* MessageRequestsMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD88BADA27A750F200BBC442 /* MessageRequestsMigration.swift */; };
|
||||
FDC4380927B31D4E00C60D73 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4380827B31D4E00C60D73 /* Error.swift */; };
|
||||
FDC4380B27B31D7E00C60D73 /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4380A27B31D7E00C60D73 /* Request.swift */; };
|
||||
FDC4380927B31D4E00C60D73 /* SOGSError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4380827B31D4E00C60D73 /* SOGSError.swift */; };
|
||||
FDC4381527B329CE00C60D73 /* NonceGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381427B329CE00C60D73 /* NonceGenerator.swift */; };
|
||||
FDC4381727B32EC700C60D73 /* Personalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381627B32EC700C60D73 /* Personalization.swift */; };
|
||||
FDC4381A27B34EBA00C60D73 /* LegacyCompactPollBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381927B34EBA00C60D73 /* LegacyCompactPollBody.swift */; };
|
||||
FDC4381C27B354AC00C60D73 /* LegacyPublicKeyBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381B27B354AC00C60D73 /* LegacyPublicKeyBody.swift */; };
|
||||
FDC4382027B36ADC00C60D73 /* Endpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381F27B36ADC00C60D73 /* Endpoint.swift */; };
|
||||
FDC4382027B36ADC00C60D73 /* SOGSEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381F27B36ADC00C60D73 /* SOGSEndpoint.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 */; };
|
||||
|
@ -832,7 +833,7 @@
|
|||
FDC4385D27B4C18900C60D73 /* Room.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4385C27B4C18900C60D73 /* Room.swift */; };
|
||||
FDC4385F27B4C4A200C60D73 /* PinnedMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4385E27B4C4A200C60D73 /* PinnedMessage.swift */; };
|
||||
FDC4386127B4CDDF00C60D73 /* FileResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4386027B4CDDF00C60D73 /* FileResponse.swift */; };
|
||||
FDC4386327B4D94E00C60D73 /* OGMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4386227B4D94E00C60D73 /* OGMessage.swift */; };
|
||||
FDC4386327B4D94E00C60D73 /* SOGSMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4386227B4D94E00C60D73 /* SOGSMessage.swift */; };
|
||||
FDC4386527B4DE7600C60D73 /* RoomPollInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4386427B4DE7600C60D73 /* RoomPollInfo.swift */; };
|
||||
FDC4386727B4E10E00C60D73 /* Capabilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4386627B4E10E00C60D73 /* Capabilities.swift */; };
|
||||
FDC4386927B4E6B800C60D73 /* String+Utlities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4386827B4E6B700C60D73 /* String+Utlities.swift */; };
|
||||
|
@ -1947,6 +1948,8 @@
|
|||
FD83B9C427CF3E2A005E1583 /* OpenGroupSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupSpec.swift; sourceTree = "<group>"; };
|
||||
FD83B9C627CF3F10005E1583 /* CapabilitiesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapabilitiesSpec.swift; sourceTree = "<group>"; };
|
||||
FD83B9C827D0487A005E1583 /* SendDirectMessageResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendDirectMessageResponse.swift; sourceTree = "<group>"; };
|
||||
FD83B9CB27D179BC005E1583 /* FSEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FSEndpoint.swift; sourceTree = "<group>"; };
|
||||
FD83B9CD27D17A04005E1583 /* Request.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Request.swift; sourceTree = "<group>"; };
|
||||
FD859EEF27BF207700510D0C /* SessionProtos.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = SessionProtos.proto; sourceTree = "<group>"; };
|
||||
FD859EF027BF207C00510D0C /* WebSocketResources.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = WebSocketResources.proto; sourceTree = "<group>"; };
|
||||
FD859EF127BF6BA200510D0C /* Data+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Utilities.swift"; sourceTree = "<group>"; };
|
||||
|
@ -1959,13 +1962,12 @@
|
|||
FD88BAD827A7439C00BBC442 /* MessageRequestsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestsCell.swift; sourceTree = "<group>"; };
|
||||
FD88BADA27A750F200BBC442 /* MessageRequestsMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestsMigration.swift; sourceTree = "<group>"; };
|
||||
FD9039443F7CB729CF71350E /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
FDC4380827B31D4E00C60D73 /* Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = "<group>"; };
|
||||
FDC4380A27B31D7E00C60D73 /* Request.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Request.swift; sourceTree = "<group>"; };
|
||||
FDC4380827B31D4E00C60D73 /* SOGSError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOGSError.swift; sourceTree = "<group>"; };
|
||||
FDC4381427B329CE00C60D73 /* NonceGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonceGenerator.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 /* LegacyPublicKeyBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyPublicKeyBody.swift; sourceTree = "<group>"; };
|
||||
FDC4381F27B36ADC00C60D73 /* Endpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Endpoint.swift; sourceTree = "<group>"; };
|
||||
FDC4381F27B36ADC00C60D73 /* SOGSEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOGSEndpoint.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>"; };
|
||||
|
@ -1989,7 +1991,7 @@
|
|||
FDC4385C27B4C18900C60D73 /* Room.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Room.swift; sourceTree = "<group>"; };
|
||||
FDC4385E27B4C4A200C60D73 /* PinnedMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedMessage.swift; sourceTree = "<group>"; };
|
||||
FDC4386027B4CDDF00C60D73 /* FileResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileResponse.swift; sourceTree = "<group>"; };
|
||||
FDC4386227B4D94E00C60D73 /* OGMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OGMessage.swift; sourceTree = "<group>"; };
|
||||
FDC4386227B4D94E00C60D73 /* SOGSMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOGSMessage.swift; sourceTree = "<group>"; };
|
||||
FDC4386427B4DE7600C60D73 /* RoomPollInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPollInfo.swift; sourceTree = "<group>"; };
|
||||
FDC4386627B4E10E00C60D73 /* Capabilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Capabilities.swift; sourceTree = "<group>"; };
|
||||
FDC4386827B4E6B700C60D73 /* String+Utlities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Utlities.swift"; sourceTree = "<group>"; };
|
||||
|
@ -3420,6 +3422,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
FDC4383227B385B200C60D73 /* Models */,
|
||||
FD83B9CA27D179AF005E1583 /* Types */,
|
||||
B87EF17026367CF800124B3C /* FileServerAPIV2.swift */,
|
||||
);
|
||||
path = "File Server";
|
||||
|
@ -3905,6 +3908,14 @@
|
|||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FD83B9CA27D179AF005E1583 /* Types */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FD83B9CB27D179BC005E1583 /* FSEndpoint.swift */,
|
||||
);
|
||||
path = Types;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FD88BAD727A7438E00BBC442 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -3916,9 +3927,8 @@
|
|||
FDC4380727B31D3A00C60D73 /* Types */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FDC4380A27B31D7E00C60D73 /* Request.swift */,
|
||||
FDC4381F27B36ADC00C60D73 /* Endpoint.swift */,
|
||||
FDC4380827B31D4E00C60D73 /* Error.swift */,
|
||||
FDC4381F27B36ADC00C60D73 /* SOGSEndpoint.swift */,
|
||||
FDC4380827B31D4E00C60D73 /* SOGSError.swift */,
|
||||
FDC4381627B32EC700C60D73 /* Personalization.swift */,
|
||||
FDC4381427B329CE00C60D73 /* NonceGenerator.swift */,
|
||||
FDC438C027BB4E6800C60D73 /* Dependencies.swift */,
|
||||
|
@ -3940,7 +3950,7 @@
|
|||
FDC4386027B4CDDF00C60D73 /* FileResponse.swift */,
|
||||
FDC4387727B5C35400C60D73 /* SendMessageRequest.swift */,
|
||||
FDC438CA27BB7DB100C60D73 /* UpdateMessageRequest.swift */,
|
||||
FDC4386227B4D94E00C60D73 /* OGMessage.swift */,
|
||||
FDC4386227B4D94E00C60D73 /* SOGSMessage.swift */,
|
||||
FDC438C627BB6DF000C60D73 /* DirectMessage.swift */,
|
||||
FDC438C827BB706500C60D73 /* SendDirectMessageRequest.swift */,
|
||||
FD83B9C827D0487A005E1583 /* SendDirectMessageResponse.swift */,
|
||||
|
@ -3988,6 +3998,7 @@
|
|||
FDC4385527B484AE00C60D73 /* Models */,
|
||||
FDC4384E27B4804F00C60D73 /* Header.swift */,
|
||||
FDC4385027B4807400C60D73 /* QueryParam.swift */,
|
||||
FD83B9CD27D17A04005E1583 /* Request.swift */,
|
||||
);
|
||||
path = "Common Networking";
|
||||
sourceTree = "<group>";
|
||||
|
@ -5287,7 +5298,6 @@
|
|||
C3D9E3BF25676AD70040E4F3 /* TSAttachmentStream.m in Sources */,
|
||||
C3C2A7562553A3AB00C340D1 /* VisibleMessage+Quote.swift in Sources */,
|
||||
B8B32021258B1A650020074B /* Contact.swift in Sources */,
|
||||
FDC4380B27B31D7E00C60D73 /* Request.swift in Sources */,
|
||||
FDC4384027B4746D00C60D73 /* LegacyGetInfoResponse.swift in Sources */,
|
||||
C32C5C89256DD0D2003C73A2 /* Storage+Jobs.swift in Sources */,
|
||||
C300A5FC2554B0A000555489 /* MessageReceiver.swift in Sources */,
|
||||
|
@ -5337,11 +5347,12 @@
|
|||
B8EB20EE2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift in Sources */,
|
||||
FDC4381C27B354AC00C60D73 /* LegacyPublicKeyBody.swift in Sources */,
|
||||
FDC4382F27B383AF00C60D73 /* UnregisterResponse.swift in Sources */,
|
||||
FDC4386327B4D94E00C60D73 /* OGMessage.swift in Sources */,
|
||||
FDC4386327B4D94E00C60D73 /* SOGSMessage.swift in Sources */,
|
||||
C3C2A7712553A41E00C340D1 /* ControlMessage.swift in Sources */,
|
||||
C32C5D19256DD493003C73A2 /* OWSLinkPreview.swift in Sources */,
|
||||
C32C5CF0256DD3E4003C73A2 /* Storage+Shared.swift in Sources */,
|
||||
C300A5BD2554B00D00555489 /* ReadReceipt.swift in Sources */,
|
||||
FD83B9CC27D179BC005E1583 /* FSEndpoint.swift in Sources */,
|
||||
C32C5AB5256DBE8F003C73A2 /* TSOutgoingMessage+Conversion.swift in Sources */,
|
||||
FDC4385927B484E800C60D73 /* FileUploadBody.swift in Sources */,
|
||||
C32C5E5B256DDF45003C73A2 /* OWSStorage.m in Sources */,
|
||||
|
@ -5377,8 +5388,8 @@
|
|||
B8D0A25925E367AC00C1835E /* Notification+MessageReceiver.swift in Sources */,
|
||||
FDC4387227B5BB3B00C60D73 /* FileUploadResponse.swift in Sources */,
|
||||
C32C599E256DB02B003C73A2 /* TypingIndicators.swift in Sources */,
|
||||
FDC4380927B31D4E00C60D73 /* Error.swift in Sources */,
|
||||
FDC4382027B36ADC00C60D73 /* Endpoint.swift in Sources */,
|
||||
FDC4380927B31D4E00C60D73 /* SOGSError.swift in Sources */,
|
||||
FDC4382027B36ADC00C60D73 /* SOGSEndpoint.swift in Sources */,
|
||||
C3DB66CC260AF1F3001EFC55 /* OpenGroupAPI+ObjC.swift in Sources */,
|
||||
C32C5BEF256DC8EE003C73A2 /* OWSDisappearingMessagesJob.m in Sources */,
|
||||
C34A977425A3E34A00852C71 /* ClosedGroupControlMessage.swift in Sources */,
|
||||
|
@ -5403,6 +5414,7 @@
|
|||
C300A5E72554B07300555489 /* ExpirationTimerUpdate.swift in Sources */,
|
||||
B88A1AC725C90A4700E6D421 /* TypingIndicatorInteraction.swift in Sources */,
|
||||
C3D9E3C025676AD70040E4F3 /* TSAttachment.m in Sources */,
|
||||
FD83B9CE27D17A04005E1583 /* Request.swift in Sources */,
|
||||
FDC4384827B47F4D00C60D73 /* LegacyRoomInfo.swift in Sources */,
|
||||
C32C598A256D0664003C73A2 /* SNProtoEnvelope+Conversion.swift in Sources */,
|
||||
FDC438CB27BB7DB100C60D73 /* UpdateMessageRequest.swift in Sources */,
|
||||
|
|
|
@ -568,7 +568,7 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
|
|||
let attachment = mediaView.attachment
|
||||
if let pointer = attachment as? TSAttachmentPointer {
|
||||
if pointer.state == .failed {
|
||||
// TODO: Tapped a failed incoming attachment
|
||||
// TODO: Tapped a failed incoming attachment (Note: This is generally a permanent failure - see `AttachmentDownloadJob`)
|
||||
}
|
||||
}
|
||||
guard let stream = attachment as? TSAttachmentStream else { return }
|
||||
|
|
|
@ -182,7 +182,7 @@ extension OpenGroupSuggestionGrid {
|
|||
|
||||
label.text = room.name
|
||||
|
||||
if let imageId: Int64 = room.imageId {
|
||||
if let imageId: UInt64 = 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)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import UIKit
|
||||
import SessionUtilitiesKit
|
||||
|
||||
final class SettingsVC : BaseVC, AvatarViewHelperDelegate {
|
||||
private var profilePictureToBeUploaded: UIImage?
|
||||
|
@ -382,7 +383,7 @@ final class SettingsVC : BaseVC, AvatarViewHelperDelegate {
|
|||
DispatchQueue.main.async {
|
||||
modalActivityIndicator.dismiss {
|
||||
var isMaxFileSizeExceeded = false
|
||||
if let error = error as? FileServerAPIV2.Error {
|
||||
if let error = error as? HTTP.Error {
|
||||
isMaxFileSizeExceeded = (error == .maxFileSizeExceeded)
|
||||
}
|
||||
let title = isMaxFileSizeExceeded ? "Maximum File Size Exceeded" : "Couldn't Update Profile"
|
||||
|
|
|
@ -37,9 +37,7 @@ extension FileDownloadResponse {
|
|||
|
||||
let base64EncodedData: String = try container.decode(String.self, forKey: .base64EncodedData)
|
||||
|
||||
guard let data = Data(base64Encoded: base64EncodedData) else {
|
||||
throw FileServerAPIV2.Error.parsingFailed
|
||||
}
|
||||
guard let data = Data(base64Encoded: base64EncodedData) else { throw HTTP.Error.parsingFailed }
|
||||
|
||||
self = FileDownloadResponse(
|
||||
fileName: try container.decode(String.self, forKey: .fileName),
|
||||
|
|
|
@ -24,9 +24,7 @@ extension LegacyFileDownloadResponse {
|
|||
|
||||
let base64EncodedData: String = try container.decode(String.self, forKey: .base64EncodedData)
|
||||
|
||||
guard let data = Data(base64Encoded: base64EncodedData) else {
|
||||
throw FileServerAPIV2.Error.parsingFailed
|
||||
}
|
||||
guard let data = Data(base64Encoded: base64EncodedData) else { throw HTTP.Error.parsingFailed }
|
||||
|
||||
self = LegacyFileDownloadResponse(
|
||||
data: data
|
||||
|
|
|
@ -8,4 +8,5 @@ enum QueryParam: String {
|
|||
|
||||
case required = "required"
|
||||
case limit // For messages - number between 1 and 256 (default is 100)
|
||||
case platform // For file server session version check
|
||||
}
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
import Foundation
|
||||
import SessionUtilitiesKit
|
||||
|
||||
// MARK: - Convenience Types
|
||||
|
||||
struct Empty: Codable {}
|
||||
|
||||
typealias NoBody = Empty
|
||||
typealias NoResponse = Empty
|
||||
|
||||
protocol EndpointType: Hashable {
|
||||
var path: String { get }
|
||||
}
|
||||
|
||||
// MARK: - Request
|
||||
|
||||
struct Request<T: Encodable, Endpoint: EndpointType> {
|
||||
let method: HTTP.Verb
|
||||
let server: String
|
||||
let endpoint: Endpoint
|
||||
let queryParameters: [QueryParam: String]
|
||||
let headers: [Header: String]
|
||||
/// This is the body value sent during the request
|
||||
///
|
||||
/// **Warning:** The `bodyData` value should be used to when making the actual request instead of this as there
|
||||
/// is custom handling for certain data types
|
||||
let body: T?
|
||||
let isAuthRequired: Bool
|
||||
/// Always `true` under normal circumstances. You might want to disable
|
||||
/// this when running over Lokinet.
|
||||
let useOnionRouting: Bool
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
init(
|
||||
method: HTTP.Verb = .get,
|
||||
server: String,
|
||||
endpoint: Endpoint,
|
||||
queryParameters: [QueryParam: String] = [:],
|
||||
headers: [Header: String] = [:],
|
||||
body: T? = nil,
|
||||
isAuthRequired: Bool = true,
|
||||
useOnionRouting: Bool = true
|
||||
) {
|
||||
self.method = method
|
||||
self.server = server
|
||||
self.endpoint = endpoint
|
||||
self.queryParameters = queryParameters
|
||||
self.headers = headers
|
||||
self.body = body
|
||||
self.isAuthRequired = isAuthRequired
|
||||
self.useOnionRouting = useOnionRouting
|
||||
}
|
||||
|
||||
// MARK: - Internal Methods
|
||||
|
||||
private var url: URL? {
|
||||
return URL(string: "\(server)\(urlPathAndParamsString)")
|
||||
}
|
||||
|
||||
private func bodyData() throws -> Data? {
|
||||
// Note: Need to differentiate between JSON, b64 string and bytes body values to ensure they are
|
||||
// encoded correctly so the server knows how to handle them
|
||||
switch body {
|
||||
case let bodyString as String:
|
||||
// The only acceptable string body is a base64 encoded one
|
||||
guard let encodedData: Data = Data(base64Encoded: bodyString) else { throw HTTP.Error.parsingFailed }
|
||||
|
||||
return encodedData
|
||||
|
||||
case let bodyBytes as [UInt8]:
|
||||
return Data(bodyBytes)
|
||||
|
||||
default:
|
||||
// Having no body is fine so just return nil
|
||||
guard let body: T = body else { return nil }
|
||||
|
||||
return try JSONEncoder().encode(body)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Request Generation
|
||||
|
||||
var urlPathAndParamsString: String {
|
||||
return [
|
||||
"/\(endpoint.path)",
|
||||
queryParameters
|
||||
.map { key, value in "\(key.rawValue)=\(value)" }
|
||||
.joined(separator: "&")
|
||||
]
|
||||
.compactMap { $0 }
|
||||
.filter { !$0.isEmpty }
|
||||
.joined(separator: "?")
|
||||
}
|
||||
|
||||
func generateUrlRequest() throws -> URLRequest {
|
||||
guard let url: URL = url else { throw HTTP.Error.invalidURL }
|
||||
|
||||
var urlRequest: URLRequest = URLRequest(url: url)
|
||||
urlRequest.httpMethod = method.rawValue
|
||||
urlRequest.allHTTPHeaderFields = headers.toHTTPHeaders()
|
||||
urlRequest.httpBody = try bodyData()
|
||||
|
||||
return urlRequest
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
import YapDatabase
|
||||
|
||||
extension Storage {
|
||||
|
||||
|
@ -96,10 +97,14 @@ extension Storage {
|
|||
public func getMessageSendJob(for messageSendJobID: String) -> MessageSendJob? {
|
||||
var result: MessageSendJob?
|
||||
Storage.read { transaction in
|
||||
result = transaction.object(forKey: messageSendJobID, inCollection: MessageSendJob.collection) as? MessageSendJob
|
||||
result = self.getMessageSendJob(for: messageSendJobID, using: transaction)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
public func getMessageSendJob(for messageSendJobID: String, using transaction: Any) -> MessageSendJob? {
|
||||
return (transaction as! YapDatabaseReadTransaction).object(forKey: messageSendJobID, inCollection: MessageSendJob.collection) as? MessageSendJob
|
||||
}
|
||||
|
||||
public func resumeMessageSendJobIfNeeded(_ messageSendJobID: String) {
|
||||
guard let job = getMessageSendJob(for: messageSendJobID) else { return }
|
||||
|
|
|
@ -4,7 +4,8 @@ import SessionSnodeKit
|
|||
@objc(SNFileServerAPIV2)
|
||||
public final class FileServerAPIV2 : NSObject {
|
||||
|
||||
// MARK: Settings
|
||||
// MARK: - Settings
|
||||
|
||||
@objc public static let oldServer = "http://88.99.175.227"
|
||||
public static let oldServerPublicKey = "7cb31905b55cd5580c686911debf672577b3fb0bff81df4ce2d5c4cb3a7aaa69"
|
||||
@objc public static let server = "http://filev2.getsession.org"
|
||||
|
@ -18,92 +19,12 @@ public final class FileServerAPIV2 : NSObject {
|
|||
/// possible after proof of work has been calculated and the onion request encryption has happened, which takes several seconds.
|
||||
public static let fileSizeORMultiplier: Double = 2
|
||||
|
||||
// MARK: Initialization
|
||||
// MARK: - Initialization
|
||||
|
||||
private override init() { }
|
||||
|
||||
// MARK: Error
|
||||
public enum Error: LocalizedError {
|
||||
case parsingFailed
|
||||
case invalidURL
|
||||
case maxFileSizeExceeded
|
||||
|
||||
public var errorDescription: String? {
|
||||
switch self {
|
||||
case .parsingFailed: return "Invalid response."
|
||||
case .invalidURL: return "Invalid URL."
|
||||
case .maxFileSizeExceeded: return "Maximum file size exceeded."
|
||||
}
|
||||
}
|
||||
}
|
||||
// MARK: - File Storage
|
||||
|
||||
// MARK: Request
|
||||
private struct Request {
|
||||
let verb: HTTP.Verb
|
||||
let endpoint: String
|
||||
let queryParameters: [QueryParam: String]
|
||||
let body: Data?
|
||||
let headers: [Header: String]
|
||||
/// Always `true` under normal circumstances. You might want to disable
|
||||
/// this when running over Lokinet.
|
||||
let useOnionRouting: Bool
|
||||
|
||||
init(verb: HTTP.Verb, endpoint: String, queryParameters: [QueryParam: String] = [:], body: Data? = nil,
|
||||
headers: [Header: String] = [:], useOnionRouting: Bool = true) {
|
||||
self.verb = verb
|
||||
self.endpoint = endpoint
|
||||
self.queryParameters = queryParameters
|
||||
self.body = body
|
||||
self.headers = headers
|
||||
self.useOnionRouting = useOnionRouting
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Convenience
|
||||
|
||||
private static func send(_ request: Request, useOldServer: Bool) -> Promise<Data> {
|
||||
let server = useOldServer ? oldServer : server
|
||||
let serverPublicKey = useOldServer ? oldServerPublicKey : serverPublicKey
|
||||
var urlRequest: URLRequest
|
||||
// TODO: Combine this 'Request' with the the pattern in OpenGroupServerV2?
|
||||
switch request.verb {
|
||||
case .get:
|
||||
var rawURL = "\(server)/\(request.endpoint)"
|
||||
|
||||
if !request.queryParameters.isEmpty {
|
||||
let queryString = request.queryParameters.map { key, value in "\(key)=\(value)" }.joined(separator: "&")
|
||||
rawURL += "?\(queryString)"
|
||||
}
|
||||
|
||||
guard let url = URL(string: rawURL) else { return Promise(error: Error.invalidURL) }
|
||||
|
||||
urlRequest = URLRequest(url: url)
|
||||
|
||||
case .post, .put, .delete:
|
||||
let rawURL = "\(server)/\(request.endpoint)"
|
||||
|
||||
guard let url = URL(string: rawURL) else { return Promise(error: Error.invalidURL) }
|
||||
|
||||
urlRequest = URLRequest(url: url)
|
||||
urlRequest.httpMethod = request.verb.rawValue
|
||||
urlRequest.httpBody = request.body
|
||||
}
|
||||
|
||||
urlRequest.allHTTPHeaderFields = request.headers.toHTTPHeaders()
|
||||
|
||||
guard request.useOnionRouting else {
|
||||
preconditionFailure("It's currently not allowed to send non onion routed requests.")
|
||||
}
|
||||
|
||||
// TODO: Upgrade this to use the V4 onion requests once supported.
|
||||
return OnionRequestAPI.sendOnionRequest(urlRequest, to: server, using: .v3, with: serverPublicKey)
|
||||
.map2 { _, response in
|
||||
guard let response: Data = response else { throw Error.parsingFailed }
|
||||
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: File Storage
|
||||
@objc(upload:)
|
||||
public static func objc_upload(file: Data) -> AnyPromise {
|
||||
return AnyPromise.from(upload(file).map { String($0) })
|
||||
|
@ -112,41 +33,72 @@ public final class FileServerAPIV2 : NSObject {
|
|||
public static func upload(_ file: Data) -> Promise<UInt64> {
|
||||
let requestBody: FileUploadBody = FileUploadBody(file: file.base64EncodedString())
|
||||
|
||||
guard let body: Data = try? JSONEncoder().encode(requestBody) else {
|
||||
return Promise(error: HTTP.Error.invalidJSON)
|
||||
}
|
||||
let request = Request(
|
||||
method: .post,
|
||||
server: server,
|
||||
endpoint: Endpoint.files,
|
||||
body: requestBody
|
||||
)
|
||||
|
||||
let request = Request(verb: .post, endpoint: "files", body: body)
|
||||
return send(request, useOldServer: false).map(on: DispatchQueue.global(qos: .userInitiated)) { data in
|
||||
let response: LegacyFileUploadResponse = try data.decoded(as: LegacyFileUploadResponse.self, customError: Error.parsingFailed)
|
||||
|
||||
return response.fileId
|
||||
}
|
||||
return send(request, serverPublicKey: serverPublicKey)
|
||||
.decoded(as: LegacyFileUploadResponse.self, on: .global(qos: .userInitiated), error: HTTP.Error.parsingFailed)
|
||||
.map { response in response.fileId }
|
||||
}
|
||||
|
||||
@objc(download:useOldServer:)
|
||||
public static func objc_download(file: String, useOldServer: Bool) -> AnyPromise {
|
||||
guard let id = UInt64(file) else { return AnyPromise.from(Promise<Data>(error: Error.invalidURL)) }
|
||||
guard let id = UInt64(file) else { return AnyPromise.from(Promise<Data>(error: HTTP.Error.invalidURL)) }
|
||||
return AnyPromise.from(download(id, useOldServer: useOldServer))
|
||||
}
|
||||
|
||||
public static func download(_ file: UInt64, useOldServer: Bool) -> Promise<Data> {
|
||||
let request = Request(verb: .get, endpoint: "files/\(file)")
|
||||
let serverPublicKey: String = (useOldServer ? oldServerPublicKey : serverPublicKey)
|
||||
let request = Request<NoBody, Endpoint>(
|
||||
server: (useOldServer ? oldServer : server),
|
||||
endpoint: .file(fileId: file)
|
||||
)
|
||||
|
||||
return send(request, useOldServer: useOldServer).map(on: DispatchQueue.global(qos: .userInitiated)) { data in
|
||||
let response: LegacyFileDownloadResponse = try data.decoded(as: LegacyFileDownloadResponse.self, customError: Error.parsingFailed)
|
||||
|
||||
return response.data
|
||||
}
|
||||
return send(request, serverPublicKey: serverPublicKey)
|
||||
.decoded(as: LegacyFileDownloadResponse.self, on: .global(qos: .userInitiated), error: HTTP.Error.parsingFailed)
|
||||
.map { response in response.data }
|
||||
}
|
||||
|
||||
public static func getVersion(_ platform: String) -> Promise<String> {
|
||||
let request = Request(verb: .get, endpoint: "session_version?platform=\(platform)")
|
||||
let request = Request<NoBody, Endpoint>(
|
||||
server: server,
|
||||
endpoint: .sessionVersion,
|
||||
queryParameters: [
|
||||
.platform: platform
|
||||
]
|
||||
)
|
||||
|
||||
return send(request, useOldServer: false).map(on: DispatchQueue.global(qos: .userInitiated)) { data in
|
||||
let response: VersionResponse = try data.decoded(as: VersionResponse.self, customError: Error.parsingFailed)
|
||||
|
||||
return response.version
|
||||
return send(request, serverPublicKey: serverPublicKey)
|
||||
.decoded(as: VersionResponse.self, on: .global(qos: .userInitiated), error: HTTP.Error.parsingFailed)
|
||||
.map { response in response.version }
|
||||
}
|
||||
|
||||
// MARK: - Convenience
|
||||
|
||||
private static func send<T: Encodable>(_ request: Request<T, Endpoint>, serverPublicKey: String) -> Promise<Data> {
|
||||
guard request.useOnionRouting else {
|
||||
preconditionFailure("It's currently not allowed to send non onion routed requests.")
|
||||
}
|
||||
|
||||
let urlRequest: URLRequest
|
||||
|
||||
do {
|
||||
urlRequest = try request.generateUrlRequest()
|
||||
}
|
||||
catch {
|
||||
return Promise(error: error)
|
||||
}
|
||||
|
||||
// TODO: Rename file to be 'FileServerAPI' (drop the 'V2')
|
||||
return OnionRequestAPI.sendOnionRequest(urlRequest, to: request.server, with: serverPublicKey)
|
||||
.map2 { _, response in
|
||||
guard let response: Data = response else { throw HTTP.Error.parsingFailed }
|
||||
|
||||
return response
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
extension FileServerAPIV2 {
|
||||
public enum Endpoint: EndpointType {
|
||||
case files
|
||||
case file(fileId: UInt64)
|
||||
case sessionVersion
|
||||
|
||||
var path: String {
|
||||
switch self {
|
||||
case .files: return "files"
|
||||
case .file(let fileId): return "files/\(fileId)"
|
||||
case .sessionVersion: return "session_version"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -98,7 +98,7 @@ public final class AttachmentDownloadJob : NSObject, Job, NSCoding { // NSObject
|
|||
}
|
||||
}
|
||||
if let tsMessage = TSMessage.fetch(uniqueId: tsMessageID), let openGroup = storage.getOpenGroup(for: tsMessage.uniqueThreadId) {
|
||||
guard let fileAsString = pointer.downloadURL.split(separator: "/").last, let file = UInt64(fileAsString) else {
|
||||
guard let fileAsString = pointer.downloadURL.split(separator: "/").last, let fileId = UInt64(fileAsString) else {
|
||||
return handleFailure(Error.invalidURL)
|
||||
}
|
||||
// TODO: Upgrade this to use the non-legacy version
|
||||
|
|
|
@ -66,25 +66,33 @@ public final class AttachmentUploadJob : NSObject, Job, NSCoding { // NSObject/N
|
|||
guard let stream = TSAttachment.fetch(uniqueId: attachmentID) as? TSAttachmentStream else {
|
||||
return handleFailure(error: Error.noAttachment)
|
||||
}
|
||||
guard !stream.isUploaded else { return handleSuccess() } // Should never occur
|
||||
guard !stream.isUploaded else { return handleSuccess(stream.serverId) } // Should never occur
|
||||
|
||||
let storage = SNMessagingKitConfiguration.shared.storage
|
||||
if let openGroup = storage.getOpenGroup(for: threadID) {
|
||||
AttachmentUploadJob.upload(
|
||||
stream,
|
||||
using: { data in
|
||||
// TODO: Upgrade this to use the non-legacy version
|
||||
return OpenGroupAPI.legacyUpload(data, to: openGroup.room, on: openGroup.server)
|
||||
OpenGroupAPI.uploadFile(data.bytes, to: openGroup.room, on: openGroup.server)
|
||||
.map { _, response -> UInt64 in response.id }
|
||||
},
|
||||
encrypt: false,
|
||||
onSuccess: handleSuccess,
|
||||
onSuccess: { [weak self] fileId in self?.handleSuccess(fileId) },
|
||||
onFailure: handleFailure
|
||||
)
|
||||
}
|
||||
else {
|
||||
AttachmentUploadJob.upload(
|
||||
stream,
|
||||
using: FileServerAPIV2.upload,
|
||||
encrypt: true,
|
||||
onSuccess: { [weak self] fileId in self?.handleSuccess(fileId) },
|
||||
onFailure: handleFailure
|
||||
)
|
||||
} else {
|
||||
AttachmentUploadJob.upload(stream, using: FileServerAPIV2.upload, encrypt: true, onSuccess: handleSuccess, onFailure: handleFailure)
|
||||
}
|
||||
}
|
||||
|
||||
public static func upload(_ stream: TSAttachmentStream, using upload: (Data) -> Promise<UInt64>, encrypt: Bool, onSuccess: (() -> Void)?, onFailure: ((Swift.Error) -> Void)?) {
|
||||
public static func upload(_ stream: TSAttachmentStream, using upload: (Data) -> Promise<UInt64>, encrypt: Bool, onSuccess: ((UInt64) -> Void)?, onFailure: ((Swift.Error) -> Void)?) {
|
||||
// Get the attachment
|
||||
guard var data = try? stream.readDataFromFile() else {
|
||||
SNLog("Couldn't read attachment from disk.")
|
||||
|
@ -105,37 +113,74 @@ public final class AttachmentUploadJob : NSObject, Job, NSCoding { // NSObject/N
|
|||
// Check the file size
|
||||
SNLog("File size: \(data.count) bytes.")
|
||||
if Double(data.count) > Double(FileServerAPIV2.maxFileSize) / FileServerAPIV2.fileSizeORMultiplier {
|
||||
onFailure?(FileServerAPIV2.Error.maxFileSizeExceeded); return
|
||||
onFailure?(HTTP.Error.maxFileSizeExceeded)
|
||||
return
|
||||
}
|
||||
|
||||
// Send the request
|
||||
stream.isUploaded = false
|
||||
stream.save()
|
||||
upload(data).done(on: DispatchQueue.global(qos: .userInitiated)) { fileID in
|
||||
let downloadURL = "\(FileServerAPIV2.server)/files/\(fileID)"
|
||||
stream.serverId = fileID
|
||||
upload(data).done(on: DispatchQueue.global(qos: .userInitiated)) { fileId in
|
||||
let downloadURL = "\(FileServerAPIV2.server)/files/\(fileId)"
|
||||
stream.serverId = fileId
|
||||
stream.isUploaded = true
|
||||
stream.downloadURL = downloadURL
|
||||
stream.save()
|
||||
onSuccess?()
|
||||
onSuccess?(fileId)
|
||||
}.catch { error in
|
||||
onFailure?(error)
|
||||
}
|
||||
}
|
||||
|
||||
private func handleSuccess() {
|
||||
private func handleSuccess(_ fileId: UInt64) {
|
||||
SNLog("Attachment uploaded successfully.")
|
||||
delegate?.handleJobSucceeded(self)
|
||||
SNMessagingKitConfiguration.shared.storage.resumeMessageSendJobIfNeeded(messageSendJobID)
|
||||
Storage.shared.write(with: { transaction in
|
||||
var message: TSMessage?
|
||||
let transaction = transaction as! YapDatabaseReadWriteTransaction
|
||||
TSDatabaseSecondaryIndexes.enumerateMessages(withTimestamp: self.message.sentTimestamp!, with: { _, key, _ in
|
||||
message = TSMessage.fetch(uniqueId: key, transaction: transaction)
|
||||
}, using: transaction)
|
||||
if let message = message {
|
||||
MessageInvalidator.invalidate(message, with: transaction)
|
||||
|
||||
let messageSendJobId: String = messageSendJobID
|
||||
|
||||
Storage.shared.write(
|
||||
with: { transaction in
|
||||
// Get the existing MessageSendJob and replace it with one that has it's destination updated
|
||||
// to include the returned fileId
|
||||
if let oldJob: MessageSendJob = SNMessagingKitConfiguration.shared.storage.getMessageSendJob(for: messageSendJobId, using: transaction) {
|
||||
switch oldJob.destination {
|
||||
case .openGroup(let roomToken, let server, let whisperTo, let whisperMods, let oldFileIds):
|
||||
let job: MessageSendJob = MessageSendJob(
|
||||
message: oldJob.message,
|
||||
destination: .openGroup(
|
||||
roomToken: roomToken,
|
||||
server: server,
|
||||
whisperTo: whisperTo,
|
||||
whisperMods: whisperMods,
|
||||
fileIds: (oldFileIds ?? []) + [fileId]
|
||||
)
|
||||
)
|
||||
job.id = oldJob.id // Use the existing id so it gets overwritten
|
||||
job.delegate = oldJob.delegate
|
||||
job.failureCount = oldJob.failureCount
|
||||
|
||||
// This method just writes the job directly and doesn't generate a new id (as we want)
|
||||
SNMessagingKitConfiguration.shared.storage.persist(job, using: transaction)
|
||||
|
||||
default: break
|
||||
}
|
||||
}
|
||||
},
|
||||
completion: {
|
||||
SNMessagingKitConfiguration.shared.storage.resumeMessageSendJobIfNeeded(messageSendJobId)
|
||||
|
||||
Storage.shared.write(with: { transaction in
|
||||
var message: TSMessage?
|
||||
let transaction = transaction as! YapDatabaseReadWriteTransaction
|
||||
TSDatabaseSecondaryIndexes.enumerateMessages(withTimestamp: self.message.sentTimestamp!, with: { _, key, _ in
|
||||
message = TSMessage.fetch(uniqueId: key, transaction: transaction)
|
||||
}, using: transaction)
|
||||
if let message = message {
|
||||
MessageInvalidator.invalidate(message, with: transaction)
|
||||
}
|
||||
}, completion: { })
|
||||
}
|
||||
}, completion: { })
|
||||
)
|
||||
}
|
||||
|
||||
private func handlePermanentFailure(error: Swift.Error) {
|
||||
|
|
|
@ -6,7 +6,7 @@ public final class MessageSendJob : NSObject, Job, NSCoding { // NSObject/NSCodi
|
|||
public let message: Message
|
||||
public let destination: Message.Destination
|
||||
public var delegate: JobDelegate?
|
||||
public var id: String?
|
||||
public var id: String? // This should only get set in either `JobQueue` or `AttachmentUploadJob`
|
||||
public var failureCount: UInt = 0
|
||||
|
||||
// MARK: - Settings
|
||||
|
@ -100,7 +100,7 @@ public final class MessageSendJob : NSObject, Job, NSCoding { // NSObject/NSCodi
|
|||
.replacingOccurrences(of: "]", with: "")
|
||||
.split(separator: "|")
|
||||
.map { String($0) }
|
||||
let fileIds: [Int64]? = (fileIdStrings.isEmpty ? nil : fileIdStrings.compactMap { Int64($0) })
|
||||
let fileIds: [UInt64]? = (fileIdStrings.isEmpty ? nil : fileIdStrings.compactMap { UInt64($0) })
|
||||
|
||||
destination = .openGroup(
|
||||
roomToken: roomToken,
|
||||
|
|
|
@ -10,11 +10,11 @@ public extension Message {
|
|||
server: String,
|
||||
whisperTo: String? = nil,
|
||||
whisperMods: Bool = false,
|
||||
fileIds: [Int64]? = nil // TODO: Handle 'fileIds'
|
||||
fileIds: [UInt64]? = nil
|
||||
)
|
||||
case openGroupInbox(server: String, openGroupPublicKey: String, blindedPublicKey: String)
|
||||
|
||||
static func from(_ thread: TSThread) -> Message.Destination {
|
||||
static func from(_ thread: TSThread, fileIds: [UInt64]? = nil) -> Message.Destination {
|
||||
if let thread = thread as? TSContactThread {
|
||||
if SessionId.Prefix(from: thread.contactSessionID()) == .blinded {
|
||||
guard let server: String = thread.originalOpenGroupServer, let publicKey: String = thread.originalOpenGroupPublicKey else {
|
||||
|
@ -40,7 +40,7 @@ public extension Message {
|
|||
if let thread = thread as? TSGroupThread, thread.isOpenGroup {
|
||||
let openGroup: OpenGroup = Storage.shared.getOpenGroup(for: thread.uniqueId!)!
|
||||
|
||||
return .openGroup(roomToken: openGroup.room, server: openGroup.server)
|
||||
return .openGroup(roomToken: openGroup.room, server: openGroup.server, fileIds: fileIds)
|
||||
}
|
||||
|
||||
preconditionFailure("TODO: Handle legacy closed groups.")
|
||||
|
|
|
@ -28,7 +28,7 @@ extension OpenGroupAPI {
|
|||
private let b64: String?
|
||||
private let bytes: [UInt8]?
|
||||
|
||||
init<T: Encodable>(request: Request<T>) {
|
||||
init<T: Encodable>(request: Request<T, Endpoint>) {
|
||||
self.method = request.method
|
||||
self.path = request.urlPathAndParamsString
|
||||
self.headers = (request.headers.isEmpty ? nil : request.headers.toHTTPHeaders())
|
||||
|
@ -86,17 +86,17 @@ extension OpenGroupAPI {
|
|||
// MARK: - BatchRequestInfo<T, R>
|
||||
|
||||
struct BatchRequestInfo<T: Encodable>: BatchRequestInfoType {
|
||||
let request: Request<T>
|
||||
let request: Request<T, Endpoint>
|
||||
let responseType: Codable.Type
|
||||
|
||||
var endpoint: Endpoint { request.endpoint }
|
||||
|
||||
init<R: Codable>(request: Request<T>, responseType: R.Type) {
|
||||
init<R: Codable>(request: Request<T, Endpoint>, responseType: R.Type) {
|
||||
self.request = request
|
||||
self.responseType = BatchSubResponse<R>.self
|
||||
}
|
||||
|
||||
init(request: Request<T>) {
|
||||
init(request: Request<T, Endpoint>) {
|
||||
self.init(
|
||||
request: request,
|
||||
responseType: NoResponse.self
|
||||
|
@ -124,7 +124,7 @@ extension OpenGroupAPI.BatchSubResponse {
|
|||
code: try container.decode(Int32.self, forKey: .code),
|
||||
headers: try container.decode([String: String].self, forKey: .headers),
|
||||
body: body,
|
||||
failedToParseBody: (body == nil && T.self != OpenGroupAPI.NoResponse.self && !(T.self is ExpressibleByNilLiteral.Type))
|
||||
failedToParseBody: (body == nil && T.self != NoResponse.self && !(T.self is ExpressibleByNilLiteral.Type))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -143,31 +143,31 @@ protocol BatchRequestInfoType {
|
|||
// MARK: - Convenience
|
||||
|
||||
public extension Decodable {
|
||||
static func decoded(from data: Data, customError: Error, using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()) throws -> Self {
|
||||
return try data.decoded(as: Self.self, customError: customError, using: dependencies)
|
||||
static func decoded(from data: Data, using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()) throws -> Self {
|
||||
return try data.decoded(as: Self.self, using: dependencies)
|
||||
}
|
||||
}
|
||||
|
||||
extension Promise where T == (OnionRequestResponseInfoType, Data?) {
|
||||
func decoded(as types: OpenGroupAPI.BatchResponseTypes, on queue: DispatchQueue? = nil, error: Error, using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()) -> Promise<OpenGroupAPI.BatchResponse> {
|
||||
func decoded(as types: OpenGroupAPI.BatchResponseTypes, on queue: DispatchQueue? = nil, using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()) -> Promise<OpenGroupAPI.BatchResponse> {
|
||||
self.map(on: queue) { responseInfo, maybeData -> OpenGroupAPI.BatchResponse in
|
||||
// Need to split the data into an array of data so each item can be Decoded correctly
|
||||
guard let data: Data = maybeData else { throw OpenGroupAPI.Error.parsingFailed }
|
||||
guard let data: Data = maybeData else { throw HTTP.Error.parsingFailed }
|
||||
guard let jsonObject: Any = try? JSONSerialization.jsonObject(with: data, options: [.fragmentsAllowed]) else {
|
||||
throw OpenGroupAPI.Error.parsingFailed
|
||||
throw HTTP.Error.parsingFailed
|
||||
}
|
||||
guard let anyArray: [Any] = jsonObject as? [Any] else { throw OpenGroupAPI.Error.parsingFailed }
|
||||
guard let anyArray: [Any] = jsonObject as? [Any] else { throw HTTP.Error.parsingFailed }
|
||||
|
||||
let dataArray: [Data] = anyArray.compactMap { try? JSONSerialization.data(withJSONObject: $0) }
|
||||
guard dataArray.count == types.count else { throw OpenGroupAPI.Error.parsingFailed }
|
||||
guard dataArray.count == types.count else { throw HTTP.Error.parsingFailed }
|
||||
|
||||
do {
|
||||
return try zip(dataArray, types)
|
||||
.map { data, type in try type.decoded(from: data, customError: error, using: dependencies) }
|
||||
.map { data, type in try type.decoded(from: data, using: dependencies) }
|
||||
.map { data in (responseInfo, data) }
|
||||
}
|
||||
catch _ {
|
||||
throw error
|
||||
catch {
|
||||
throw HTTP.Error.parsingFailed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ extension OpenGroupAPI.LegacyAuthTokenResponse.Challenge {
|
|||
let base64EncodedEphemeralPublicKey: String = try container.decode(String.self, forKey: .ephemeralPublicKey)
|
||||
|
||||
guard let ciphertext = Data(base64Encoded: base64EncodedCiphertext), let ephemeralPublicKey = Data(base64Encoded: base64EncodedEphemeralPublicKey) else {
|
||||
throw OpenGroupAPI.Error.parsingFailed
|
||||
throw HTTP.Error.parsingFailed
|
||||
}
|
||||
|
||||
self = OpenGroupAPI.LegacyAuthTokenResponse.Challenge(
|
||||
|
|
|
@ -49,7 +49,7 @@ extension LegacyOpenGroupMessageV2 {
|
|||
|
||||
// Validate the message signature
|
||||
guard let data = Data(base64Encoded: base64EncodedData), let signature = Data(base64Encoded: base64EncodedSignature) else {
|
||||
throw OpenGroupAPI.Error.parsingFailed
|
||||
throw HTTP.Error.parsingFailed
|
||||
}
|
||||
|
||||
let publicKey = Data(hex: sender.removingIdPrefixIfNeeded())
|
||||
|
@ -57,7 +57,7 @@ extension LegacyOpenGroupMessageV2 {
|
|||
|
||||
guard isValid else {
|
||||
SNLog("Ignoring message with invalid signature.")
|
||||
throw OpenGroupAPI.Error.parsingFailed
|
||||
throw HTTP.Error.parsingFailed
|
||||
}
|
||||
|
||||
self = LegacyOpenGroupMessageV2(
|
||||
|
|
|
@ -70,7 +70,7 @@ extension OpenGroupAPI {
|
|||
/// File ID of an uploaded file containing the room's image
|
||||
///
|
||||
/// Omitted if there is no image
|
||||
public let imageId: Int64?
|
||||
public let imageId: UInt64?
|
||||
|
||||
/// Array of pinned message information (omitted entirely if there are no pinned messages)
|
||||
public let pinnedMessages: [PinnedMessage]?
|
||||
|
@ -160,7 +160,7 @@ extension OpenGroupAPI.Room {
|
|||
|
||||
activeUsers: try container.decode(Int64.self, forKey: .activeUsers),
|
||||
activeUsersCutoff: try container.decode(Int64.self, forKey: .activeUsersCutoff),
|
||||
imageId: try? container.decode(Int64.self, forKey: .imageId),
|
||||
imageId: try? container.decode(UInt64.self, forKey: .imageId),
|
||||
pinnedMessages: try? container.decode([OpenGroupAPI.PinnedMessage].self, forKey: .pinnedMessages),
|
||||
|
||||
admin: ((try? container.decode(Bool.self, forKey: .admin)) ?? false),
|
||||
|
|
|
@ -45,10 +45,10 @@ extension OpenGroupAPI.Message {
|
|||
// If we have data and a signature (ie. the message isn't a deletion) then validate the signature
|
||||
if let base64EncodedData: String = maybeBase64EncodedData, let base64EncodedSignature: String = maybeBase64EncodedSignature {
|
||||
guard let sender: String = maybeSender, let data = Data(base64Encoded: base64EncodedData), let signature = Data(base64Encoded: base64EncodedSignature) else {
|
||||
throw OpenGroupAPI.Error.parsingFailed
|
||||
throw HTTP.Error.parsingFailed
|
||||
}
|
||||
guard let dependencies: OpenGroupAPI.Dependencies = decoder.userInfo[OpenGroupAPI.Dependencies.userInfoKey] as? OpenGroupAPI.Dependencies else {
|
||||
throw OpenGroupAPI.Error.parsingFailed
|
||||
throw HTTP.Error.parsingFailed
|
||||
}
|
||||
|
||||
// Verify the signature based on the SessionId.Prefix type
|
||||
|
@ -58,18 +58,18 @@ extension OpenGroupAPI.Message {
|
|||
case .blinded:
|
||||
guard dependencies.sign.verify(message: data.bytes, publicKey: publicKey.bytes, signature: signature.bytes) else {
|
||||
SNLog("Ignoring message with invalid signature.")
|
||||
throw OpenGroupAPI.Error.parsingFailed
|
||||
throw HTTP.Error.parsingFailed
|
||||
}
|
||||
|
||||
case .standard, .unblinded:
|
||||
guard (try? dependencies.ed25519.verifySignature(signature, publicKey: publicKey, data: data)) == true else {
|
||||
SNLog("Ignoring message with invalid signature.")
|
||||
throw OpenGroupAPI.Error.parsingFailed
|
||||
throw HTTP.Error.parsingFailed
|
||||
}
|
||||
|
||||
case .none:
|
||||
SNLog("Ignoring message with invalid sender.")
|
||||
throw OpenGroupAPI.Error.parsingFailed
|
||||
throw HTTP.Error.parsingFailed
|
||||
}
|
||||
}
|
||||
|
|
@ -40,7 +40,7 @@ extension OpenGroupAPI {
|
|||
///
|
||||
/// When submitting a message edit this field must contain the IDs of any newly uploaded files that are part of the edit; existing
|
||||
/// attachment IDs may also be included, but are not required
|
||||
let fileIds: [Int64]?
|
||||
let fileIds: [UInt64]?
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
|
@ -49,7 +49,7 @@ extension OpenGroupAPI {
|
|||
signature: Data,
|
||||
whisperTo: String? = nil,
|
||||
whisperMods: Bool? = nil,
|
||||
fileIds: [Int64]? = nil
|
||||
fileIds: [UInt64]? = nil
|
||||
) {
|
||||
self.data = data
|
||||
self.signature = signature
|
||||
|
|
|
@ -59,7 +59,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
// Generate the requests
|
||||
let requestResponseType: [BatchRequestInfoType] = [
|
||||
BatchRequestInfo(
|
||||
request: Request<NoBody>(
|
||||
request: Request<NoBody, Endpoint>(
|
||||
server: server,
|
||||
endpoint: .capabilities
|
||||
),
|
||||
|
@ -85,14 +85,14 @@ public final class OpenGroupAPI: NSObject {
|
|||
|
||||
return [
|
||||
BatchRequestInfo(
|
||||
request: Request<NoBody>(
|
||||
request: Request<NoBody, Endpoint>(
|
||||
server: server,
|
||||
endpoint: .roomPollInfo(openGroup.room, openGroup.infoUpdates)
|
||||
),
|
||||
responseType: RoomPollInfo.self
|
||||
),
|
||||
BatchRequestInfo(
|
||||
request: Request<NoBody>(
|
||||
request: Request<NoBody, Endpoint>(
|
||||
server: server,
|
||||
endpoint: (shouldRetrieveRecentMessages ?
|
||||
.roomMessagesRecent(openGroup.room) :
|
||||
|
@ -107,7 +107,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
.appending([
|
||||
// Inbox
|
||||
BatchRequestInfo(
|
||||
request: Request<NoBody>(
|
||||
request: Request<NoBody, Endpoint>(
|
||||
server: server,
|
||||
endpoint: (maybeLastInboxMessageId == nil ?
|
||||
.inbox :
|
||||
|
@ -119,7 +119,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
|
||||
// Outbox
|
||||
BatchRequestInfo(
|
||||
request: Request<NoBody>(
|
||||
request: Request<NoBody, Endpoint>(
|
||||
server: server,
|
||||
endpoint: (maybeLastOutboxMessageId == nil ?
|
||||
.outbox :
|
||||
|
@ -146,12 +146,12 @@ public final class OpenGroupAPI: NSObject {
|
|||
let request: Request = Request(
|
||||
method: .post,
|
||||
server: server,
|
||||
endpoint: .batch,
|
||||
endpoint: Endpoint.batch,
|
||||
body: requestBody
|
||||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: responseTypes, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
.decoded(as: responseTypes, on: OpenGroupAPI.workQueue, using: dependencies)
|
||||
.map { result in
|
||||
result.enumerated()
|
||||
.reduce(into: [:]) { prev, next in
|
||||
|
@ -176,13 +176,13 @@ public final class OpenGroupAPI: NSObject {
|
|||
let request: Request = Request(
|
||||
method: .post,
|
||||
server: server,
|
||||
endpoint: .sequence,
|
||||
endpoint: Endpoint.sequence,
|
||||
body: requestBody
|
||||
)
|
||||
|
||||
// TODO: Handle a `412` response (ie. a required capability isn't supported)
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: responseTypes, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
.decoded(as: responseTypes, on: OpenGroupAPI.workQueue, using: dependencies)
|
||||
.map { result in
|
||||
result.enumerated()
|
||||
.reduce(into: [:]) { prev, next in
|
||||
|
@ -201,7 +201,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
/// Eg. `GET /capabilities` could return `{"capabilities": ["sogs", "batch"]}` `GET /capabilities?required=magic,batch`
|
||||
/// could return: `{"capabilities": ["sogs", "batch"], "missing": ["magic"]}`
|
||||
public static func capabilities(on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, Capabilities)> {
|
||||
let request: Request = Request<NoBody>(
|
||||
let request: Request = Request<NoBody, Endpoint>(
|
||||
server: server,
|
||||
endpoint: .capabilities,
|
||||
queryParameters: [:] // TODO: Add any requirements '.required'.
|
||||
|
@ -209,7 +209,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
|
||||
// TODO: Handle a `412` response (ie. a required capability isn't supported)
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: Capabilities.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
.decoded(as: Capabilities.self, on: OpenGroupAPI.workQueue, using: dependencies)
|
||||
}
|
||||
|
||||
// MARK: - Room
|
||||
|
@ -218,13 +218,13 @@ public final class OpenGroupAPI: NSObject {
|
|||
///
|
||||
/// Rooms to which the user does not have access (e.g. because they are banned, or the room has restricted access permissions) are not included
|
||||
public static func rooms(for server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, [Room])> {
|
||||
let request: Request = Request<NoBody>(
|
||||
let request: Request = Request<NoBody, Endpoint>(
|
||||
server: server,
|
||||
endpoint: .rooms
|
||||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: [Room].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
.decoded(as: [Room].self, on: OpenGroupAPI.workQueue, using: dependencies)
|
||||
}
|
||||
|
||||
/// Returns the details of a single room
|
||||
|
@ -234,13 +234,13 @@ public final class OpenGroupAPI: NSObject {
|
|||
/// method to ensure things are processed correctly
|
||||
@available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead")
|
||||
public static func room(for roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, Room)> {
|
||||
let request: Request = Request<NoBody>(
|
||||
let request: Request = Request<NoBody, Endpoint>(
|
||||
server: server,
|
||||
endpoint: .room(roomToken)
|
||||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: Room.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
.decoded(as: Room.self, on: OpenGroupAPI.workQueue, using: dependencies)
|
||||
}
|
||||
|
||||
/// Polls a room for metadata updates
|
||||
|
@ -253,13 +253,13 @@ public final class OpenGroupAPI: NSObject {
|
|||
/// method to ensure things are processed correctly
|
||||
@available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead")
|
||||
public static func roomPollInfo(lastUpdated: Int64, for roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, RoomPollInfo)> {
|
||||
let request: Request = Request<NoBody>(
|
||||
let request: Request = Request<NoBody, Endpoint>(
|
||||
server: server,
|
||||
endpoint: .roomPollInfo(roomToken, lastUpdated)
|
||||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: RoomPollInfo.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
.decoded(as: RoomPollInfo.self, on: OpenGroupAPI.workQueue, using: dependencies)
|
||||
}
|
||||
|
||||
/// This is a convenience method which constructs a `/sequence` of the `capabilities` and `room` requests, refer to those
|
||||
|
@ -272,7 +272,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
let requestResponseType: [BatchRequestInfoType] = [
|
||||
// Get the latest capabilities for the server (in case it's a new server or the cached ones are stale)
|
||||
BatchRequestInfo(
|
||||
request: Request<NoBody>(
|
||||
request: Request<NoBody, Endpoint>(
|
||||
server: server,
|
||||
endpoint: .capabilities
|
||||
),
|
||||
|
@ -281,7 +281,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
|
||||
// And the room info
|
||||
BatchRequestInfo(
|
||||
request: Request<NoBody>(
|
||||
request: Request<NoBody, Endpoint>(
|
||||
server: server,
|
||||
endpoint: .room(roomToken)
|
||||
),
|
||||
|
@ -298,14 +298,14 @@ public final class OpenGroupAPI: NSObject {
|
|||
switch endpoint {
|
||||
case .capabilities:
|
||||
guard let responseData: BatchSubResponse<Capabilities> = endpointResponse.data as? BatchSubResponse<Capabilities>, let responseBody: Capabilities = responseData.body else {
|
||||
throw Error.parsingFailed
|
||||
throw HTTP.Error.parsingFailed
|
||||
}
|
||||
|
||||
capabilities = (endpointResponse.info, responseBody)
|
||||
|
||||
case .room:
|
||||
guard let responseData: OpenGroupAPI.BatchSubResponse<OpenGroupAPI.Room> = endpointResponse.data as? OpenGroupAPI.BatchSubResponse<OpenGroupAPI.Room>, let responseBody: OpenGroupAPI.Room = responseData.body else {
|
||||
throw Error.parsingFailed
|
||||
throw HTTP.Error.parsingFailed
|
||||
}
|
||||
|
||||
room = (endpointResponse.info, responseBody)
|
||||
|
@ -315,7 +315,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
}
|
||||
|
||||
guard let capabilities: (OnionRequestResponseInfoType, Capabilities?) = capabilities, let room: (OnionRequestResponseInfoType, Room?) = room else {
|
||||
throw Error.parsingFailed
|
||||
throw HTTP.Error.parsingFailed
|
||||
}
|
||||
|
||||
return (capabilities, room)
|
||||
|
@ -331,6 +331,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
on server: String,
|
||||
whisperTo: String?,
|
||||
whisperMods: Bool,
|
||||
fileIds: [UInt64]?,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> Promise<(OnionRequestResponseInfoType, Message)> {
|
||||
guard let signResult: (publicKey: String, signature: Bytes) = sign(plaintext.bytes, for: server, using: dependencies) else {
|
||||
|
@ -342,18 +343,18 @@ public final class OpenGroupAPI: NSObject {
|
|||
signature: Data(signResult.signature),
|
||||
whisperTo: whisperTo,
|
||||
whisperMods: whisperMods,
|
||||
fileIds: nil // TODO: Add support for 'fileIds'.
|
||||
fileIds: fileIds
|
||||
)
|
||||
|
||||
let request = Request(
|
||||
method: .post,
|
||||
server: server,
|
||||
endpoint: .roomMessage(roomToken),
|
||||
endpoint: Endpoint.roomMessage(roomToken),
|
||||
body: requestBody
|
||||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: Message.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
.decoded(as: Message.self, on: OpenGroupAPI.workQueue, using: dependencies)
|
||||
.map { response, message in
|
||||
// Store the 'message.posted' timestamp to prevent the sent message getting duplicated when it is later retrieved
|
||||
dependencies.storage.write { transaction in
|
||||
|
@ -367,13 +368,13 @@ public final class OpenGroupAPI: NSObject {
|
|||
|
||||
/// Returns a single message by ID
|
||||
public static func message(_ id: Int64, in roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, Message)> {
|
||||
let request: Request = Request<NoBody>(
|
||||
let request: Request = Request<NoBody, Endpoint>(
|
||||
server: server,
|
||||
endpoint: .roomMessageIndividual(roomToken, id: id)
|
||||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: Message.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
.decoded(as: Message.self, on: OpenGroupAPI.workQueue, using: dependencies)
|
||||
}
|
||||
|
||||
/// Edits a message, replacing its existing content with new content and a new signature
|
||||
|
@ -400,7 +401,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
let request: Request = Request(
|
||||
method: .put,
|
||||
server: server,
|
||||
endpoint: .roomMessageIndividual(roomToken, id: id),
|
||||
endpoint: Endpoint.roomMessageIndividual(roomToken, id: id),
|
||||
body: requestBody
|
||||
)
|
||||
|
||||
|
@ -414,7 +415,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
on server: String,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> Promise<(OnionRequestResponseInfoType, Data?)> {
|
||||
let request: Request = Request<NoBody>(
|
||||
let request: Request = Request<NoBody, Endpoint>(
|
||||
method: .delete,
|
||||
server: server,
|
||||
endpoint: .roomMessageIndividual(roomToken, id: id)
|
||||
|
@ -428,7 +429,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
/// method to ensure things are processed correctly
|
||||
@available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead")
|
||||
public static func recentMessages(in roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, [Message])> {
|
||||
let request: Request = Request<NoBody>(
|
||||
let request: Request = Request<NoBody, Endpoint>(
|
||||
server: server,
|
||||
endpoint: .roomMessagesRecent(roomToken)
|
||||
// TODO: Limit?.
|
||||
|
@ -436,7 +437,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: [Message].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
.decoded(as: [Message].self, on: OpenGroupAPI.workQueue, using: dependencies)
|
||||
}
|
||||
|
||||
/// **Note:** This is the direct request to retrieve recent messages before a given message and is currently unused, in order to call this directly
|
||||
|
@ -445,7 +446,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
@available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead")
|
||||
public static func messagesBefore(messageId: Int64, in roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, [Message])> {
|
||||
// TODO: Do we need to be able to load old messages?
|
||||
let request: Request = Request<NoBody>(
|
||||
let request: Request = Request<NoBody, Endpoint>(
|
||||
server: server,
|
||||
endpoint: .roomMessagesBefore(roomToken, id: messageId)
|
||||
// TODO: Limit?.
|
||||
|
@ -453,7 +454,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: [Message].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
.decoded(as: [Message].self, on: OpenGroupAPI.workQueue, using: dependencies)
|
||||
}
|
||||
|
||||
/// **Note:** This is the direct request to retrieve messages since a given message `seqNo` so should be retrieved automatically from the
|
||||
|
@ -461,7 +462,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
/// `OpenGroupManager.handleMessages` method to ensure things are processed correctly
|
||||
@available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead")
|
||||
public static func messagesSince(seqNo: Int64, in roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, [Message])> {
|
||||
let request: Request = Request<NoBody>(
|
||||
let request: Request = Request<NoBody, Endpoint>(
|
||||
server: server,
|
||||
endpoint: .roomMessagesSince(roomToken, seqNo: seqNo)
|
||||
// TODO: Limit?.
|
||||
|
@ -469,7 +470,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: [Message].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
.decoded(as: [Message].self, on: OpenGroupAPI.workQueue, using: dependencies)
|
||||
}
|
||||
|
||||
// MARK: - Pinning
|
||||
|
@ -485,7 +486,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
/// messages are returned in pinning-order this allows admins to order multiple pinned messages in a room by re-pinning (via this endpoint) in the
|
||||
/// order in which pinned messages should be displayed
|
||||
public static func pinMessage(id: Int64, in roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<OnionRequestResponseInfoType> {
|
||||
let request: Request = Request<NoBody>(
|
||||
let request: Request = Request<NoBody, Endpoint>(
|
||||
method: .post,
|
||||
server: server,
|
||||
endpoint: .roomPinMessage(roomToken, id: id)
|
||||
|
@ -499,7 +500,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
///
|
||||
/// The user must have `admin` (not just `moderator`) permissions in the room
|
||||
public static func unpinMessage(id: Int64, in roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<OnionRequestResponseInfoType> {
|
||||
let request: Request = Request<NoBody>(
|
||||
let request: Request = Request<NoBody, Endpoint>(
|
||||
method: .post,
|
||||
server: server,
|
||||
endpoint: .roomUnpinMessage(roomToken, id: id)
|
||||
|
@ -513,7 +514,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
///
|
||||
/// The user must have `admin` (not just `moderator`) permissions in the room
|
||||
public static func unpinAll(in roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<OnionRequestResponseInfoType> {
|
||||
let request: Request = Request<NoBody>(
|
||||
let request: Request = Request<NoBody, Endpoint>(
|
||||
method: .post,
|
||||
server: server,
|
||||
endpoint: .roomUnpinAll(roomToken)
|
||||
|
@ -529,7 +530,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
let request: Request = Request(
|
||||
method: .post,
|
||||
server: server,
|
||||
endpoint: .roomFile(roomToken),
|
||||
endpoint: Endpoint.roomFile(roomToken),
|
||||
headers: [
|
||||
.contentDisposition: [ "attachment", fileName.map { "filename=\"\($0)\"" } ]
|
||||
.compactMap{ $0 }
|
||||
|
@ -540,50 +541,31 @@ public final class OpenGroupAPI: NSObject {
|
|||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: FileUploadResponse.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
.decoded(as: FileUploadResponse.self, on: OpenGroupAPI.workQueue, using: dependencies)
|
||||
}
|
||||
|
||||
/// Warning: This approach is less efficient as it expects the data to be base64Encoded (with is 33% larger than binary), please use the binary approach
|
||||
/// whenever possible
|
||||
public static func uploadFile(_ base64EncodedString: String, fileName: String? = nil, to roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, FileUploadResponse)> {
|
||||
let request: Request = Request(
|
||||
method: .post,
|
||||
server: server,
|
||||
endpoint: .roomFileJson(roomToken),
|
||||
headers: [
|
||||
.contentDisposition: [ "attachment", fileName.map { "filename=\"\($0)\"" } ]
|
||||
.compactMap{ $0 }
|
||||
.joined(separator: "; "),
|
||||
],
|
||||
body: base64EncodedString
|
||||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: FileUploadResponse.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
}
|
||||
|
||||
public static func downloadFile(_ fileId: Int64, from roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, Data)> {
|
||||
let request: Request = Request<NoBody>(
|
||||
public static func downloadFile(_ fileId: UInt64, from roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, Data)> {
|
||||
let request: Request = Request<NoBody, Endpoint>(
|
||||
server: server,
|
||||
endpoint: .roomFileIndividual(roomToken, fileId)
|
||||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.map { responseInfo, maybeData in
|
||||
guard let data: Data = maybeData else { throw Error.parsingFailed }
|
||||
guard let data: Data = maybeData else { throw HTTP.Error.parsingFailed }
|
||||
|
||||
return (responseInfo, data)
|
||||
}
|
||||
}
|
||||
|
||||
public static func downloadFileJson(_ fileId: Int64, from roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, FileDownloadResponse)> {
|
||||
let request: Request = Request<NoBody>(
|
||||
public static func downloadFileJson(_ fileId: UInt64, from roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, FileDownloadResponse)> {
|
||||
let request: Request = Request<NoBody, Endpoint>(
|
||||
server: server,
|
||||
endpoint: .roomFileIndividualJson(roomToken, fileId)
|
||||
)
|
||||
// TODO: This endpoint is getting rewritten to return just data (properties would come through as headers).
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: FileDownloadResponse.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
.decoded(as: FileDownloadResponse.self, on: OpenGroupAPI.workQueue, using: dependencies)
|
||||
}
|
||||
|
||||
// MARK: - Inbox/Outbox (Message Requests)
|
||||
|
@ -595,13 +577,13 @@ public final class OpenGroupAPI: NSObject {
|
|||
/// `OpenGroupManager.handleDirectMessages` method to ensure things are processed correctly
|
||||
@available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead")
|
||||
public static func inbox(on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, [DirectMessage]?)> {
|
||||
let request: Request = Request<NoBody>(
|
||||
let request: Request = Request<NoBody, Endpoint>(
|
||||
server: server,
|
||||
endpoint: .inbox
|
||||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: [DirectMessage]?.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
.decoded(as: [DirectMessage]?.self, on: OpenGroupAPI.workQueue, using: dependencies)
|
||||
}
|
||||
|
||||
/// Polls for any DMs received since the given id, this method will return a `304` with an empty response if there are no messages
|
||||
|
@ -611,13 +593,13 @@ public final class OpenGroupAPI: NSObject {
|
|||
/// of this method to the `OpenGroupManager.handleDirectMessages` method to ensure things are processed correctly
|
||||
@available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead")
|
||||
public static func inboxSince(id: Int64, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, [DirectMessage]?)> {
|
||||
let request: Request = Request<NoBody>(
|
||||
let request: Request = Request<NoBody, Endpoint>(
|
||||
server: server,
|
||||
endpoint: .inboxSince(id: id)
|
||||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: [DirectMessage]?.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
.decoded(as: [DirectMessage]?.self, on: OpenGroupAPI.workQueue, using: dependencies)
|
||||
}
|
||||
|
||||
/// Delivers a direct message to a user via their blinded Session ID
|
||||
|
@ -631,12 +613,12 @@ public final class OpenGroupAPI: NSObject {
|
|||
let request: Request = Request(
|
||||
method: .post,
|
||||
server: server,
|
||||
endpoint: .inboxFor(sessionId: blindedSessionId),
|
||||
endpoint: Endpoint.inboxFor(sessionId: blindedSessionId),
|
||||
body: requestBody
|
||||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: SendDirectMessageResponse.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
.decoded(as: SendDirectMessageResponse.self, on: OpenGroupAPI.workQueue, using: dependencies)
|
||||
.map { response, message in
|
||||
// Store the 'message.posted' timestamp to prevent the sent message getting duplicated when it is later retrieved
|
||||
dependencies.storage.write { transaction in
|
||||
|
@ -655,13 +637,13 @@ public final class OpenGroupAPI: NSObject {
|
|||
/// this method to the `OpenGroupManager.handleDirectMessages` method to ensure things are processed correctly
|
||||
@available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead")
|
||||
public static func outbox(on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, [DirectMessage]?)> {
|
||||
let request: Request = Request<NoBody>(
|
||||
let request: Request = Request<NoBody, Endpoint>(
|
||||
server: server,
|
||||
endpoint: .outbox
|
||||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: [DirectMessage]?.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
.decoded(as: [DirectMessage]?.self, on: OpenGroupAPI.workQueue, using: dependencies)
|
||||
}
|
||||
|
||||
/// Polls for any DMs sent since the given id, this method will return a `304` with an empty response if there are no messages
|
||||
|
@ -671,13 +653,13 @@ public final class OpenGroupAPI: NSObject {
|
|||
/// to route the response of this method to the `OpenGroupManager.handleDirectMessages` method to ensure things are processed correctly
|
||||
@available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead")
|
||||
public static func outboxSince(id: Int64, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, [DirectMessage]?)> {
|
||||
let request: Request = Request<NoBody>(
|
||||
let request: Request = Request<NoBody, Endpoint>(
|
||||
server: server,
|
||||
endpoint: .outboxSince(id: id)
|
||||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: [DirectMessage]?.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
.decoded(as: [DirectMessage]?.self, on: OpenGroupAPI.workQueue, using: dependencies)
|
||||
}
|
||||
|
||||
// MARK: - Users
|
||||
|
@ -729,7 +711,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
let request: Request = Request(
|
||||
method: .post,
|
||||
server: server,
|
||||
endpoint: .userBan(sessionId),
|
||||
endpoint: Endpoint.userBan(sessionId),
|
||||
body: requestBody
|
||||
)
|
||||
|
||||
|
@ -774,7 +756,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
let request: Request = Request(
|
||||
method: .post,
|
||||
server: server,
|
||||
endpoint: .userUnban(sessionId),
|
||||
endpoint: Endpoint.userUnban(sessionId),
|
||||
body: requestBody
|
||||
)
|
||||
|
||||
|
@ -841,7 +823,9 @@ public final class OpenGroupAPI: NSObject {
|
|||
on server: String,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> Promise<(OnionRequestResponseInfoType, Data?)> {
|
||||
guard (moderator != nil && admin == nil) || (moderator == nil && admin != nil) else { return Promise(error: Error.generic) }
|
||||
guard (moderator != nil && admin == nil) || (moderator == nil && admin != nil) else {
|
||||
return Promise(error: HTTP.Error.generic)
|
||||
}
|
||||
|
||||
let requestBody: UserModeratorRequest = UserModeratorRequest(
|
||||
rooms: roomTokens,
|
||||
|
@ -854,7 +838,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
let request: Request = Request(
|
||||
method: .post,
|
||||
server: server,
|
||||
endpoint: .userModerator(sessionId),
|
||||
endpoint: Endpoint.userModerator(sessionId),
|
||||
body: requestBody
|
||||
)
|
||||
|
||||
|
@ -894,12 +878,12 @@ public final class OpenGroupAPI: NSObject {
|
|||
let request: Request = Request(
|
||||
method: .post,
|
||||
server: server,
|
||||
endpoint: .userDeleteMessages(sessionId),
|
||||
endpoint: Endpoint.userDeleteMessages(sessionId),
|
||||
body: requestBody
|
||||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: UserDeleteMessagesResponse.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
.decoded(as: UserDeleteMessagesResponse.self, on: OpenGroupAPI.workQueue, using: dependencies)
|
||||
}
|
||||
|
||||
// TODO: Need to test this once the API has been implemented
|
||||
|
@ -1003,11 +987,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
|
||||
/// Get a hash of any body content
|
||||
let bodyHash: Bytes? = {
|
||||
// Note: We need the `!body.isEmpty` check because of the default `Data()` value when trying to
|
||||
// init data from the httpBodyStream
|
||||
guard let body: Data = (request.httpBody ?? request.httpBodyStream.map { ((try? Data(from: $0)) ?? Data()) }), !body.isEmpty else {
|
||||
return nil
|
||||
}
|
||||
guard let body: Data = request.httpBody else { return nil }
|
||||
|
||||
return dependencies.genericHash.hash(message: body.bytes, outputLength: 64)
|
||||
}()
|
||||
|
@ -1045,20 +1025,14 @@ public final class OpenGroupAPI: NSObject {
|
|||
|
||||
// MARK: - Convenience
|
||||
|
||||
private static func send<T: Encodable>(_ request: Request<T>, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, Data?)> {
|
||||
guard let url: URL = request.url else { return Promise(error: Error.invalidURL) }
|
||||
|
||||
var urlRequest: URLRequest = URLRequest(url: url)
|
||||
urlRequest.httpMethod = request.method.rawValue
|
||||
urlRequest.allHTTPHeaderFields = request.headers
|
||||
.setting(.room, request.room) // TODO: Is this needed anymore? Add at the request level?.
|
||||
.toHTTPHeaders()
|
||||
private static func send<T: Encodable>(_ request: Request<T, Endpoint>, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, Data?)> {
|
||||
let urlRequest: URLRequest
|
||||
|
||||
do {
|
||||
urlRequest.httpBody = try request.bodyData()
|
||||
urlRequest = try request.generateUrlRequest()
|
||||
}
|
||||
catch {
|
||||
return Promise(error: Error.parsingFailed)
|
||||
return Promise(error: error)
|
||||
}
|
||||
|
||||
if request.useOnionRouting {
|
||||
|
|
|
@ -46,7 +46,7 @@ public final class OpenGroupManager: NSObject {
|
|||
let groupId: Data = LKGroupUtilities.getEncodedOpenGroupIDAsData("\(server).\(roomToken)")
|
||||
|
||||
if OpenGroupManager.shared.pollers[server] != nil && TSGroupThread.fetch(uniqueId: TSGroupThread.threadId(fromGroupId: groupId), transaction: transaction) != nil {
|
||||
SNLog("Ignoring join open group attempt, user initiated: \(!isConfigMessage)")
|
||||
SNLog("Ignoring join open group attempt (already joined), user initiated: \(!isConfigMessage)")
|
||||
return Promise.value(())
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,7 @@ public final class OpenGroupManager: NSObject {
|
|||
.done(on: DispatchQueue.global(qos: .userInitiated)) { (capabilitiesResponse: (info: OnionRequestResponseInfoType, data: OpenGroupAPI.Capabilities?), roomResponse: (info: OnionRequestResponseInfoType, data: OpenGroupAPI.Room?)) in
|
||||
guard let capabilities: OpenGroupAPI.Capabilities = capabilitiesResponse.data, let room: OpenGroupAPI.Room = roomResponse.data else {
|
||||
SNLog("Failed to join open group due to invalid data.")
|
||||
seal.reject(OpenGroupAPI.Error.generic)
|
||||
seal.reject(HTTP.Error.generic)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -254,7 +254,7 @@ public final class OpenGroupManager: NSObject {
|
|||
}
|
||||
|
||||
// - Room image (if there is one)
|
||||
if let imageId: Int64 = pollInfo.details?.imageId {
|
||||
if let imageId: UInt64 = pollInfo.details?.imageId {
|
||||
OpenGroupManager.roomImage(imageId, for: roomToken, on: server)
|
||||
.done(on: DispatchQueue.global(qos: .userInitiated)) { data in
|
||||
dependencies.storage.write { transaction in
|
||||
|
@ -493,8 +493,8 @@ public final class OpenGroupManager: NSObject {
|
|||
OpenGroupManager.defaultRoomsPromise?
|
||||
.done(on: OpenGroupAPI.workQueue) { items in
|
||||
items
|
||||
.compactMap { room -> (Int64, String)? in
|
||||
guard let imageId: Int64 = room.imageId else { return nil}
|
||||
.compactMap { room -> (UInt64, String)? in
|
||||
guard let imageId: UInt64 = room.imageId else { return nil}
|
||||
|
||||
return (imageId, room.token)
|
||||
}
|
||||
|
@ -511,7 +511,7 @@ public final class OpenGroupManager: NSObject {
|
|||
}
|
||||
|
||||
public static func roomImage(
|
||||
_ fileId: Int64,
|
||||
_ fileId: UInt64,
|
||||
for roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()
|
||||
|
|
|
@ -1,94 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import SessionUtilitiesKit
|
||||
|
||||
extension OpenGroupAPI {
|
||||
struct Empty: Codable {}
|
||||
|
||||
typealias NoBody = Empty
|
||||
typealias NoResponse = Empty
|
||||
|
||||
struct Request<T: Encodable> {
|
||||
let method: HTTP.Verb
|
||||
let server: String
|
||||
let room: String? // TODO: Remove this?
|
||||
let endpoint: Endpoint
|
||||
let queryParameters: [QueryParam: String]
|
||||
let headers: [Header: String]
|
||||
/// This is the body value sent during the request
|
||||
///
|
||||
/// **Warning:** The `bodyData` value should be used to when making the actual request instead of this as there
|
||||
/// is custom handling for certain data types
|
||||
let body: T?
|
||||
let isAuthRequired: Bool
|
||||
/// Always `true` under normal circumstances. You might want to disable
|
||||
/// this when running over Lokinet.
|
||||
let useOnionRouting: Bool
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
init(
|
||||
method: HTTP.Verb = .get,
|
||||
server: String,
|
||||
room: String? = nil,
|
||||
endpoint: Endpoint,
|
||||
queryParameters: [QueryParam: String] = [:],
|
||||
headers: [Header: String] = [:],
|
||||
body: T? = nil,
|
||||
isAuthRequired: Bool = true,
|
||||
useOnionRouting: Bool = true
|
||||
) {
|
||||
self.method = method
|
||||
self.server = server
|
||||
self.room = room
|
||||
self.endpoint = endpoint
|
||||
self.queryParameters = queryParameters
|
||||
self.headers = headers
|
||||
self.body = body
|
||||
self.isAuthRequired = isAuthRequired
|
||||
self.useOnionRouting = useOnionRouting
|
||||
}
|
||||
|
||||
// MARK: - Convenience
|
||||
|
||||
var url: URL? {
|
||||
return URL(string: "\(server)\(urlPathAndParamsString)")
|
||||
}
|
||||
|
||||
var urlPathAndParamsString: String {
|
||||
return [
|
||||
"/\(endpoint.path)",
|
||||
queryParameters
|
||||
.map { key, value in "\(key.rawValue)=\(value)" }
|
||||
.joined(separator: "&")
|
||||
]
|
||||
.compactMap { $0 }
|
||||
.filter { !$0.isEmpty }
|
||||
.joined(separator: "?")
|
||||
}
|
||||
|
||||
func bodyData() throws -> Data? {
|
||||
// Note: Need to differentiate between JSON, b64 string and bytes body values to ensure they are
|
||||
// encoded correctly so the server knows how to handle them
|
||||
switch body {
|
||||
case let bodyString as String:
|
||||
// The only acceptable string body is a base64 encoded one
|
||||
guard let encodedData: Data = Data(base64Encoded: bodyString) else {
|
||||
throw OpenGroupAPI.Error.parsingFailed
|
||||
}
|
||||
|
||||
return encodedData
|
||||
|
||||
case let bodyBytes as [UInt8]:
|
||||
return Data(bodyBytes)
|
||||
|
||||
default:
|
||||
// Having no body is fine so just return nil
|
||||
guard let body: T = body else { return nil }
|
||||
|
||||
return try JSONEncoder().encode(body)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
import Foundation
|
||||
|
||||
extension OpenGroupAPI {
|
||||
public enum Endpoint: Hashable {
|
||||
public enum Endpoint: EndpointType {
|
||||
// Utility
|
||||
|
||||
case onion
|
||||
|
@ -34,9 +34,8 @@ extension OpenGroupAPI {
|
|||
// Files
|
||||
|
||||
case roomFile(String)
|
||||
case roomFileJson(String)
|
||||
case roomFileIndividual(String, Int64)
|
||||
case roomFileIndividualJson(String, Int64)
|
||||
case roomFileIndividual(String, UInt64)
|
||||
case roomFileIndividualJson(String, UInt64)
|
||||
|
||||
// Inbox/Outbox (Message Requests)
|
||||
|
||||
|
@ -126,7 +125,6 @@ extension OpenGroupAPI {
|
|||
// Files
|
||||
|
||||
case .roomFile(let roomToken): return "room/\(roomToken)/file"
|
||||
case .roomFileJson(let roomToken): return "room/\(roomToken)/fileJSON"
|
||||
case .roomFileIndividual(let roomToken, let fileId):
|
||||
// Note: The 'fileName' value is ignored by the server and is only used to distinguish
|
||||
// this from the 'Json' variant
|
|
@ -4,20 +4,14 @@ import Foundation
|
|||
|
||||
extension OpenGroupAPI {
|
||||
public enum Error: LocalizedError {
|
||||
case generic
|
||||
case parsingFailed
|
||||
case decryptionFailed
|
||||
case signingFailed
|
||||
case invalidURL
|
||||
case noPublicKey
|
||||
|
||||
public var errorDescription: String? {
|
||||
switch self {
|
||||
case .generic: return "An error occurred."
|
||||
case .parsingFailed: return "Invalid response."
|
||||
case .decryptionFailed: return "Couldn't decrypt response."
|
||||
case .signingFailed: return "Couldn't sign message."
|
||||
case .invalidURL: return "Invalid URL."
|
||||
case .noPublicKey: return "Couldn't find server public key."
|
||||
}
|
||||
}
|
|
@ -379,7 +379,7 @@ public final class MessageSender : NSObject {
|
|||
|
||||
// Send the result
|
||||
|
||||
guard case .openGroup(let room, let server, let whisperTo, let whisperMods, _) = destination else {
|
||||
guard case .openGroup(let room, let server, let whisperTo, let whisperMods, let fileIds) = destination else {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
|
@ -389,7 +389,8 @@ public final class MessageSender : NSObject {
|
|||
to: room,
|
||||
on: server,
|
||||
whisperTo: whisperTo,
|
||||
whisperMods: whisperMods
|
||||
whisperMods: whisperMods,
|
||||
fileIds: fileIds
|
||||
)
|
||||
.done(on: DispatchQueue.global(qos: .userInitiated)) { responseInfo, data in
|
||||
message.openGroupServerMessageID = UInt64(data.id)
|
||||
|
|
|
@ -45,6 +45,7 @@ public protocol SessionMessagingKitStorageProtocol {
|
|||
func getAllPendingJobs(of type: Job.Type) -> [Job]
|
||||
func getAttachmentUploadJob(for attachmentID: String) -> AttachmentUploadJob?
|
||||
func getMessageSendJob(for messageSendJobID: String) -> MessageSendJob?
|
||||
func getMessageSendJob(for messageSendJobID: String, using transaction: Any) -> MessageSendJob?
|
||||
func resumeMessageSendJobIfNeeded(_ messageSendJobID: String)
|
||||
func isJobCanceled(_ job: Job) -> Bool
|
||||
|
||||
|
|
|
@ -13,13 +13,16 @@ extension Promise where T == Data {
|
|||
}
|
||||
|
||||
extension Promise where T == (OnionRequestResponseInfoType, Data?) {
|
||||
func decoded<R: Decodable>(as type: R.Type, on queue: DispatchQueue? = nil, error: Error? = nil, using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()) -> Promise<(OnionRequestResponseInfoType, R)> {
|
||||
func decoded<R: Decodable>(as type: R.Type, on queue: DispatchQueue? = nil, using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()) -> Promise<(OnionRequestResponseInfoType, R)> {
|
||||
self.map(on: queue) { responseInfo, maybeData -> (OnionRequestResponseInfoType, R) in
|
||||
guard let data: Data = maybeData else {
|
||||
throw OpenGroupAPI.Error.parsingFailed
|
||||
}
|
||||
guard let data: Data = maybeData else { throw HTTP.Error.parsingFailed }
|
||||
|
||||
return (responseInfo, try data.decoded(as: type, customError: error, using: dependencies))
|
||||
do {
|
||||
return (responseInfo, try data.decoded(as: type, using: dependencies))
|
||||
}
|
||||
catch {
|
||||
throw HTTP.Error.parsingFailed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -252,7 +252,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
|
||||
expect(error?.localizedDescription)
|
||||
.toEventually(
|
||||
equal(OpenGroupAPI.Error.parsingFailed.localizedDescription),
|
||||
equal(HTTP.Error.parsingFailed.localizedDescription),
|
||||
timeout: .milliseconds(100)
|
||||
)
|
||||
|
||||
|
@ -272,7 +272,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
|
||||
expect(error?.localizedDescription)
|
||||
.toEventually(
|
||||
equal(OpenGroupAPI.Error.parsingFailed.localizedDescription),
|
||||
equal(HTTP.Error.parsingFailed.localizedDescription),
|
||||
timeout: .milliseconds(100)
|
||||
)
|
||||
|
||||
|
@ -292,7 +292,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
|
||||
expect(error?.localizedDescription)
|
||||
.toEventually(
|
||||
equal(OpenGroupAPI.Error.parsingFailed.localizedDescription),
|
||||
equal(HTTP.Error.parsingFailed.localizedDescription),
|
||||
timeout: .milliseconds(100)
|
||||
)
|
||||
|
||||
|
@ -312,7 +312,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
|
||||
expect(error?.localizedDescription)
|
||||
.toEventually(
|
||||
equal(OpenGroupAPI.Error.parsingFailed.localizedDescription),
|
||||
equal(HTTP.Error.parsingFailed.localizedDescription),
|
||||
timeout: .milliseconds(100)
|
||||
)
|
||||
|
||||
|
@ -364,7 +364,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
|
||||
expect(error?.localizedDescription)
|
||||
.toEventually(
|
||||
equal(OpenGroupAPI.Error.parsingFailed.localizedDescription),
|
||||
equal(HTTP.Error.parsingFailed.localizedDescription),
|
||||
timeout: .milliseconds(100)
|
||||
)
|
||||
|
||||
|
@ -413,7 +413,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
|
||||
expect(error?.localizedDescription)
|
||||
.toEventually(
|
||||
equal(OpenGroupAPI.Error.parsingFailed.localizedDescription),
|
||||
equal(HTTP.Error.parsingFailed.localizedDescription),
|
||||
timeout: .milliseconds(100)
|
||||
)
|
||||
|
||||
|
@ -449,7 +449,8 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(requestData?.urlString).to(equal("testServer/room/testRoom/file"))
|
||||
expect(requestData?.httpMethod).to(equal("POST"))
|
||||
expect(requestData?.headers).to(haveCount(4))
|
||||
expect(requestData?.headers.keys).toNot(contain(Header.fileName.rawValue))
|
||||
expect(requestData?.headers[Header.contentDisposition.rawValue])
|
||||
.toNot(contain("filename"))
|
||||
}
|
||||
|
||||
it("adds a fileName header when provided") {
|
||||
|
@ -477,7 +478,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
expect(requestData?.urlString).to(equal("testServer/room/testRoom/file"))
|
||||
expect(requestData?.httpMethod).to(equal("POST"))
|
||||
expect(requestData?.headers).to(haveCount(5))
|
||||
expect(requestData?.headers[Header.fileName.rawValue]).to(equal("TestFileName"))
|
||||
expect(requestData?.headers[Header.contentDisposition.rawValue]).to(contain("TestFileName"))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,24 +14,22 @@ internal extension OnionRequestAPI {
|
|||
}
|
||||
|
||||
/// Encrypts `payload` for `destination` and returns the result. Use this to build the core of an onion request.
|
||||
static func encrypt(_ payload: String, for destination: Destination, with version: Version) -> Promise<AESGCM.EncryptionResult> {
|
||||
static func encrypt(_ payload: Data, for destination: Destination, with version: Version) -> Promise<AESGCM.EncryptionResult> {
|
||||
let (promise, seal) = Promise<AESGCM.EncryptionResult>.pending()
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
do {
|
||||
guard let payloadAsData: Data = payload.data(using: .utf8) else { throw Error.invalidRequestInfo }
|
||||
|
||||
let data: Data
|
||||
|
||||
switch version {
|
||||
case .v2, .v3:
|
||||
// Wrapping is only needed for snode requests
|
||||
switch destination {
|
||||
case .snode: data = try encode(ciphertext: payloadAsData, json: [ "headers" : "" ])
|
||||
case .server: data = payloadAsData
|
||||
case .snode: data = try encode(ciphertext: payload, json: [ "headers" : "" ])
|
||||
case .server: data = payload
|
||||
}
|
||||
|
||||
case .v4:
|
||||
data = payloadAsData
|
||||
data = payload
|
||||
}
|
||||
|
||||
let result = try encrypt(data, for: destination)
|
||||
|
|
|
@ -249,7 +249,7 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
|||
}
|
||||
|
||||
/// Builds an onion around `payload` and returns the result.
|
||||
private static func buildOnion(around payload: String, targetedAt destination: Destination, version: Version) -> Promise<OnionBuildingResult> {
|
||||
private static func buildOnion(around payload: Data, targetedAt destination: Destination, version: Version) -> Promise<OnionBuildingResult> {
|
||||
var guardSnode: Snode!
|
||||
var targetSnodeSymmetricKey: Data! // Needed by invoke(_:on:with:) to decrypt the response sent back by the destination
|
||||
var encryptionResult: AESGCM.EncryptionResult!
|
||||
|
@ -287,7 +287,7 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
|||
public static func sendOnionRequest(to snode: Snode, invoking method: Snode.Method, with parameters: JSON, using version: Version, associatedWith publicKey: String?) -> Promise<Data> {
|
||||
let payloadJson: JSON = [ "method": method.rawValue, "params": parameters ]
|
||||
|
||||
guard let jsonData: Data = try? JSONSerialization.data(withJSONObject: payloadJson, options: []), let payload: String = String(data: jsonData, encoding: .utf8) else {
|
||||
guard let payload: Data = try? JSONSerialization.data(withJSONObject: payloadJson, options: []) else {
|
||||
return Promise(error: HTTP.Error.invalidJSON)
|
||||
}
|
||||
|
||||
|
@ -316,7 +316,7 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
|||
let scheme: String? = url.scheme
|
||||
let port: UInt16? = url.port.map { UInt16($0) }
|
||||
|
||||
guard let payload: String = generatePayload(for: request, with: version) else {
|
||||
guard let payload: Data = generatePayload(for: request, with: version) else {
|
||||
return Promise(error: Error.invalidRequestInfo)
|
||||
}
|
||||
|
||||
|
@ -328,7 +328,7 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
|||
return promise
|
||||
}
|
||||
|
||||
public static func sendOnionRequest(with payload: String, to destination: Destination, version: Version) -> Promise<(OnionRequestResponseInfoType, Data?)> {
|
||||
public static func sendOnionRequest(with payload: Data, to destination: Destination, version: Version) -> Promise<(OnionRequestResponseInfoType, Data?)> {
|
||||
let (promise, seal) = Promise<(OnionRequestResponseInfoType, Data?)>.pending()
|
||||
var guardSnode: Snode?
|
||||
Threading.workQueue.async { // Avoid race conditions on `guardSnodes` and `paths`
|
||||
|
@ -451,7 +451,7 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
|||
|
||||
// MARK: - Version Handling
|
||||
|
||||
private static func generatePayload(for request: URLRequest, with version: Version) -> String? {
|
||||
private static func generatePayload(for request: URLRequest, with version: Version) -> Data? {
|
||||
guard let url = request.url else { return nil }
|
||||
|
||||
switch version {
|
||||
|
@ -475,10 +475,6 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
|||
headers["Content-Type"] = "application/json" // Assume data is JSON
|
||||
bodyAsString = (String(data: body, encoding: .utf8) ?? "null")
|
||||
}
|
||||
else if let inputStream: InputStream = request.httpBodyStream, let body: Data = try? Data(from: inputStream) {
|
||||
headers["Content-Type"] = request.allHTTPHeaderFields!["Content-Type"]
|
||||
bodyAsString = "{ \"fileUpload\" : \"\(String(data: body.base64EncodedData(), encoding: .utf8) ?? "null")\" }"
|
||||
}
|
||||
else {
|
||||
bodyAsString = "null"
|
||||
}
|
||||
|
@ -492,7 +488,7 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
|||
|
||||
guard let jsonData: Data = try? JSONSerialization.data(withJSONObject: payload, options: []) else { return nil }
|
||||
|
||||
return String(data: jsonData, encoding: .utf8)
|
||||
return jsonData
|
||||
|
||||
// V4 Onion Requests have a very different structure
|
||||
case .v4:
|
||||
|
@ -502,12 +498,12 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
|||
.appending(url.query.map { value in "?\(value)" })
|
||||
|
||||
let requestInfo: RequestInfo = RequestInfo(
|
||||
method: (request.httpMethod ?? "GET"), // Default (if nil) is 'GET'
|
||||
method: (request.httpMethod ?? "GET"), // The default (if nil) is 'GET'
|
||||
endpoint: endpoint,
|
||||
headers: (request.allHTTPHeaderFields ?? [:])
|
||||
.setting(
|
||||
"Content-Type",
|
||||
(request.httpBody == nil && request.httpBodyStream == nil ? nil :
|
||||
(request.httpBody == nil ? nil :
|
||||
// Default to JSON if not defined
|
||||
((request.allHTTPHeaderFields ?? [:])["Content-Type"] ?? "application/json")
|
||||
)
|
||||
|
@ -515,26 +511,17 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
|||
.removingValue(forKey: "User-Agent")
|
||||
)
|
||||
|
||||
guard let requestInfoData: Data = try? JSONEncoder().encode(requestInfo), let requestInfoString: String = String(data: requestInfoData, encoding: .ascii) else {
|
||||
/// Generate the Bencoded payload in the form `l{requestInfoLength}:{requestInfo}{bodyLength}:{body}e`
|
||||
guard let requestInfoData: Data = try? JSONEncoder().encode(requestInfo) else { return nil }
|
||||
guard let prefixData: Data = "l\(requestInfoData.count):".data(using: .ascii), let suffixData: Data = "e".data(using: .ascii) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if let body: Data = request.httpBody {
|
||||
guard let bodyString: String = String(data: body, encoding: .ascii) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return "l\(requestInfoString.count):\(requestInfoString)\(bodyString.count):\(bodyString)e"
|
||||
}
|
||||
else if let inputStream: InputStream = request.httpBodyStream, let body: Data = try? Data(from: inputStream), let bodyString: String = String(data: body, encoding: .ascii) {
|
||||
// TODO: Handle this properly
|
||||
// headers["Content-Type"] = request.allHTTPHeaderFields!["Content-Type"]
|
||||
// bodyAsString = "{ \"fileUpload\" : \"\(String(data: body.base64EncodedData(), encoding: .utf8) ?? "null")\" }"
|
||||
return "l\(requestInfoString.count):\(requestInfoString)\(bodyString.count):\(bodyString)e"
|
||||
}
|
||||
else {
|
||||
return "l\(requestInfoString.count):\(requestInfoString)e"
|
||||
if let body: Data = request.httpBody, let bodyCountData: Data = "\(body.count):".data(using: .ascii) {
|
||||
return (prefixData + requestInfoData + bodyCountData + body + suffixData)
|
||||
}
|
||||
|
||||
return (prefixData + requestInfoData + suffixData)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -653,12 +640,6 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
|||
return seal.fulfill((responseInfo, nil))
|
||||
}
|
||||
|
||||
// TODO: Is this going to be done anymore...???
|
||||
// if let timestamp = body["t"] as? Int64 {
|
||||
// let offset = timestamp - Int64(NSDate.millisecondTimestamp())
|
||||
// SnodeAPI.clockOffset = offset
|
||||
// }
|
||||
|
||||
// Extract the response data as well
|
||||
let dataString: String = String(responseString.suffix(from: infoStringEndIndex))
|
||||
let dataStringParts: [String.SubSequence] = dataString.split(separator: ":")
|
||||
|
|
|
@ -78,18 +78,23 @@ public enum HTTP {
|
|||
|
||||
// MARK: - Error
|
||||
|
||||
public enum Error : LocalizedError {
|
||||
public enum Error: LocalizedError, Equatable {
|
||||
case generic
|
||||
case httpRequestFailed(statusCode: UInt, data: Data?)
|
||||
case invalidURL
|
||||
case invalidJSON
|
||||
case parsingFailed
|
||||
case invalidResponse
|
||||
|
||||
case maxFileSizeExceeded
|
||||
case httpRequestFailed(statusCode: UInt, data: Data?)
|
||||
|
||||
public var errorDescription: String? {
|
||||
switch self {
|
||||
case .generic: return "An error occurred."
|
||||
case .httpRequestFailed(let statusCode, _): return "HTTP request failed with status code: \(statusCode)."
|
||||
case .invalidURL: return "Invalid URL."
|
||||
case .invalidJSON: return "Invalid JSON."
|
||||
case .invalidResponse: return "Invalid Response"
|
||||
case .parsingFailed, .invalidResponse: return "Invalid response."
|
||||
case .maxFileSizeExceeded: return "Maximum file size exceeded."
|
||||
case .httpRequestFailed(let statusCode, _): return "HTTP request failed with status code: \(statusCode)."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,79 +39,108 @@ extension MessageSender {
|
|||
}
|
||||
|
||||
public static func sendNonDurably(_ message: VisibleMessage, with attachmentIDs: [String], in thread: TSThread, using transaction: YapDatabaseReadWriteTransaction) -> Promise<Void> {
|
||||
let attachments = attachmentIDs.compactMap { TSAttachment.fetch(uniqueId: $0, transaction: transaction) as? TSAttachmentStream }
|
||||
let attachments = attachmentIDs.compactMap {
|
||||
TSAttachment.fetch(uniqueId: $0, transaction: transaction) as? TSAttachmentStream
|
||||
}
|
||||
let attachmentsToUpload = attachments.filter { !$0.isUploaded }
|
||||
let attachmentUploadPromises: [Promise<Void>] = attachmentsToUpload.map { stream in
|
||||
let attachmentUploadPromises: [Promise<UInt64>] = attachmentsToUpload.map { stream in
|
||||
let storage = SNMessagingKitConfiguration.shared.storage
|
||||
if let openGroup = storage.getOpenGroup(for: thread.uniqueId!) {
|
||||
let (promise, seal) = Promise<Void>.pending()
|
||||
|
||||
if let threadId: String = thread.uniqueId, let openGroup = storage.getOpenGroup(for: threadId) {
|
||||
let (promise, seal) = Promise<UInt64>.pending()
|
||||
AttachmentUploadJob.upload(
|
||||
stream,
|
||||
using: { data in
|
||||
// TODO: Update to non-legacy version.
|
||||
OpenGroupAPI.legacyUpload(
|
||||
data,
|
||||
to: openGroup.room,
|
||||
on: openGroup.server
|
||||
)
|
||||
OpenGroupAPI
|
||||
.uploadFile(
|
||||
data.bytes,
|
||||
to: openGroup.room,
|
||||
on: openGroup.server
|
||||
)
|
||||
.map { _, response -> UInt64 in response.id }
|
||||
},
|
||||
encrypt: false,
|
||||
onSuccess: { seal.fulfill(()) },
|
||||
onSuccess: { fileId in seal.fulfill(fileId) },
|
||||
onFailure: { seal.reject($0) }
|
||||
)
|
||||
|
||||
return promise
|
||||
} else {
|
||||
let (promise, seal) = Promise<Void>.pending()
|
||||
AttachmentUploadJob.upload(stream, using: FileServerAPIV2.upload, encrypt: true, onSuccess: { seal.fulfill(()) }, onFailure: { seal.reject($0) })
|
||||
return promise
|
||||
}
|
||||
|
||||
let (promise, seal) = Promise<UInt64>.pending()
|
||||
AttachmentUploadJob.upload(
|
||||
stream,
|
||||
using: FileServerAPIV2.upload,
|
||||
encrypt: true,
|
||||
onSuccess: { fileId in seal.fulfill(fileId) },
|
||||
onFailure: { seal.reject($0) }
|
||||
)
|
||||
return promise
|
||||
}
|
||||
return when(resolved: attachmentUploadPromises).then(on: DispatchQueue.global(qos: .userInitiated)) { results -> Promise<Void> in
|
||||
let errors = results.compactMap { result -> Swift.Error? in
|
||||
if case .rejected(let error) = result { return error } else { return nil }
|
||||
|
||||
return when(resolved: attachmentUploadPromises)
|
||||
.then(on: DispatchQueue.global(qos: .userInitiated)) { results -> Promise<Void> in
|
||||
let errors = results.compactMap { result -> Swift.Error? in
|
||||
if case .rejected(let error) = result { return error } else { return nil }
|
||||
}
|
||||
if let error = errors.first { return Promise(error: error) }
|
||||
let fileIds: [UInt64] = results.compactMap { result -> UInt64? in
|
||||
switch result {
|
||||
case .fulfilled(let fileId): return fileId
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
return sendNonDurably(message, in: thread, with: fileIds, using: transaction)
|
||||
}
|
||||
if let error = errors.first { return Promise(error: error) }
|
||||
return sendNonDurably(message, in: thread, using: transaction)
|
||||
}
|
||||
}
|
||||
|
||||
public static func sendNonDurably(_ message: Message, in thread: TSThread, using transaction: YapDatabaseReadWriteTransaction) -> Promise<Void> {
|
||||
public static func sendNonDurably(_ message: Message, in thread: TSThread, with fileIds: [UInt64]? = nil, using transaction: YapDatabaseReadWriteTransaction) -> Promise<Void> {
|
||||
message.threadID = thread.uniqueId!
|
||||
let destination = Message.Destination.from(thread)
|
||||
let destination = Message.Destination.from(thread, fileIds: fileIds)
|
||||
return MessageSender.send(message, to: destination, using: transaction)
|
||||
}
|
||||
|
||||
public static func sendNonDurably(_ message: VisibleMessage, with attachments: [SignalAttachment], in thread: TSThread) -> Promise<Void> {
|
||||
Storage.writeSync{ transaction in
|
||||
Storage.writeSync { transaction in
|
||||
prep(attachments, for: message, using: transaction)
|
||||
}
|
||||
let attachments = message.attachmentIDs.compactMap { TSAttachment.fetch(uniqueId: $0) as? TSAttachmentStream }
|
||||
let attachmentsToUpload = attachments.filter { !$0.isUploaded }
|
||||
let attachmentUploadPromises: [Promise<Void>] = attachmentsToUpload.map { stream in
|
||||
let attachmentUploadPromises: [Promise<UInt64>] = attachmentsToUpload.map { stream in
|
||||
let storage = SNMessagingKitConfiguration.shared.storage
|
||||
|
||||
if let openGroup = storage.getOpenGroup(for: thread.uniqueId!) {
|
||||
let (promise, seal) = Promise<Void>.pending()
|
||||
let (promise, seal) = Promise<UInt64>.pending()
|
||||
|
||||
AttachmentUploadJob.upload(
|
||||
stream,
|
||||
using: { data in
|
||||
// TODO: Update to non-legacy version
|
||||
OpenGroupAPI.legacyUpload(
|
||||
data,
|
||||
to: openGroup.room,
|
||||
on: openGroup.server
|
||||
)
|
||||
OpenGroupAPI
|
||||
.uploadFile(
|
||||
data.bytes,
|
||||
to: openGroup.room,
|
||||
on: openGroup.server
|
||||
)
|
||||
.map { _, response in response.id }
|
||||
},
|
||||
encrypt: false,
|
||||
onSuccess: { seal.fulfill(()) },
|
||||
onSuccess: { fileId in seal.fulfill(fileId) },
|
||||
onFailure: { seal.reject($0) }
|
||||
)
|
||||
return promise
|
||||
} else {
|
||||
let (promise, seal) = Promise<Void>.pending()
|
||||
AttachmentUploadJob.upload(stream, using: FileServerAPIV2.upload, encrypt: true, onSuccess: { seal.fulfill(()) }, onFailure: { seal.reject($0) })
|
||||
return promise
|
||||
}
|
||||
|
||||
let (promise, seal) = Promise<UInt64>.pending()
|
||||
AttachmentUploadJob.upload(
|
||||
stream,
|
||||
using: FileServerAPIV2.upload,
|
||||
encrypt: true,
|
||||
onSuccess: { fileId in seal.fulfill(fileId) },
|
||||
onFailure: { seal.reject($0) }
|
||||
)
|
||||
|
||||
return promise
|
||||
}
|
||||
let (promise, seal) = Promise<Void>.pending()
|
||||
let results = when(resolved: attachmentUploadPromises).wait()
|
||||
|
@ -119,13 +148,23 @@ extension MessageSender {
|
|||
if case .rejected(let error) = result { return error } else { return nil }
|
||||
}
|
||||
if let error = errors.first { seal.reject(error) }
|
||||
Storage.write{ transaction in
|
||||
sendNonDurably(message, in: thread, using: transaction).done {
|
||||
seal.fulfill(())
|
||||
}.catch { error in
|
||||
seal.reject(error)
|
||||
let fileIds: [UInt64] = results.compactMap { result -> UInt64? in
|
||||
switch result {
|
||||
case .fulfilled(let fileId): return fileId
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
Storage.write { transaction in
|
||||
sendNonDurably(message, in: thread, with: fileIds, using: transaction)
|
||||
.done {
|
||||
seal.fulfill(())
|
||||
}
|
||||
.catch { error in
|
||||
seal.reject(error)
|
||||
}
|
||||
}
|
||||
|
||||
return promise
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue