2020-11-06 03:46:06 +01:00
|
|
|
import PromiseKit
|
|
|
|
import SessionSnodeKit
|
2020-11-09 00:58:47 +01:00
|
|
|
import SessionUtilitiesKit
|
2020-11-05 23:17:05 +01:00
|
|
|
|
2020-11-19 01:16:23 +01:00
|
|
|
@objc(SNMessageSender)
|
|
|
|
public final class MessageSender : NSObject {
|
2020-11-06 03:46:06 +01:00
|
|
|
|
2020-11-24 10:09:23 +01:00
|
|
|
// MARK: Error
|
2020-11-11 00:58:56 +01:00
|
|
|
public enum Error : LocalizedError {
|
2020-11-06 06:28:06 +01:00
|
|
|
case invalidMessage
|
2020-11-06 03:46:06 +01:00
|
|
|
case protoConversionFailed
|
2020-12-10 06:12:22 +01:00
|
|
|
case noUserX25519KeyPair
|
|
|
|
case noUserED25519KeyPair
|
|
|
|
case signingFailed
|
|
|
|
case encryptionFailed
|
2021-03-03 04:19:38 +01:00
|
|
|
case noUsername
|
2020-11-25 06:15:16 +01:00
|
|
|
// Closed groups
|
|
|
|
case noThread
|
2021-01-04 05:30:13 +01:00
|
|
|
case noKeyPair
|
2020-11-25 06:15:16 +01:00
|
|
|
case invalidClosedGroupUpdate
|
2020-11-06 03:46:06 +01:00
|
|
|
|
2020-11-23 05:46:53 +01:00
|
|
|
internal var isRetryable: Bool {
|
|
|
|
switch self {
|
2021-05-03 07:13:18 +02:00
|
|
|
case .invalidMessage, .protoConversionFailed, .invalidClosedGroupUpdate, .signingFailed, .encryptionFailed: return false
|
2020-11-23 05:46:53 +01:00
|
|
|
default: return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-11 00:58:56 +01:00
|
|
|
public var errorDescription: String? {
|
2020-11-06 03:46:06 +01:00
|
|
|
switch self {
|
2020-11-06 06:28:06 +01:00
|
|
|
case .invalidMessage: return "Invalid message."
|
2020-11-06 03:46:06 +01:00
|
|
|
case .protoConversionFailed: return "Couldn't convert message to proto."
|
2020-12-10 06:12:22 +01:00
|
|
|
case .noUserX25519KeyPair: return "Couldn't find user X25519 key pair."
|
|
|
|
case .noUserED25519KeyPair: return "Couldn't find user ED25519 key pair."
|
|
|
|
case .signingFailed: return "Couldn't sign message."
|
|
|
|
case .encryptionFailed: return "Couldn't encrypt message."
|
2021-03-03 04:19:38 +01:00
|
|
|
case .noUsername: return "Missing username."
|
2020-11-25 06:15:16 +01:00
|
|
|
// Closed groups
|
|
|
|
case .noThread: return "Couldn't find a thread associated with the given group public key."
|
2021-01-04 05:30:13 +01:00
|
|
|
case .noKeyPair: return "Couldn't find a private key associated with the given group public key."
|
2020-11-25 06:15:16 +01:00
|
|
|
case .invalidClosedGroupUpdate: return "Invalid group update."
|
2020-11-06 03:46:06 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-11-24 10:09:23 +01:00
|
|
|
|
|
|
|
// MARK: Initialization
|
2020-11-19 01:16:23 +01:00
|
|
|
private override init() { }
|
2020-11-06 03:46:06 +01:00
|
|
|
|
2020-11-30 22:35:13 +01:00
|
|
|
// MARK: Preparation
|
2020-12-01 05:44:33 +01:00
|
|
|
public static func prep(_ signalAttachments: [SignalAttachment], for message: VisibleMessage, using transaction: YapDatabaseReadWriteTransaction) {
|
2020-11-30 22:35:13 +01:00
|
|
|
guard let tsMessage = TSOutgoingMessage.find(withTimestamp: message.sentTimestamp!) else {
|
|
|
|
#if DEBUG
|
|
|
|
preconditionFailure()
|
2020-12-01 05:44:33 +01:00
|
|
|
#else
|
2020-11-30 22:35:13 +01:00
|
|
|
return
|
2020-12-01 05:44:33 +01:00
|
|
|
#endif
|
2020-11-30 22:35:13 +01:00
|
|
|
}
|
2020-12-01 05:44:33 +01:00
|
|
|
var attachments: [TSAttachmentStream] = []
|
|
|
|
signalAttachments.forEach {
|
|
|
|
let attachment = TSAttachmentStream(contentType: $0.mimeType, byteCount: UInt32($0.dataLength), sourceFilename: $0.sourceFilename,
|
2020-11-30 22:35:13 +01:00
|
|
|
caption: $0.captionText, albumMessageId: tsMessage.uniqueId!)
|
2020-12-01 05:44:33 +01:00
|
|
|
attachments.append(attachment)
|
|
|
|
attachment.write($0.dataSource)
|
|
|
|
attachment.save(with: transaction)
|
2020-11-30 22:35:13 +01:00
|
|
|
}
|
2020-12-07 05:11:49 +01:00
|
|
|
prep(attachments, for: message, using: transaction)
|
|
|
|
}
|
|
|
|
|
|
|
|
@objc(prep:forMessage:usingTransaction:)
|
|
|
|
public static func prep(_ attachmentStreams: [TSAttachmentStream], for message: VisibleMessage, using transaction: YapDatabaseReadWriteTransaction) {
|
|
|
|
guard let tsMessage = TSOutgoingMessage.find(withTimestamp: message.sentTimestamp!) else {
|
|
|
|
#if DEBUG
|
|
|
|
preconditionFailure()
|
|
|
|
#else
|
|
|
|
return
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
var attachments = attachmentStreams
|
2020-12-01 05:44:33 +01:00
|
|
|
// The line below locally generates a thumbnail for the quoted attachment. It just needs to happen at some point during the
|
|
|
|
// message sending process.
|
2020-11-30 22:35:13 +01:00
|
|
|
tsMessage.quotedMessage?.createThumbnailAttachmentsIfNecessary(with: transaction)
|
|
|
|
var linkPreviewAttachmentID: String?
|
|
|
|
if let id = tsMessage.linkPreview?.imageAttachmentId,
|
2020-12-01 05:44:33 +01:00
|
|
|
let attachment = TSAttachment.fetch(uniqueId: id, transaction: transaction) as? TSAttachmentStream {
|
2020-11-30 22:35:13 +01:00
|
|
|
linkPreviewAttachmentID = id
|
2020-12-01 05:44:33 +01:00
|
|
|
attachments.append(attachment)
|
2020-11-30 22:35:13 +01:00
|
|
|
}
|
2020-12-01 05:44:33 +01:00
|
|
|
// Anything added to message.attachmentIDs will be uploaded by an UploadAttachmentJob. Any attachment IDs added to tsMessage will
|
|
|
|
// make it render as an attachment (not what we want in the case of a link preview or quoted attachment).
|
|
|
|
message.attachmentIDs = attachments.map { $0.uniqueId! }
|
2020-12-07 05:11:49 +01:00
|
|
|
tsMessage.attachmentIds.removeAllObjects()
|
2020-11-30 22:35:13 +01:00
|
|
|
tsMessage.attachmentIds.addObjects(from: message.attachmentIDs)
|
|
|
|
if let id = linkPreviewAttachmentID { tsMessage.attachmentIds.remove(id) }
|
|
|
|
tsMessage.save(with: transaction)
|
|
|
|
}
|
|
|
|
|
2020-11-24 10:09:23 +01:00
|
|
|
// MARK: Convenience
|
2020-11-12 06:02:21 +01:00
|
|
|
public static func send(_ message: Message, to destination: Message.Destination, using transaction: Any) -> Promise<Void> {
|
2020-11-08 22:36:33 +01:00
|
|
|
switch destination {
|
|
|
|
case .contact(_), .closedGroup(_): return sendToSnodeDestination(destination, message: message, using: transaction)
|
2021-03-24 03:28:30 +01:00
|
|
|
case .openGroup(_, _), .openGroupV2(_, _): return sendToOpenGroupDestination(destination, message: message, using: transaction)
|
2020-11-08 22:36:33 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-24 10:09:23 +01:00
|
|
|
// MARK: One-on-One Chats & Closed Groups
|
2021-01-14 04:57:32 +01:00
|
|
|
internal static func sendToSnodeDestination(_ destination: Message.Destination, message: Message, using transaction: Any, isSyncMessage: Bool = false) -> Promise<Void> {
|
2020-11-24 04:43:53 +01:00
|
|
|
let (promise, seal) = Promise<Void>.pending()
|
2020-12-02 06:25:16 +01:00
|
|
|
let storage = SNMessagingKitConfiguration.shared.storage
|
2020-12-03 00:39:53 +01:00
|
|
|
let transaction = transaction as! YapDatabaseReadWriteTransaction
|
|
|
|
let userPublicKey = storage.getUserPublicKey()
|
2020-12-07 00:04:38 +01:00
|
|
|
var isMainAppAndActive = false
|
|
|
|
if let sharedUserDefaults = UserDefaults(suiteName: "group.com.loki-project.loki-messenger") {
|
|
|
|
isMainAppAndActive = sharedUserDefaults.bool(forKey: "isMainAppActive")
|
|
|
|
}
|
2020-12-03 00:39:53 +01:00
|
|
|
// Set the timestamp, sender and recipient
|
2020-11-24 10:09:23 +01:00
|
|
|
if message.sentTimestamp == nil { // Visible messages will already have their sent timestamp set
|
2020-11-19 06:21:00 +01:00
|
|
|
message.sentTimestamp = NSDate.millisecondTimestamp()
|
|
|
|
}
|
2020-11-30 05:06:05 +01:00
|
|
|
message.sender = userPublicKey
|
2020-11-10 05:48:47 +01:00
|
|
|
switch destination {
|
|
|
|
case .contact(let publicKey): message.recipient = publicKey
|
|
|
|
case .closedGroup(let groupPublicKey): message.recipient = groupPublicKey
|
2021-03-24 03:28:30 +01:00
|
|
|
case .openGroup(_, _), .openGroupV2(_, _): preconditionFailure()
|
2020-11-10 05:48:47 +01:00
|
|
|
}
|
2020-11-30 05:06:05 +01:00
|
|
|
let isSelfSend = (message.recipient == userPublicKey)
|
2020-12-03 00:39:53 +01:00
|
|
|
// Set the failure handler (need it here already for precondition failure handling)
|
|
|
|
func handleFailure(with error: Swift.Error, using transaction: YapDatabaseReadWriteTransaction) {
|
|
|
|
MessageSender.handleFailedMessageSend(message, with: error, using: transaction)
|
2020-12-03 00:53:30 +01:00
|
|
|
seal.reject(error)
|
2020-11-24 04:43:53 +01:00
|
|
|
}
|
2020-11-07 23:00:10 +01:00
|
|
|
// Validate the message
|
2020-12-03 00:39:53 +01:00
|
|
|
guard message.isValid else { handleFailure(with: Error.invalidMessage, using: transaction); return promise }
|
2021-02-10 00:56:46 +01:00
|
|
|
// Stop here if this is a self-send, unless it's:
|
|
|
|
// • a configuration message
|
|
|
|
// • a sync message
|
|
|
|
// • a closed group control message of type `new`
|
2021-08-02 02:32:47 +02:00
|
|
|
// • an unsend request
|
2021-02-10 00:56:46 +01:00
|
|
|
let isNewClosedGroupControlMessage = given(message as? ClosedGroupControlMessage) { if case .new = $0.kind { return true } else { return false } } ?? false
|
2021-08-02 02:32:47 +02:00
|
|
|
guard !isSelfSend || message is ConfigurationMessage || isSyncMessage || isNewClosedGroupControlMessage || message is UnsendRequest else {
|
2020-12-07 06:00:21 +01:00
|
|
|
storage.write(with: { transaction in
|
2020-11-30 05:20:03 +01:00
|
|
|
MessageSender.handleSuccessfulMessageSend(message, to: destination, using: transaction)
|
2020-12-03 00:12:29 +01:00
|
|
|
seal.fulfill(())
|
2020-12-03 00:53:30 +01:00
|
|
|
}, completion: { })
|
2020-11-30 05:06:05 +01:00
|
|
|
return promise
|
|
|
|
}
|
2020-11-26 05:16:35 +01:00
|
|
|
// Attach the user's profile if needed
|
|
|
|
if let message = message as? VisibleMessage {
|
2021-03-03 04:19:38 +01:00
|
|
|
guard let name = storage.getUser()?.name else { handleFailure(with: Error.noUsername, using: transaction); return promise }
|
2021-07-22 06:52:10 +02:00
|
|
|
if let profileKey = storage.getUser()?.profileEncryptionKey?.keyData, let profilePictureURL = storage.getUser()?.profilePictureURL {
|
2021-02-26 05:56:41 +01:00
|
|
|
message.profile = VisibleMessage.Profile(displayName: name, profileKey: profileKey, profilePictureURL: profilePictureURL)
|
2020-11-26 05:16:35 +01:00
|
|
|
} else {
|
2021-02-26 05:56:41 +01:00
|
|
|
message.profile = VisibleMessage.Profile(displayName: name)
|
2020-11-26 05:16:35 +01:00
|
|
|
}
|
|
|
|
}
|
2020-11-07 23:00:10 +01:00
|
|
|
// Convert it to protobuf
|
2020-12-08 03:17:02 +01:00
|
|
|
guard let proto = message.toProto(using: transaction) else { handleFailure(with: Error.protoConversionFailed, using: transaction); return promise }
|
2020-11-07 23:00:10 +01:00
|
|
|
// Serialize the protobuf
|
2020-11-06 06:28:06 +01:00
|
|
|
let plaintext: Data
|
2020-11-06 03:46:06 +01:00
|
|
|
do {
|
2021-01-14 04:08:44 +01:00
|
|
|
plaintext = (try proto.serializedData() as NSData).paddedMessageBody()
|
2020-11-06 03:46:06 +01:00
|
|
|
} catch {
|
|
|
|
SNLog("Couldn't serialize proto due to error: \(error).")
|
2020-12-03 00:39:53 +01:00
|
|
|
handleFailure(with: error, using: transaction)
|
2020-11-24 04:43:53 +01:00
|
|
|
return promise
|
2020-11-06 09:32:09 +01:00
|
|
|
}
|
2020-11-07 23:00:10 +01:00
|
|
|
// Encrypt the serialized protobuf
|
2020-11-06 09:32:09 +01:00
|
|
|
let ciphertext: Data
|
|
|
|
do {
|
|
|
|
switch destination {
|
2021-01-04 00:53:08 +01:00
|
|
|
case .contact(let publicKey): ciphertext = try encryptWithSessionProtocol(plaintext, for: publicKey)
|
2021-01-05 23:26:49 +01:00
|
|
|
case .closedGroup(let groupPublicKey):
|
|
|
|
guard let encryptionKeyPair = Storage.shared.getLatestClosedGroupEncryptionKeyPair(for: groupPublicKey) else { throw Error.noKeyPair }
|
|
|
|
ciphertext = try encryptWithSessionProtocol(plaintext, for: encryptionKeyPair.hexEncodedPublicKey)
|
2021-03-24 03:28:30 +01:00
|
|
|
case .openGroup(_, _), .openGroupV2(_, _): preconditionFailure()
|
2020-11-06 09:32:09 +01:00
|
|
|
}
|
|
|
|
} catch {
|
|
|
|
SNLog("Couldn't encrypt message for destination: \(destination) due to error: \(error).")
|
2020-12-03 00:39:53 +01:00
|
|
|
handleFailure(with: error, using: transaction)
|
2020-11-24 04:43:53 +01:00
|
|
|
return promise
|
2020-11-06 03:46:06 +01:00
|
|
|
}
|
2020-11-08 22:36:33 +01:00
|
|
|
// Wrap the result
|
|
|
|
let kind: SNProtoEnvelope.SNProtoEnvelopeType
|
|
|
|
let senderPublicKey: String
|
|
|
|
switch destination {
|
|
|
|
case .contact(_):
|
2021-03-02 03:12:24 +01:00
|
|
|
kind = .sessionMessage
|
2020-11-08 22:36:33 +01:00
|
|
|
senderPublicKey = ""
|
|
|
|
case .closedGroup(let groupPublicKey):
|
2021-03-02 03:12:24 +01:00
|
|
|
kind = .closedGroupMessage
|
2020-11-08 22:36:33 +01:00
|
|
|
senderPublicKey = groupPublicKey
|
2021-03-24 03:28:30 +01:00
|
|
|
case .openGroup(_, _), .openGroupV2(_, _): preconditionFailure()
|
2020-11-08 22:36:33 +01:00
|
|
|
}
|
|
|
|
let wrappedMessage: Data
|
|
|
|
do {
|
|
|
|
wrappedMessage = try MessageWrapper.wrap(type: kind, timestamp: message.sentTimestamp!,
|
|
|
|
senderPublicKey: senderPublicKey, base64EncodedContent: ciphertext.base64EncodedString())
|
|
|
|
} catch {
|
|
|
|
SNLog("Couldn't wrap message due to error: \(error).")
|
2020-12-03 00:39:53 +01:00
|
|
|
handleFailure(with: error, using: transaction)
|
2020-11-24 04:43:53 +01:00
|
|
|
return promise
|
2020-11-08 22:36:33 +01:00
|
|
|
}
|
2020-11-07 23:00:10 +01:00
|
|
|
// Send the result
|
2021-04-14 06:05:26 +02:00
|
|
|
let base64EncodedData = wrappedMessage.base64EncodedString()
|
2021-07-23 05:42:13 +02:00
|
|
|
let timestamp = UInt64(Int64(message.sentTimestamp!) + SnodeAPI.clockOffset)
|
|
|
|
let snodeMessage = SnodeMessage(recipient: message.recipient!, data: base64EncodedData, ttl: message.ttl, timestamp: timestamp)
|
2020-11-24 10:09:23 +01:00
|
|
|
SnodeAPI.sendMessage(snodeMessage).done(on: DispatchQueue.global(qos: .userInitiated)) { promises in
|
2020-11-06 09:44:02 +01:00
|
|
|
var isSuccess = false
|
|
|
|
let promiseCount = promises.count
|
|
|
|
var errorCount = 0
|
|
|
|
promises.forEach {
|
2021-08-02 06:49:34 +02:00
|
|
|
let _ = $0.done(on: DispatchQueue.global(qos: .userInitiated)) { rawResponse in
|
2020-11-06 09:44:02 +01:00
|
|
|
guard !isSuccess else { return } // Succeed as soon as the first promise succeeds
|
|
|
|
isSuccess = true
|
2020-12-07 06:00:21 +01:00
|
|
|
storage.write(with: { transaction in
|
2021-08-02 06:49:34 +02:00
|
|
|
let json = rawResponse as? JSON
|
|
|
|
let hash = json?["hash"] as? String
|
|
|
|
message.serverHash = hash
|
2021-01-25 05:50:30 +01:00
|
|
|
MessageSender.handleSuccessfulMessageSend(message, to: destination, isSyncMessage: isSyncMessage, using: transaction)
|
2021-07-30 08:51:43 +02:00
|
|
|
var shouldNotify = ((message is VisibleMessage || message is UnsendRequest) && !isSyncMessage)
|
2021-05-24 00:57:31 +02:00
|
|
|
/*
|
2021-01-22 00:28:26 +01:00
|
|
|
if let closedGroupControlMessage = message as? ClosedGroupControlMessage, case .new = closedGroupControlMessage.kind {
|
2020-12-04 00:00:06 +01:00
|
|
|
shouldNotify = true
|
|
|
|
}
|
2021-05-24 00:57:31 +02:00
|
|
|
*/
|
2020-12-04 00:00:06 +01:00
|
|
|
if shouldNotify {
|
2020-12-03 00:12:29 +01:00
|
|
|
let notifyPNServerJob = NotifyPNServerJob(message: snodeMessage)
|
2020-12-07 00:04:38 +01:00
|
|
|
if isMainAppAndActive {
|
|
|
|
JobQueue.shared.add(notifyPNServerJob, using: transaction)
|
|
|
|
seal.fulfill(())
|
|
|
|
} else {
|
|
|
|
notifyPNServerJob.execute().done(on: DispatchQueue.global(qos: .userInitiated)) {
|
|
|
|
seal.fulfill(())
|
|
|
|
}.catch(on: DispatchQueue.global(qos: .userInitiated)) { _ in
|
|
|
|
seal.fulfill(()) // Always fulfill because the notify PN server job isn't critical.
|
|
|
|
}
|
|
|
|
}
|
2020-12-09 00:15:59 +01:00
|
|
|
} else {
|
|
|
|
seal.fulfill(())
|
2020-12-03 00:12:29 +01:00
|
|
|
}
|
2020-12-03 00:53:30 +01:00
|
|
|
}, completion: { })
|
2020-11-06 09:44:02 +01:00
|
|
|
}
|
2020-11-24 10:09:23 +01:00
|
|
|
$0.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in
|
2020-11-06 09:44:02 +01:00
|
|
|
errorCount += 1
|
|
|
|
guard errorCount == promiseCount else { return } // Only error out if all promises failed
|
2020-12-07 06:00:21 +01:00
|
|
|
storage.write(with: { transaction in
|
2020-12-03 00:39:53 +01:00
|
|
|
handleFailure(with: error, using: transaction as! YapDatabaseReadWriteTransaction)
|
|
|
|
}, completion: { })
|
2020-11-06 09:44:02 +01:00
|
|
|
}
|
|
|
|
}
|
2020-11-24 10:09:23 +01:00
|
|
|
}.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in
|
2020-11-06 09:44:02 +01:00
|
|
|
SNLog("Couldn't send message due to error: \(error).")
|
2020-12-07 06:00:21 +01:00
|
|
|
storage.write(with: { transaction in
|
2020-12-03 00:39:53 +01:00
|
|
|
handleFailure(with: error, using: transaction as! YapDatabaseReadWriteTransaction)
|
|
|
|
}, completion: { })
|
2020-11-06 09:44:02 +01:00
|
|
|
}
|
2020-11-24 10:09:23 +01:00
|
|
|
// Return
|
2020-11-06 09:44:02 +01:00
|
|
|
return promise
|
2020-11-06 03:46:06 +01:00
|
|
|
}
|
2020-11-09 03:56:50 +01:00
|
|
|
|
2020-11-24 10:09:23 +01:00
|
|
|
// MARK: Open Groups
|
2020-11-09 03:56:50 +01:00
|
|
|
internal static func sendToOpenGroupDestination(_ destination: Message.Destination, message: Message, using transaction: Any) -> Promise<Void> {
|
2020-11-24 04:43:53 +01:00
|
|
|
let (promise, seal) = Promise<Void>.pending()
|
2020-12-02 06:25:16 +01:00
|
|
|
let storage = SNMessagingKitConfiguration.shared.storage
|
2020-12-03 00:39:53 +01:00
|
|
|
let transaction = transaction as! YapDatabaseReadWriteTransaction
|
|
|
|
// Set the timestamp, sender and recipient
|
2020-12-01 06:25:31 +01:00
|
|
|
if message.sentTimestamp == nil { // Visible messages will already have their sent timestamp set
|
|
|
|
message.sentTimestamp = NSDate.millisecondTimestamp()
|
|
|
|
}
|
2020-11-30 01:00:28 +01:00
|
|
|
message.sender = storage.getUserPublicKey()
|
2020-11-12 06:02:21 +01:00
|
|
|
switch destination {
|
|
|
|
case .contact(_): preconditionFailure()
|
|
|
|
case .closedGroup(_): preconditionFailure()
|
|
|
|
case .openGroup(let channel, let server): message.recipient = "\(server).\(channel)"
|
2021-03-24 03:28:30 +01:00
|
|
|
case .openGroupV2(let room, let server): message.recipient = "\(server).\(room)"
|
2020-11-12 06:02:21 +01:00
|
|
|
}
|
2020-12-03 00:39:53 +01:00
|
|
|
// Set the failure handler (need it here already for precondition failure handling)
|
|
|
|
func handleFailure(with error: Swift.Error, using transaction: YapDatabaseReadWriteTransaction) {
|
|
|
|
MessageSender.handleFailedMessageSend(message, with: error, using: transaction)
|
2020-12-03 00:53:30 +01:00
|
|
|
seal.reject(error)
|
2020-11-24 04:43:53 +01:00
|
|
|
}
|
2020-11-24 10:09:23 +01:00
|
|
|
// Validate the message
|
2020-11-30 03:18:26 +01:00
|
|
|
guard let message = message as? VisibleMessage else {
|
2020-11-30 01:00:28 +01:00
|
|
|
#if DEBUG
|
|
|
|
preconditionFailure()
|
2020-12-01 05:44:33 +01:00
|
|
|
#else
|
2020-12-03 00:39:53 +01:00
|
|
|
handleFailure(with: Error.invalidMessage, using: transaction)
|
2020-11-30 03:18:26 +01:00
|
|
|
return promise
|
2020-12-01 05:44:33 +01:00
|
|
|
#endif
|
2020-11-30 01:00:28 +01:00
|
|
|
}
|
2020-12-03 00:39:53 +01:00
|
|
|
guard message.isValid else { handleFailure(with: Error.invalidMessage, using: transaction); return promise }
|
2021-05-04 07:46:48 +02:00
|
|
|
// Attach the user's profile
|
|
|
|
guard let name = storage.getUser()?.name else { handleFailure(with: Error.noUsername, using: transaction); return promise }
|
2021-07-22 06:52:10 +02:00
|
|
|
if let profileKey = storage.getUser()?.profileEncryptionKey?.keyData, let profilePictureURL = storage.getUser()?.profilePictureURL {
|
2021-05-04 07:46:48 +02:00
|
|
|
message.profile = VisibleMessage.Profile(displayName: name, profileKey: profileKey, profilePictureURL: profilePictureURL)
|
2021-03-24 03:28:30 +01:00
|
|
|
} else {
|
2021-05-04 07:46:48 +02:00
|
|
|
message.profile = VisibleMessage.Profile(displayName: name)
|
|
|
|
}
|
|
|
|
// Convert it to protobuf
|
|
|
|
guard let proto = message.toProto(using: transaction) else { handleFailure(with: Error.protoConversionFailed, using: transaction); return promise }
|
|
|
|
// Serialize the protobuf
|
|
|
|
let plaintext: Data
|
|
|
|
do {
|
|
|
|
plaintext = (try proto.serializedData() as NSData).paddedMessageBody()
|
|
|
|
} catch {
|
|
|
|
SNLog("Couldn't serialize proto due to error: \(error).")
|
|
|
|
handleFailure(with: error, using: transaction)
|
|
|
|
return promise
|
|
|
|
}
|
|
|
|
// Send the result
|
|
|
|
guard case .openGroupV2(let room, let server) = destination else { preconditionFailure() }
|
|
|
|
let openGroupMessage = OpenGroupMessageV2(serverID: nil, sender: nil, sentTimestamp: message.sentTimestamp!,
|
|
|
|
base64EncodedData: plaintext.base64EncodedString(), base64EncodedSignature: nil)
|
|
|
|
OpenGroupAPIV2.send(openGroupMessage, to: room, on: server).done(on: DispatchQueue.global(qos: .userInitiated)) { openGroupMessage in
|
|
|
|
message.openGroupServerMessageID = given(openGroupMessage.serverID) { UInt64($0) }
|
|
|
|
storage.write(with: { transaction in
|
|
|
|
MessageSender.handleSuccessfulMessageSend(message, to: destination, using: transaction)
|
|
|
|
seal.fulfill(())
|
|
|
|
}, completion: { })
|
|
|
|
}.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in
|
|
|
|
storage.write(with: { transaction in
|
|
|
|
handleFailure(with: error, using: transaction as! YapDatabaseReadWriteTransaction)
|
|
|
|
}, completion: { })
|
2020-11-09 03:56:50 +01:00
|
|
|
}
|
2021-05-04 07:46:48 +02:00
|
|
|
// Return
|
|
|
|
return promise
|
2020-11-09 03:56:50 +01:00
|
|
|
}
|
2020-11-30 22:35:13 +01:00
|
|
|
|
|
|
|
// MARK: Success & Failure Handling
|
2021-01-25 05:50:30 +01:00
|
|
|
public static func handleSuccessfulMessageSend(_ message: Message, to destination: Message.Destination, isSyncMessage: Bool = false, using transaction: Any) {
|
2021-01-21 01:27:52 +01:00
|
|
|
let transaction = transaction as! YapDatabaseReadWriteTransaction
|
2021-01-28 05:49:43 +01:00
|
|
|
// Ignore future self-sends
|
|
|
|
Storage.shared.addReceivedMessageTimestamp(message.sentTimestamp!, using: transaction)
|
2021-02-24 05:19:50 +01:00
|
|
|
// Get the visible message if possible
|
|
|
|
if let tsMessage = TSOutgoingMessage.find(withTimestamp: message.sentTimestamp!) {
|
2021-08-04 03:49:21 +02:00
|
|
|
if (!isSyncMessage) { tsMessage.serverHash = message.serverHash }
|
|
|
|
else { tsMessage.syncMessageServerHash = message.serverHash }
|
2021-02-24 05:19:50 +01:00
|
|
|
// Track the open group server message ID
|
|
|
|
tsMessage.openGroupServerMessageID = message.openGroupServerMessageID ?? 0
|
|
|
|
tsMessage.save(with: transaction)
|
|
|
|
// Mark the message as sent
|
|
|
|
var recipients = [ message.recipient! ]
|
|
|
|
if case .closedGroup(_) = destination, let threadID = message.threadID, // threadID should always be set at this point
|
|
|
|
let thread = TSGroupThread.fetch(uniqueId: threadID, transaction: transaction), thread.isClosedGroup {
|
|
|
|
recipients = thread.groupModel.groupMemberIds
|
|
|
|
}
|
|
|
|
recipients.forEach { recipient in
|
|
|
|
tsMessage.update(withSentRecipient: recipient, wasSentByUD: true, transaction: transaction)
|
|
|
|
}
|
|
|
|
// Start the disappearing messages timer if needed
|
|
|
|
OWSDisappearingMessagesJob.shared().startAnyExpiration(for: tsMessage, expirationStartedAt: NSDate.millisecondTimestamp(), transaction: transaction)
|
2020-11-30 22:35:13 +01:00
|
|
|
}
|
2021-01-25 05:50:30 +01:00
|
|
|
// Sync the message if:
|
2021-02-24 05:19:50 +01:00
|
|
|
// • it's a visible message or an expiration timer update
|
2021-02-10 00:19:07 +01:00
|
|
|
// • the destination was a contact
|
2021-01-25 05:50:30 +01:00
|
|
|
// • we didn't sync it already
|
2021-01-14 04:57:32 +01:00
|
|
|
let userPublicKey = getUserHexEncodedPublicKey()
|
2021-02-24 05:19:50 +01:00
|
|
|
if case .contact(let publicKey) = destination, !isSyncMessage {
|
|
|
|
if let message = message as? VisibleMessage { message.syncTarget = publicKey }
|
|
|
|
if let message = message as? ExpirationTimerUpdate { message.syncTarget = publicKey }
|
2021-01-14 04:57:32 +01:00
|
|
|
// FIXME: Make this a job
|
|
|
|
sendToSnodeDestination(.contact(publicKey: userPublicKey), message: message, using: transaction, isSyncMessage: true).retainUntilComplete()
|
|
|
|
}
|
2020-11-30 22:35:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public static func handleFailedMessageSend(_ message: Message, with error: Swift.Error, using transaction: Any) {
|
|
|
|
guard let tsMessage = TSOutgoingMessage.find(withTimestamp: message.sentTimestamp!) else { return }
|
|
|
|
tsMessage.update(sendingError: error, transaction: transaction as! YapDatabaseReadWriteTransaction)
|
|
|
|
}
|
2020-11-05 23:17:05 +01:00
|
|
|
}
|