Path: blob/main/SignalServiceKit/Messages/DeviceSyncing/OutgoingSentMessageTranscript.swift
1 views
//
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
/// Notifies your other registered devices (if you have any) that you've
/// sent a message. This way the message you just sent can appear on all
/// your devices.
class OutgoingSentMessageTranscript: OutgoingSyncMessage {
let message: TSOutgoingMessage
let messageThread: TSThread
let isRecipientUpdate: Bool
// sentRecipientAddress is the recipient of message, for contact thread messages.
// It is used to identify the thread/conversation to desktop.
let sentRecipientAddress: SignalServiceAddress?
init(
localThread: TSContactThread,
messageThread: TSThread,
message: TSOutgoingMessage,
isRecipientUpdate: Bool,
tx: DBReadTransaction,
) {
self.message = message
self.messageThread = messageThread
self.isRecipientUpdate = isRecipientUpdate
self.sentRecipientAddress = (messageThread as? TSContactThread)?.contactAddress
// The sync message's timestamp must match the original outgoing message's timestamp.
super.init(timestamp: message.timestamp, localThread: localThread, tx: tx)
}
override func encode(with coder: NSCoder) {
owsFail("Doesn't support serialization.")
}
required init?(coder: NSCoder) {
// Doesn't support serialization.
return nil
}
override var hash: Int {
var hasher = Hasher()
hasher.combine(super.hash)
hasher.combine(self.isRecipientUpdate)
hasher.combine(self.message)
hasher.combine(self.messageThread)
hasher.combine(self.sentRecipientAddress)
return hasher.finalize()
}
override func isEqual(_ object: Any?) -> Bool {
guard let object = object as? Self else { return false }
guard super.isEqual(object) else { return false }
guard self.isRecipientUpdate == object.isRecipientUpdate else { return false }
guard self.message == object.message else { return false }
guard self.messageThread == object.messageThread else { return false }
guard self.sentRecipientAddress == object.sentRecipientAddress else { return false }
return true
}
override var isUrgent: Bool { false }
override func syncMessageBuilder(tx: DBReadTransaction) -> SSKProtoSyncMessageBuilder? {
let sentBuilder = SSKProtoSyncMessageSent.builder()
sentBuilder.setTimestamp(self.timestamp)
if let phoneNumber = self.sentRecipientAddress?.phoneNumber {
sentBuilder.setDestinationE164(phoneNumber)
}
if let serviceId = self.sentRecipientAddress?.serviceId {
sentBuilder.setDestinationServiceIDBinary(serviceId.serviceIdBinary)
}
sentBuilder.setIsRecipientUpdate(self.isRecipientUpdate)
guard prepareDataSyncMessageContent(with: sentBuilder, tx: tx) else {
return nil
}
prepareUnidentifiedStatusSyncMessageContent(with: sentBuilder, tx: tx)
do {
let syncMessageBuilder = SSKProtoSyncMessage.builder()
syncMessageBuilder.setSent(try sentBuilder.build())
return syncMessageBuilder
} catch {
owsFailDebug("couldn't serialize sent transcript: \(error)")
return nil
}
}
override var relatedUniqueIds: Set<String> {
return super.relatedUniqueIds.union([self.message.uniqueId])
}
func prepareDataSyncMessageContent(
with sentBuilder: SSKProtoSyncMessageSentBuilder,
tx: DBReadTransaction,
) -> Bool {
let dataMessage: SSKProtoDataMessage
if message.isViewOnceMessage {
let dataBuilder = SSKProtoDataMessage.builder()
dataBuilder.setTimestamp(message.timestamp)
dataBuilder.setExpireTimer(message.expiresInSeconds)
if let expireTimerVersion = message.expireTimerVersion {
owsAssertDebug(expireTimerVersion.uint32Value >= 1)
dataBuilder.setExpireTimerVersion(expireTimerVersion.uint32Value)
}
dataBuilder.setIsViewOnce(true)
dataBuilder.setRequiredProtocolVersion(UInt32(SSKProtoDataMessageProtocolVersion.viewOnceVideo.rawValue))
if let groupThread = messageThread as? TSGroupThread {
switch groupThread.groupModel.groupsVersion {
case .V1:
Logger.error("[GV1] Failed to build sync message contents for V1 groups message!")
return false
case .V2:
guard let groupModel = groupThread.groupModel as? TSGroupModelV2 else {
return false
}
do {
let groupContextV2 = try GroupsV2Protos.buildGroupContextProto(
groupModel: groupModel,
groupChangeProtoData: nil,
)
dataBuilder.setGroupV2(groupContextV2)
} catch {
owsFailDebug("Error \(error)")
return false
}
}
}
do {
dataMessage = try dataBuilder.build()
} catch {
owsFailDebug("Could not build protobuf: \(error)")
return false
}
} else {
guard let newDataMessage = message.buildDataMessage(messageThread, transaction: tx) else {
owsFailDebug("Could not build protobuf")
return false
}
dataMessage = newDataMessage
}
sentBuilder.setMessage(dataMessage)
sentBuilder.setExpirationStartTimestamp(message.timestamp)
return true
}
private func prepareUnidentifiedStatusSyncMessageContent(
with sentBuilder: SSKProtoSyncMessageSentBuilder,
tx: DBReadTransaction,
) {
for recipientAddress in message.sentRecipientAddresses() {
guard let recipientState = message.recipientState(for: recipientAddress) else {
owsFailDebug("Unexpectedly missing recipient state for address?")
continue
}
guard let recipientServiceId = recipientAddress.serviceId else {
owsFailDebug("Missing service ID for sent recipient!")
continue
}
let statusBuilder = SSKProtoSyncMessageSentUnidentifiedDeliveryStatus.builder()
statusBuilder.setDestinationServiceIDBinary(recipientServiceId.serviceIdBinary)
statusBuilder.setUnidentified(recipientState.wasSentByUD)
sentBuilder.addUnidentifiedStatus(statusBuilder.buildInfallibly())
}
}
}