diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 05fe97beb7..12f0abb17c 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -606,6 +606,7 @@ 500B25832DD6A08300E9ECAA /* BackgroundMessageFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 500B25822DD6A08300E9ECAA /* BackgroundMessageFetcher.swift */; }; 500BAD802C519F2D00B4CD7F /* MessageTimestampGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 500BAD7E2C519F2D00B4CD7F /* MessageTimestampGenerator.swift */; }; 500BAD822C519F3600B4CD7F /* MessageTimestampGeneratorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 500BAD7F2C519F2D00B4CD7F /* MessageTimestampGeneratorTest.swift */; }; + 500CC1E52F15661A001A86F7 /* SendableMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 500CC1E42F15661A001A86F7 /* SendableMessage.swift */; }; 500CC1E72F15C20B001A86F7 /* ReceiptMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 500CC1E62F15C20B001A86F7 /* ReceiptMessage.swift */; }; 500CC1E92F15C8A7001A86F7 /* OutgoingCallMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 500CC1E82F15C8A7001A86F7 /* OutgoingCallMessage.swift */; }; 500CC1EB2F15D1E5001A86F7 /* StickerPackSyncMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 500CC1EA2F15D1E5001A86F7 /* StickerPackSyncMessage.swift */; }; @@ -4753,6 +4754,7 @@ 500B25822DD6A08300E9ECAA /* BackgroundMessageFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundMessageFetcher.swift; sourceTree = ""; }; 500BAD7E2C519F2D00B4CD7F /* MessageTimestampGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageTimestampGenerator.swift; sourceTree = ""; }; 500BAD7F2C519F2D00B4CD7F /* MessageTimestampGeneratorTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageTimestampGeneratorTest.swift; sourceTree = ""; }; + 500CC1E42F15661A001A86F7 /* SendableMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendableMessage.swift; sourceTree = ""; }; 500CC1E62F15C20B001A86F7 /* ReceiptMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceiptMessage.swift; sourceTree = ""; }; 500CC1E82F15C8A7001A86F7 /* OutgoingCallMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutgoingCallMessage.swift; sourceTree = ""; }; 500CC1EA2F15D1E5001A86F7 /* StickerPackSyncMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerPackSyncMessage.swift; sourceTree = ""; }; @@ -14393,6 +14395,7 @@ 66D13F092A731E590092D47B /* RecipientHidingManager+SignalServiceAddress.swift */, E1A090372A4B909B00F2BE8B /* RecipientHidingManager.swift */, 50B0E9472AC73C3B005D46AB /* RecipientStateMerger.swift */, + 500CC1E42F15661A001A86F7 /* SendableMessage.swift */, 504F98B22EB0270A00DF465B /* SendMessageFailure.swift */, 50A5AA9A2A7449D000CF2ECC /* ServerReceiptEnvelope.swift */, 506395802F2403BE00B2F772 /* TransientOutgoingMessage.swift */, @@ -19322,6 +19325,7 @@ 66138FB6298326C7002E0CFE /* SecureValueRecovery.swift in Sources */, 662C440B2A156DF7001F83E2 /* SecureValueRecovery2Impl.swift in Sources */, 6691E7F72996EAD70032A68A /* SecureValueRecoveryMock.swift in Sources */, + 500CC1E52F15661A001A86F7 /* SendableMessage.swift in Sources */, 725465392BA01FAA00EABFD2 /* SendGiftBadgeJobQueue.swift in Sources */, D9AE0AD5291877600063488B /* SendGiftBadgeJobRecord.swift in Sources */, 504F98B32EB0270A00DF465B /* SendMessageFailure.swift in Sources */, diff --git a/SignalServiceKit/Groups/GroupManager.swift b/SignalServiceKit/Groups/GroupManager.swift index 1f71af2230..dff9234320 100644 --- a/SignalServiceKit/Groups/GroupManager.swift +++ b/SignalServiceKit/Groups/GroupManager.swift @@ -770,8 +770,8 @@ public class GroupManager: NSObject { thread.groupModel.groupMembership.invitedMembers.compactMap(\.serviceId) } - public static func shouldMessageHaveAdditionalRecipients( - _ message: TSOutgoingMessage, + static func shouldMessageHaveAdditionalRecipients( + _ message: any SendableMessage, groupThread: TSGroupThread, ) -> Bool { guard groupThread.groupModel.groupsVersion == .V2 else { diff --git a/SignalServiceKit/Messages/MessageSendLog.swift b/SignalServiceKit/Messages/MessageSendLog.swift index fd756b6f49..f9470c274f 100644 --- a/SignalServiceKit/Messages/MessageSendLog.swift +++ b/SignalServiceKit/Messages/MessageSendLog.swift @@ -84,7 +84,7 @@ public class MessageSendLog { let uniqueId: String } - func recordPayload(_ plaintext: Data, for message: TSOutgoingMessage, tx: DBWriteTransaction) -> Int64? { + func recordPayload(_ plaintext: Data, for message: any SendableMessage, tx: DBWriteTransaction) -> Int64? { guard !RemoteConfig.current.messageResendKillSwitch else { return nil } @@ -134,7 +134,7 @@ public class MessageSendLog { plaintextContent: plaintext, contentHint: message.contentHint, sentTimestamp: message.timestamp, - uniqueThreadId: message.uniqueThreadId, + uniqueThreadId: message.threadUniqueId, sendComplete: false, ) try payload.insert(tx.database) @@ -197,7 +197,7 @@ public class MessageSendLog { return existingValue.payload } - public func sendComplete(message: TSOutgoingMessage, tx: DBWriteTransaction) { + func sendComplete(message: any SendableMessage, tx: DBWriteTransaction) { guard !RemoteConfig.current.messageResendKillSwitch else { return } @@ -221,10 +221,10 @@ public class MessageSendLog { } private func fetchUniquePayload( - for message: TSOutgoingMessage, + for message: any SendableMessage, tx: DBReadTransaction, ) throws -> (Int64, Payload)? { - let query = fetchRequest(threadUniqueId: message.uniqueThreadId).filter(Column("sentTimestamp") == message.timestamp) + let query = fetchRequest(threadUniqueId: message.threadUniqueId).filter(Column("sentTimestamp") == message.timestamp) return try fetchUniquePayload(query: query, tx: tx) } @@ -283,7 +283,7 @@ public class MessageSendLog { payloadId: Int64, recipientAci: Aci, recipientDeviceId: DeviceId, - message: TSOutgoingMessage, + message: any SendableMessage, tx: DBWriteTransaction, ) { guard !RemoteConfig.current.messageResendKillSwitch else { @@ -315,7 +315,7 @@ public class MessageSendLog { } func recordSuccessfulDelivery( - message: TSOutgoingMessage, + message: any SendableMessage, recipientAci: Aci, recipientDeviceId: DeviceId, tx: DBWriteTransaction, diff --git a/SignalServiceKit/Messages/MessageSender+SenderKey.swift b/SignalServiceKit/Messages/MessageSender+SenderKey.swift index 324b307fb6..72dbcce0d0 100644 --- a/SignalServiceKit/Messages/MessageSender+SenderKey.swift +++ b/SignalServiceKit/Messages/MessageSender+SenderKey.swift @@ -55,7 +55,7 @@ extension MessageSender { func prepareSenderKeyMessageSend( for recipients: [ServiceId], in thread: TSThread, - message: TSOutgoingMessage, + message: any SendableMessage, serializedMessage: SerializedMessage, endorsements: GroupSendEndorsements?, udAccessMap: [Aci: OWSUDAccess], @@ -219,7 +219,7 @@ extension MessageSender { private func sendSenderKeyMessage( to eligibleRecipients: Set, in thread: TSThread, - message: TSOutgoingMessage, + message: any SendableMessage, serializedMessage: SerializedMessage, authBuilder: (_ readyRecipients: [ServiceId]) -> TSRequest.SealedSenderAuth, senderCertificate: SenderCertificate, @@ -275,7 +275,7 @@ extension MessageSender { private func sendSenderKeyCiphertext( _ ciphertextResult: Result, to recipients: [Recipient], - message: TSOutgoingMessage, + message: any SendableMessage, payloadId: Int64?, authBuilder: () -> TSRequest.SealedSenderAuth, localIdentifiers: LocalIdentifiers, @@ -343,7 +343,7 @@ extension MessageSender { private func prepareSenderKeyDistributionMessages( for recipients: some Sequence, in thread: TSThread, - originalMessage: TSOutgoingMessage, + originalMessage: any SendableMessage, endorsements: GroupSendEndorsements?, udAccessMap: [Aci: OWSUDAccess], senderCertificate: SenderCertificate, @@ -475,7 +475,7 @@ extension MessageSender { /// *except* those returned as unregistered in the result. private func sendSenderKeyRequest( to recipients: [Recipient], - message: TSOutgoingMessage, + message: any SendableMessage, ciphertextResult: Result, authBuilder: () -> TSRequest.SealedSenderAuth, ) async throws -> SenderKeySendResult { @@ -585,7 +585,7 @@ extension MessageSender { private func senderKeyMessageBody( plaintext: Data, - message: TSOutgoingMessage, + message: any SendableMessage, thread: TSThread, recipients: [Recipient], senderCertificate: SenderCertificate, diff --git a/SignalServiceKit/Messages/MessageSender.swift b/SignalServiceKit/Messages/MessageSender.swift index 36d5f6adfc..6c8cab3603 100644 --- a/SignalServiceKit/Messages/MessageSender.swift +++ b/SignalServiceKit/Messages/MessageSender.swift @@ -8,7 +8,7 @@ import LibSignalClient // MARK: - Message "isXYZ" properties -private extension TSOutgoingMessage { +extension TSOutgoingMessage { var isTransientSKDM: Bool { (self as? OutgoingSenderKeyDistributionMessage)?.isSentOnBehalfOfOnlineMessage ?? false } @@ -456,7 +456,7 @@ public class MessageSender { // * A recipient is unregistered. // * A recipient does not have the required capability. private func markSkippedRecipients( - of message: TSOutgoingMessage, + of message: any SendableMessage, sendingRecipients: [ServiceId], tx: DBWriteTransaction, ) { @@ -466,7 +466,7 @@ public class MessageSender { } private func unsentRecipients( - of message: TSOutgoingMessage, + of message: any SendableMessage, in thread: TSThread, localIdentifiers: LocalIdentifiers, tx: DBReadTransaction, @@ -578,7 +578,7 @@ public class MessageSender { ) } - private func areAttachmentsUploadedWithSneakyTransaction(for message: TSOutgoingMessage) -> Bool { + private func areAttachmentsUploadedWithSneakyTransaction(for message: any SendableMessage) -> Bool { if message.shouldBeSaved == false { // Unsaved attachments come in two types: // * no attachments @@ -597,7 +597,7 @@ public class MessageSender { } } - private func sendPreparedMessage(_ message: TSOutgoingMessage) async throws -> SendMessageFailure? { + private func sendPreparedMessage(_ message: any SendableMessage) async throws -> SendMessageFailure? { if !areAttachmentsUploadedWithSneakyTransaction(for: message) { throw OWSGenericError("attachments aren't uploaded") } @@ -691,12 +691,13 @@ public class MessageSender { } private func sendPreparedMessage( - _ message: TSOutgoingMessage, + _ message: any SendableMessage, recoveryState: OuterRecoveryState, senderCertificates: SenderCertificates, localIdentifiers: LocalIdentifiers, ) async throws -> SendMessageFailure? { - let nextAction = try await SSKEnvironment.shared.databaseStorageRef.awaitableWrite { tx -> SendMessageNextAction? in + let databaseStorage = SSKEnvironment.shared.databaseStorageRef + let nextAction = try await databaseStorage.awaitableWrite { tx -> SendMessageNextAction? in guard let thread = message.thread(tx: tx) else { throw MessageSenderError.threadMissing } @@ -906,7 +907,7 @@ public class MessageSender { } private func sendPreparedMessage( - message: TSOutgoingMessage, + message: any SendableMessage, serializedMessage: SerializedMessage, in thread: TSThread, viaFanoutTo fanoutRecipients: Set, @@ -1005,7 +1006,7 @@ public class MessageSender { } private func handleSendFailure( - message: TSOutgoingMessage, + message: any SendableMessage, thread: TSThread, perRecipientErrors allErrors: [(serviceId: ServiceId, error: any Error)], ) async throws -> SendMessageFailure? { @@ -1057,7 +1058,7 @@ public class MessageSender { } private func normalizeRecipientStatesIfNeeded( - message: TSOutgoingMessage, + message: any SendableMessage, recipientErrors: some Sequence<(serviceId: ServiceId, error: Error)>, tx: DBWriteTransaction, ) { @@ -1090,7 +1091,7 @@ public class MessageSender { /// It is important to be conservative about which messages unhide a /// recipient. It is far better to not unhide when should than to /// unhide when we should not. - private func shouldMessageSendUnhideRecipient(_ message: TSOutgoingMessage, tx: DBReadTransaction) -> Bool { + private func shouldMessageSendUnhideRecipient(_ message: any SendableMessage, tx: DBReadTransaction) -> Bool { if message.shouldBeSaved, let rowId = message.sqliteRowId, @@ -1117,7 +1118,7 @@ public class MessageSender { } private func handleMessageSentLocally( - _ message: TSOutgoingMessage, + _ message: any SendableMessage, localIdentifiers: LocalIdentifiers, ) async throws { await SSKEnvironment.shared.databaseStorageRef.awaitableWrite { tx in @@ -1181,7 +1182,7 @@ public class MessageSender { } private func sendSyncTranscriptIfNeeded( - forMessage message: TSOutgoingMessage, + forMessage message: any SendableMessage, localIdentifiers: LocalIdentifiers, ) async throws { guard message.shouldSyncTranscript() else { @@ -1195,7 +1196,7 @@ public class MessageSender { } private func sendSyncTranscript( - forMessage message: TSOutgoingMessage, + forMessage message: any SendableMessage, localIdentifiers: LocalIdentifiers, ) async throws { let databaseStorage = SSKEnvironment.shared.databaseStorageRef @@ -1228,7 +1229,7 @@ public class MessageSender { } func buildAndRecordMessage( - _ message: TSOutgoingMessage, + _ message: any SendableMessage, in thread: TSThread, tx: DBWriteTransaction, ) throws -> SerializedMessage { @@ -1615,7 +1616,7 @@ public class MessageSender { messageSend: OWSMessageSend, sealedSenderParameters: SealedSenderParameters?, ) async throws -> [SentDeviceMessage] { - let message: TSOutgoingMessage = messageSend.message + let message = messageSend.message let requestMaker = RequestMaker( label: "Message Send", @@ -1659,7 +1660,7 @@ public class MessageSender { deviceMessages: [DeviceMessage], wasSentByUD: Bool, ) async -> [SentDeviceMessage] { - let message: TSOutgoingMessage = messageSend.message + let message = messageSend.message Logger.info("Successfully sent message: \(type(of: message)), serviceId: \(messageSend.serviceId), timestamp: \(message.timestamp), wasSentByUD: \(wasSentByUD)") @@ -1722,7 +1723,7 @@ public class MessageSender { responseError: Error, sealedSenderParameters: SealedSenderParameters?, ) async throws -> [SentDeviceMessage] { - let message: TSOutgoingMessage = messageSend.message + let message = messageSend.message Logger.warn("\(type(of: message)) to \(messageSend.serviceId), timestamp: \(message.timestamp), error: \(responseError)") diff --git a/SignalServiceKit/Messages/OWSMessageSend.swift b/SignalServiceKit/Messages/OWSMessageSend.swift index 0f3e378647..b1e878a2b8 100644 --- a/SignalServiceKit/Messages/OWSMessageSend.swift +++ b/SignalServiceKit/Messages/OWSMessageSend.swift @@ -8,13 +8,13 @@ import LibSignalClient /// Provides parameters required for assembling a Sealed Sender message. final class SealedSenderParameters { - let message: TSOutgoingMessage + let message: any SendableMessage let senderCertificate: SenderCertificate let accessKey: OWSUDAccess? let endorsement: GroupSendFullTokenBuilder? init?( - message: TSOutgoingMessage, + message: any SendableMessage, senderCertificate: SenderCertificate, accessKey: OWSUDAccess?, endorsement: GroupSendFullTokenBuilder?, @@ -45,7 +45,7 @@ final class SealedSenderParameters { // to multiple recipients and therefore require multiple instances of // OWSMessageSend. final class OWSMessageSend { - let message: TSOutgoingMessage + let message: any SendableMessage let plaintextContent: Data let plaintextPayloadId: Int64? let thread: TSThread @@ -53,7 +53,7 @@ final class OWSMessageSend { let localIdentifiers: LocalIdentifiers init( - message: TSOutgoingMessage, + message: any SendableMessage, plaintextContent: Data, plaintextPayloadId: Int64?, thread: TSThread, diff --git a/SignalServiceKit/Messages/OutgoingSenderKeyDistributionMessage.swift b/SignalServiceKit/Messages/OutgoingSenderKeyDistributionMessage.swift index 9e262fb744..79ee743ee9 100644 --- a/SignalServiceKit/Messages/OutgoingSenderKeyDistributionMessage.swift +++ b/SignalServiceKit/Messages/OutgoingSenderKeyDistributionMessage.swift @@ -25,7 +25,7 @@ final class OutgoingSenderKeyDistributionMessage: TransientOutgoingMessage { init( recipientThread: TSContactThread, senderKeyDistributionMessage: SenderKeyDistributionMessage, - onBehalfOfMessage originalMessage: TSOutgoingMessage, + onBehalfOfMessage originalMessage: any SendableMessage, inThread originalThread: TSThread, tx: DBReadTransaction, ) { diff --git a/SignalServiceKit/Messages/SendableMessage.swift b/SignalServiceKit/Messages/SendableMessage.swift new file mode 100644 index 0000000000..2702a9fe42 --- /dev/null +++ b/SignalServiceKit/Messages/SendableMessage.swift @@ -0,0 +1,88 @@ +// +// Copyright 2026 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only +// + +import Foundation +import LibSignalClient + +protocol SendableMessage { + var threadUniqueId: String { get } + + var sqliteRowId: Int64? { get } + + var uniqueId: String { get } + + var timestamp: UInt64 { get } + + var isSyncMessage: Bool { get } + + var isStorySend: Bool { get } + + var isOnline: Bool { get } + + var isTransientSKDM: Bool { get } + + var isUrgent: Bool { get } + + var isResendRequest: Bool { get } + + var canSendToLocalAddress: Bool { get } + + var contentHint: SealedSenderContentHint { get } + + var encryptionStyle: EncryptionStyle { get } + + func buildPlainTextData(_ thread: TSThread, transaction: DBWriteTransaction) -> Data? + + var wasSentToAnyRecipient: Bool { get } + + func recipientAddresses() -> [SignalServiceAddress] + + func sendingRecipientAddresses() -> [SignalServiceAddress] + + func sentRecipientAddresses() -> [SignalServiceAddress] + + func insertedMessageHasRenderableContent(rowId: Int64, tx: DBReadTransaction) -> Bool + + func anyUpdateOutgoingMessage(transaction: DBWriteTransaction, block: (TSOutgoingMessage) -> Void) + + func updateWithSkippedRecipients(_ skippedRecipients: some Sequence, tx: DBWriteTransaction) + + func updateWithFailedRecipients(_ recipientErrors: some Collection<(serviceId: ServiceId, error: Error)>, tx: DBWriteTransaction) + + func updateWithSentRecipients(_ serviceIds: [ServiceId], wasSentByUD: Bool, transaction: DBWriteTransaction) + + func update(withReadRecipient recipientAddress: SignalServiceAddress, deviceId: DeviceId, readTimestamp timestamp: UInt64, tx: DBWriteTransaction) + + func update(withViewedRecipient recipientAddress: SignalServiceAddress, deviceId: DeviceId, viewedTimestamp timestamp: UInt64, tx: DBWriteTransaction) + + func shouldSyncTranscript() -> Bool + + func thread(tx: DBReadTransaction) -> TSThread? + + var shouldBeSaved: Bool { get } + + var shouldRecordSendLog: Bool { get } + + var relatedUniqueIds: Set { get } + + func envelopeGroupIdWithTransaction(_ transaction: DBReadTransaction) -> Data? + + var isVoiceMessage: Bool { get } + + var isViewOnceMessage: Bool { get } + + func buildTranscriptSyncMessage( + localThread: TSContactThread, + transaction: DBWriteTransaction, + ) -> OutgoingSyncMessage? + + func update(withHasSyncedTranscript: Bool, transaction: DBWriteTransaction) + + func allAttachments(transaction: DBReadTransaction) -> [ReferencedAttachment] +} + +extension TSOutgoingMessage: SendableMessage { + var threadUniqueId: String { self.uniqueThreadId } +}