diff --git a/Session/Database/Storage+SessionMessagingKit.swift b/Session/Database/Storage+SessionMessagingKit.swift index ff824e14f..7911e3c12 100644 --- a/Session/Database/Storage+SessionMessagingKit.swift +++ b/Session/Database/Storage+SessionMessagingKit.swift @@ -206,21 +206,21 @@ extension Storage : SessionMessagingKitStorageProtocol { } public func updateProfile(for publicKey: String, from profile: VisibleMessage.Profile, using transaction: Any) { -// let transaction = transaction as! YapDatabaseReadWriteTransaction -// let profileManager = SSKEnvironment.shared.profileManager -// if let displayName = profile.displayName { -// profileManager.updateProfileForContact(withID: publicKey, displayName: displayName, with: transaction) -// } -// if let profileKey = profile.profileKey, let profilePictureURL = profile.profilePictureURL, profileKey.count == kAES256_KeyByteLength { -// profileManager.setProfileKeyData(profileKey, forRecipientId: publicKey, avatarURL: profilePictureURL) -// } + let transaction = transaction as! YapDatabaseReadWriteTransaction + let profileManager = SSKEnvironment.shared.profileManager + if let displayName = profile.displayName { + profileManager.updateProfileForContact(withID: publicKey, displayName: displayName, with: transaction) + } + if let profileKey = profile.profileKey, let profilePictureURL = profile.profilePictureURL, profileKey.count == kAES256_KeyByteLength { + profileManager.setProfileKeyData(profileKey, forRecipientId: publicKey, avatarURL: profilePictureURL) + } } /// Returns the ID of the thread the message was stored under along with the `TSIncomingMessage` that was constructed. public func persist(_ message: VisibleMessage, using transaction: Any) -> (String, Any) { let transaction = transaction as! YapDatabaseReadWriteTransaction let thread = TSContactThread.getOrCreateThread(withContactId: message.sender!, transaction: transaction) - let message = TSIncomingMessage.from(message, using: transaction) + let message = TSIncomingMessage.from(message, associatedWith: thread, using: transaction) message.save(with: transaction) return (thread.uniqueId!, message) } @@ -230,7 +230,7 @@ extension Storage : SessionMessagingKitStorageProtocol { Storage.read { transaction in threadOrNil = TSContactThread.getWithContactId(senderPublicKey, transaction: transaction) } - guard let thread = threadOrNil else { return } // Ignore if the thread doesn't exist yet + guard let thread = threadOrNil else { return } func showTypingIndicatorsIfNeeded() { SSKEnvironment.shared.typingIndicators.didReceiveTypingStartedMessage(inThread: thread, recipientId: senderPublicKey, deviceId: 1) } @@ -248,7 +248,7 @@ extension Storage : SessionMessagingKitStorageProtocol { Storage.read { transaction in threadOrNil = TSContactThread.getWithContactId(senderPublicKey, transaction: transaction) } - guard let thread = threadOrNil else { return } // Ignore if the thread doesn't exist yet + guard let thread = threadOrNil else { return } func hideTypingIndicatorsIfNeeded() { SSKEnvironment.shared.typingIndicators.didReceiveTypingStoppedMessage(inThread: thread, recipientId: senderPublicKey, deviceId: 1) } @@ -266,7 +266,7 @@ extension Storage : SessionMessagingKitStorageProtocol { Storage.read { transaction in threadOrNil = TSContactThread.getWithContactId(senderPublicKey, transaction: transaction) } - guard let thread = threadOrNil else { return } // Ignore if the thread doesn't exist yet + guard let thread = threadOrNil else { return } func cancelTypingIndicatorsIfNeeded() { SSKEnvironment.shared.typingIndicators.didReceiveIncomingMessage(inThread: thread, recipientId: senderPublicKey, deviceId: 1) } @@ -285,4 +285,40 @@ extension Storage : SessionMessagingKitStorageProtocol { SSKEnvironment.shared.notificationsManager!.notifyUser(for: (message as! TSIncomingMessage), in: thread, transaction: transaction) } } + + public func markMessagesAsRead(_ timestamps: [UInt64], from senderPublicKey: String, at timestamp: UInt64) { + SSKEnvironment.shared.readReceiptManager.processReadReceipts(fromRecipientId: senderPublicKey, sentTimestamps: timestamps.map { NSNumber(value: $0) }, readTimestamp: timestamp) + } + + public func setExpirationTimer(to duration: UInt32, for senderPublicKey: String, using transaction: Any) { + let transaction = transaction as! YapDatabaseReadWriteTransaction + var threadOrNil: TSContactThread? + Storage.read { transaction in + threadOrNil = TSContactThread.getWithContactId(senderPublicKey, transaction: transaction) + } + guard let thread = threadOrNil else { return } + let configuration = OWSDisappearingMessagesConfiguration(threadId: thread.uniqueId!, enabled: true, durationSeconds: duration) + configuration.save(with: transaction) + let senderDisplayName = SSKEnvironment.shared.profileManager.profileNameForRecipient(withID: senderPublicKey, transaction: transaction) + let message = OWSDisappearingConfigurationUpdateInfoMessage(timestamp: NSDate.millisecondTimestamp(), thread: thread, + configuration: configuration, createdByRemoteName: senderDisplayName, createdInExistingGroup: false) + message.save(with: transaction) + SSKEnvironment.shared.disappearingMessagesJob.startIfNecessary() + } + + public func disableExpirationTimer(for senderPublicKey: String, using transaction: Any) { + let transaction = transaction as! YapDatabaseReadWriteTransaction + var threadOrNil: TSContactThread? + Storage.read { transaction in + threadOrNil = TSContactThread.getWithContactId(senderPublicKey, transaction: transaction) + } + guard let thread = threadOrNil else { return } + let configuration = OWSDisappearingMessagesConfiguration(threadId: thread.uniqueId!, enabled: false, durationSeconds: 24 * 60 * 60) + configuration.save(with: transaction) + let senderDisplayName = SSKEnvironment.shared.profileManager.profileNameForRecipient(withID: senderPublicKey, transaction: transaction) + let message = OWSDisappearingConfigurationUpdateInfoMessage(timestamp: NSDate.millisecondTimestamp(), thread: thread, + configuration: configuration, createdByRemoteName: senderDisplayName, createdInExistingGroup: false) + message.save(with: transaction) + SSKEnvironment.shared.disappearingMessagesJob.startIfNecessary() + } } diff --git a/SessionMessagingKit/Jobs/JobDelegate.swift b/SessionMessagingKit/Jobs/JobDelegate.swift index 461433e85..b45a5bf28 100644 --- a/SessionMessagingKit/Jobs/JobDelegate.swift +++ b/SessionMessagingKit/Jobs/JobDelegate.swift @@ -4,4 +4,5 @@ public protocol JobDelegate { func handleJobSucceeded(_ job: Job) func handleJobFailed(_ job: Job, with error: Error) + func handleJobFailedPermanently(_ job: Job, with error: Error) } diff --git a/SessionMessagingKit/Jobs/JobQueue.swift b/SessionMessagingKit/Jobs/JobQueue.swift index f5e24568d..8b97a725a 100644 --- a/SessionMessagingKit/Jobs/JobQueue.swift +++ b/SessionMessagingKit/Jobs/JobQueue.swift @@ -47,6 +47,20 @@ public final class JobQueue : NSObject, JobDelegate { }) } + public func handleJobFailedPermanently(_ job: Job, with error: Error) { + job.failureCount += 1 + let storage = Configuration.shared.storage + storage.withAsync({ transaction in + storage.persist(job, using: transaction) + }, completion: { // Intentionally capture self + storage.withAsync({ transaction in + storage.markJobAsFailed(job, using: transaction) + }, completion: { + // Do nothing + }) + }) + } + private func getRetryInterval(for job: Job) -> TimeInterval { // Arbitrary backoff factor... // try 1 delay: 0.00s diff --git a/SessionMessagingKit/Jobs/MessageReceiveJob.swift b/SessionMessagingKit/Jobs/MessageReceiveJob.swift index 3e6e8662e..bd3ed8ae9 100644 --- a/SessionMessagingKit/Jobs/MessageReceiveJob.swift +++ b/SessionMessagingKit/Jobs/MessageReceiveJob.swift @@ -44,7 +44,11 @@ public final class MessageReceiveJob : NSObject, Job, NSCoding { // NSObject/NSC self.handleSuccess() } catch { SNLog("Couldn't parse message due to error: \(error).") - self.handleFailure(error: error) + if let error = error as? MessageReceiver.Error, !error.isRetryable { + self.handlePermanentFailure(error: error) + } else { + self.handleFailure(error: error) + } } } }, completion: { }) @@ -54,6 +58,10 @@ public final class MessageReceiveJob : NSObject, Job, NSCoding { // NSObject/NSC delegate?.handleJobSucceeded(self) } + private func handlePermanentFailure(error: Error) { + delegate?.handleJobFailedPermanently(self, with: error) + } + private func handleFailure(error: Error) { delegate?.handleJobFailed(self, with: error) } diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index 3b2252d85..9bb04e4ef 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -1,5 +1,9 @@ import SessionUtilitiesKit +// TODO: +// • Threads don't show up on the first message; only on the second. +// • Profile pictures aren't showing up. + internal enum MessageReceiver { internal enum Error : LocalizedError { @@ -15,6 +19,13 @@ internal enum MessageReceiver { case sharedSecretGenerationFailed case selfSend + internal var isRetryable: Bool { + switch self { + case .invalidMessage, .unknownMessage, .unknownEnvelopeType, .noData, .senderBlocked, .selfSend: return false + default: return true + } + } + internal var errorDescription: String? { switch self { case .invalidMessage: return "Invalid message." @@ -57,6 +68,7 @@ internal enum MessageReceiver { let message: Message? = { if let readReceipt = ReadReceipt.fromProto(proto) { return readReceipt } if let sessionRequest = SessionRequest.fromProto(proto) { return sessionRequest } + if let nullMessage = NullMessage.fromProto(proto) { return nullMessage } if let typingIndicator = TypingIndicator.fromProto(proto) { return typingIndicator } if let closedGroupUpdate = ClosedGroupUpdate.fromProto(proto) { return closedGroupUpdate } if let expirationTimerUpdate = ExpirationTimerUpdate.fromProto(proto) { return expirationTimerUpdate } @@ -78,6 +90,7 @@ internal enum MessageReceiver { switch message { case let message as ReadReceipt: handleReadReceipt(message, using: transaction) case let message as SessionRequest: handleSessionRequest(message, using: transaction) + case let message as NullMessage: handleNullMessage(message, using: transaction) case let message as TypingIndicator: handleTypingIndicator(message, using: transaction) case let message as ClosedGroupUpdate: handleClosedGroupUpdate(message, using: transaction) case let message as ExpirationTimerUpdate: handleExpirationTimerUpdate(message, using: transaction) @@ -87,11 +100,15 @@ internal enum MessageReceiver { } private static func handleReadReceipt(_ message: ReadReceipt, using transaction: Any) { - + Configuration.shared.storage.markMessagesAsRead(message.timestamps!, from: message.sender!, at: message.receivedTimestamp!) } private static func handleSessionRequest(_ message: SessionRequest, using transaction: Any) { + // TODO: Implement + } + private static func handleNullMessage(_ message: NullMessage, using transaction: Any) { + // TODO: Implement } private static func handleTypingIndicator(_ message: TypingIndicator, using transaction: Any) { @@ -107,7 +124,12 @@ internal enum MessageReceiver { } private static func handleExpirationTimerUpdate(_ message: ExpirationTimerUpdate, using transaction: Any) { - + let storage = Configuration.shared.storage + if message.duration! > 0 { + storage.setExpirationTimer(to: message.duration!, for: message.sender!, using: transaction) + } else { + storage.disableExpirationTimer(for: message.sender!, using: transaction) + } } private static func handleVisibleMessage(_ message: VisibleMessage, using transaction: Any) { diff --git a/SessionMessagingKit/Storage.swift b/SessionMessagingKit/Storage.swift index f3029dd37..12ac5fd3e 100644 --- a/SessionMessagingKit/Storage.swift +++ b/SessionMessagingKit/Storage.swift @@ -70,4 +70,7 @@ public protocol SessionMessagingKitStorageProtocol { func hideTypingIndicatorIfNeeded(for senderPublicKey: String) func cancelTypingIndicatorsIfNeeded(for senderPublicKey: String) func notifyUserIfNeeded(for message: Any, threadID: String) + func markMessagesAsRead(_ timestamps: [UInt64], from senderPublicKey: String, at timestamp: UInt64) + func setExpirationTimer(to duration: UInt32, for senderPublicKey: String, using transaction: Any) + func disableExpirationTimer(for senderPublicKey: String, using transaction: Any) } diff --git a/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h b/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h index 710dd8d5e..efc3b9b8b 100644 --- a/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h +++ b/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h @@ -33,6 +33,7 @@ FOUNDATION_EXPORT const unsigned char SignalUtilitiesKitVersionString[]; #import #import #import +#import #import #import #import diff --git a/SignalUtilitiesKit/Utilities/TSIncomingMessage+Conversion.swift b/SignalUtilitiesKit/Utilities/TSIncomingMessage+Conversion.swift index 051e9831f..59ca58cc7 100644 --- a/SignalUtilitiesKit/Utilities/TSIncomingMessage+Conversion.swift +++ b/SignalUtilitiesKit/Utilities/TSIncomingMessage+Conversion.swift @@ -1,9 +1,8 @@ public extension TSIncomingMessage { - static func from(_ visibleMessage: VisibleMessage, using transaction: YapDatabaseReadWriteTransaction) -> TSIncomingMessage { + static func from(_ visibleMessage: VisibleMessage, associatedWith thread: TSThread, using transaction: YapDatabaseReadWriteTransaction) -> TSIncomingMessage { let sender = visibleMessage.sender! - let thread = TSContactThread.getOrCreateThread(withContactId: sender, transaction: transaction) return TSIncomingMessage( timestamp: visibleMessage.receivedTimestamp!, in: thread,