|
|
@ -5,6 +5,7 @@ import SessionUtilitiesKit
|
|
|
|
@objc(SNMessageSender)
|
|
|
|
@objc(SNMessageSender)
|
|
|
|
public final class MessageSender : NSObject {
|
|
|
|
public final class MessageSender : NSObject {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// MARK: Error
|
|
|
|
public enum Error : LocalizedError {
|
|
|
|
public enum Error : LocalizedError {
|
|
|
|
case invalidMessage
|
|
|
|
case invalidMessage
|
|
|
|
case protoConversionFailed
|
|
|
|
case protoConversionFailed
|
|
|
@ -27,9 +28,11 @@ public final class MessageSender : NSObject {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// MARK: Initialization
|
|
|
|
private override init() { }
|
|
|
|
private override init() { }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// MARK: Convenience
|
|
|
|
public static func send(_ message: Message, to destination: Message.Destination, using transaction: Any) -> Promise<Void> {
|
|
|
|
public static func send(_ message: Message, to destination: Message.Destination, using transaction: Any) -> Promise<Void> {
|
|
|
|
switch destination {
|
|
|
|
switch destination {
|
|
|
|
case .contact(_), .closedGroup(_): return sendToSnodeDestination(destination, message: message, using: transaction)
|
|
|
|
case .contact(_), .closedGroup(_): return sendToSnodeDestination(destination, message: message, using: transaction)
|
|
|
@ -37,10 +40,11 @@ public final class MessageSender : NSObject {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// MARK: One-on-One Chats & Closed Groups
|
|
|
|
internal static func sendToSnodeDestination(_ destination: Message.Destination, message: Message, using transaction: Any) -> Promise<Void> {
|
|
|
|
internal static func sendToSnodeDestination(_ destination: Message.Destination, message: Message, using transaction: Any) -> Promise<Void> {
|
|
|
|
let (promise, seal) = Promise<Void>.pending()
|
|
|
|
let (promise, seal) = Promise<Void>.pending()
|
|
|
|
let storage = Configuration.shared.storage
|
|
|
|
let storage = Configuration.shared.storage
|
|
|
|
if message.sentTimestamp == nil { // Visible messages will already have the sent timestamp set
|
|
|
|
if message.sentTimestamp == nil { // Visible messages will already have their sent timestamp set
|
|
|
|
message.sentTimestamp = NSDate.millisecondTimestamp()
|
|
|
|
message.sentTimestamp = NSDate.millisecondTimestamp()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
message.sender = storage.getUserPublicKey()
|
|
|
|
message.sender = storage.getUserPublicKey()
|
|
|
@ -49,7 +53,7 @@ public final class MessageSender : NSObject {
|
|
|
|
case .closedGroup(let groupPublicKey): message.recipient = groupPublicKey
|
|
|
|
case .closedGroup(let groupPublicKey): message.recipient = groupPublicKey
|
|
|
|
case .openGroup(_, _): preconditionFailure()
|
|
|
|
case .openGroup(_, _): preconditionFailure()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Set the failure handler
|
|
|
|
// Set the failure handler (for precondition failure handling)
|
|
|
|
let _ = promise.catch(on: DispatchQueue.main) { error in
|
|
|
|
let _ = promise.catch(on: DispatchQueue.main) { error in
|
|
|
|
storage.withAsync({ transaction in
|
|
|
|
storage.withAsync({ transaction in
|
|
|
|
Configuration.shared.messageSenderDelegate.handleFailedMessageSend(message, with: error, using: transaction)
|
|
|
|
Configuration.shared.messageSenderDelegate.handleFailedMessageSend(message, with: error, using: transaction)
|
|
|
@ -130,26 +134,27 @@ public final class MessageSender : NSObject {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let snodeMessage = SnodeMessage(recipient: recipient, data: base64EncodedData, ttl: type(of: message).ttl, timestamp: timestamp, nonce: nonce)
|
|
|
|
let snodeMessage = SnodeMessage(recipient: recipient, data: base64EncodedData, ttl: type(of: message).ttl, timestamp: timestamp, nonce: nonce)
|
|
|
|
SnodeAPI.sendMessage(snodeMessage).done(on: Threading.workQueue) { promises in
|
|
|
|
SnodeAPI.sendMessage(snodeMessage).done(on: DispatchQueue.global(qos: .userInitiated)) { promises in
|
|
|
|
var isSuccess = false
|
|
|
|
var isSuccess = false
|
|
|
|
let promiseCount = promises.count
|
|
|
|
let promiseCount = promises.count
|
|
|
|
var errorCount = 0
|
|
|
|
var errorCount = 0
|
|
|
|
promises.forEach {
|
|
|
|
promises.forEach {
|
|
|
|
let _ = $0.done(on: Threading.workQueue) { _ in
|
|
|
|
let _ = $0.done(on: DispatchQueue.global(qos: .userInitiated)) { _ in
|
|
|
|
guard !isSuccess else { return } // Succeed as soon as the first promise succeeds
|
|
|
|
guard !isSuccess else { return } // Succeed as soon as the first promise succeeds
|
|
|
|
isSuccess = true
|
|
|
|
isSuccess = true
|
|
|
|
seal.fulfill(())
|
|
|
|
seal.fulfill(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$0.catch(on: Threading.workQueue) { error in
|
|
|
|
$0.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in
|
|
|
|
errorCount += 1
|
|
|
|
errorCount += 1
|
|
|
|
guard errorCount == promiseCount else { return } // Only error out if all promises failed
|
|
|
|
guard errorCount == promiseCount else { return } // Only error out if all promises failed
|
|
|
|
seal.reject(error)
|
|
|
|
seal.reject(error)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}.catch(on: Threading.workQueue) { error in
|
|
|
|
}.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in
|
|
|
|
SNLog("Couldn't send message due to error: \(error).")
|
|
|
|
SNLog("Couldn't send message due to error: \(error).")
|
|
|
|
seal.reject(error)
|
|
|
|
seal.reject(error)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle completion
|
|
|
|
let _ = promise.done(on: DispatchQueue.main) {
|
|
|
|
let _ = promise.done(on: DispatchQueue.main) {
|
|
|
|
storage.withAsync({ transaction in
|
|
|
|
storage.withAsync({ transaction in
|
|
|
|
Configuration.shared.messageSenderDelegate.handleSuccessfulMessageSend(message, using: transaction)
|
|
|
|
Configuration.shared.messageSenderDelegate.handleSuccessfulMessageSend(message, using: transaction)
|
|
|
@ -157,14 +162,18 @@ public final class MessageSender : NSObject {
|
|
|
|
if case .contact(_) = destination {
|
|
|
|
if case .contact(_) = destination {
|
|
|
|
NotificationCenter.default.post(name: .messageSent, object: NSNumber(value: message.sentTimestamp!))
|
|
|
|
NotificationCenter.default.post(name: .messageSent, object: NSNumber(value: message.sentTimestamp!))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let notifyPNServerJob = NotifyPNServerJob(message: snodeMessage)
|
|
|
|
if message is VisibleMessage {
|
|
|
|
storage.withAsync({ transaction in
|
|
|
|
let notifyPNServerJob = NotifyPNServerJob(message: snodeMessage)
|
|
|
|
JobQueue.shared.add(notifyPNServerJob, using: transaction)
|
|
|
|
storage.withAsync({ transaction in
|
|
|
|
}, completion: { })
|
|
|
|
JobQueue.shared.add(notifyPNServerJob, using: transaction)
|
|
|
|
|
|
|
|
}, completion: { })
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return
|
|
|
|
return promise
|
|
|
|
return promise
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// MARK: Open Groups
|
|
|
|
internal static func sendToOpenGroupDestination(_ destination: Message.Destination, message: Message, using transaction: Any) -> Promise<Void> {
|
|
|
|
internal static func sendToOpenGroupDestination(_ destination: Message.Destination, message: Message, using transaction: Any) -> Promise<Void> {
|
|
|
|
let (promise, seal) = Promise<Void>.pending()
|
|
|
|
let (promise, seal) = Promise<Void>.pending()
|
|
|
|
let storage = Configuration.shared.storage
|
|
|
|
let storage = Configuration.shared.storage
|
|
|
@ -174,12 +183,15 @@ public final class MessageSender : NSObject {
|
|
|
|
case .closedGroup(_): preconditionFailure()
|
|
|
|
case .closedGroup(_): preconditionFailure()
|
|
|
|
case .openGroup(let channel, let server): message.recipient = "\(server).\(channel)"
|
|
|
|
case .openGroup(let channel, let server): message.recipient = "\(server).\(channel)"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set the failure handler (for precondition failure handling)
|
|
|
|
let _ = promise.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in
|
|
|
|
let _ = promise.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in
|
|
|
|
storage.withAsync({ transaction in
|
|
|
|
storage.withAsync({ transaction in
|
|
|
|
Configuration.shared.messageSenderDelegate.handleFailedMessageSend(message, with: error, using: transaction)
|
|
|
|
Configuration.shared.messageSenderDelegate.handleFailedMessageSend(message, with: error, using: transaction)
|
|
|
|
}, completion: { })
|
|
|
|
}, completion: { })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate the message
|
|
|
|
guard message.isValid else { seal.reject(Error.invalidMessage); return promise }
|
|
|
|
guard message.isValid else { seal.reject(Error.invalidMessage); return promise }
|
|
|
|
|
|
|
|
// Convert the message to an open group message
|
|
|
|
let (channel, server) = { () -> (UInt64, String) in
|
|
|
|
let (channel, server) = { () -> (UInt64, String) in
|
|
|
|
switch destination {
|
|
|
|
switch destination {
|
|
|
|
case .openGroup(let channel, let server): return (channel, server)
|
|
|
|
case .openGroup(let channel, let server): return (channel, server)
|
|
|
@ -188,17 +200,20 @@ public final class MessageSender : NSObject {
|
|
|
|
}()
|
|
|
|
}()
|
|
|
|
guard let message = message as? VisibleMessage,
|
|
|
|
guard let message = message as? VisibleMessage,
|
|
|
|
let openGroupMessage = OpenGroupMessage.from(message, for: server) else { seal.reject(Error.invalidMessage); return promise }
|
|
|
|
let openGroupMessage = OpenGroupMessage.from(message, for: server) else { seal.reject(Error.invalidMessage); return promise }
|
|
|
|
|
|
|
|
// Send the result
|
|
|
|
OpenGroupAPI.sendMessage(openGroupMessage, to: channel, on: server).done(on: DispatchQueue.global(qos: .userInitiated)) { openGroupMessage in
|
|
|
|
OpenGroupAPI.sendMessage(openGroupMessage, to: channel, on: server).done(on: DispatchQueue.global(qos: .userInitiated)) { openGroupMessage in
|
|
|
|
message.openGroupServerMessageID = openGroupMessage.serverID
|
|
|
|
message.openGroupServerMessageID = openGroupMessage.serverID
|
|
|
|
seal.fulfill(())
|
|
|
|
seal.fulfill(())
|
|
|
|
}.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in
|
|
|
|
}.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in
|
|
|
|
seal.reject(error)
|
|
|
|
seal.reject(error)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle completion
|
|
|
|
let _ = promise.done(on: DispatchQueue.global(qos: .userInitiated)) {
|
|
|
|
let _ = promise.done(on: DispatchQueue.global(qos: .userInitiated)) {
|
|
|
|
storage.withAsync({ transaction in
|
|
|
|
storage.withAsync({ transaction in
|
|
|
|
Configuration.shared.messageSenderDelegate.handleSuccessfulMessageSend(message, using: transaction)
|
|
|
|
Configuration.shared.messageSenderDelegate.handleSuccessfulMessageSend(message, using: transaction)
|
|
|
|
}, completion: { })
|
|
|
|
}, completion: { })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return
|
|
|
|
return promise
|
|
|
|
return promise
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|