Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/SignalServiceKit/Messages/Edit/OutgoingEditMessage.swift
1 views
//
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import Foundation

// This needs to reflect the edit as represented (and sourced) from the db.
@objc
public final class OutgoingEditMessage: TransientOutgoingMessage {
    override public class var supportsSecureCoding: Bool { true }

    public required init?(coder: NSCoder) {
        guard let editedMessage = coder.decodeObject(of: TSOutgoingMessage.self, forKey: "editedMessage") else {
            return nil
        }
        self.editedMessage = editedMessage
        guard let targetMessageTimestamp = coder.decodeObject(of: NSNumber.self, forKey: "targetMessageTimestamp") else {
            return nil
        }
        self.targetMessageTimestamp = targetMessageTimestamp.uint64Value
        super.init(coder: coder)
    }

    override public func encode(with coder: NSCoder) {
        super.encode(with: coder)
        coder.encode(editedMessage, forKey: "editedMessage")
        coder.encode(NSNumber(value: self.targetMessageTimestamp), forKey: "targetMessageTimestamp")
    }

    override public var hash: Int {
        var hasher = Hasher()
        hasher.combine(super.hash)
        hasher.combine(editedMessage)
        hasher.combine(targetMessageTimestamp)
        return hasher.finalize()
    }

    override public func isEqual(_ object: Any?) -> Bool {
        guard let object = object as? Self else { return false }
        guard super.isEqual(object) else { return false }
        guard self.editedMessage == object.editedMessage else { return false }
        guard self.targetMessageTimestamp == object.targetMessageTimestamp else { return false }
        return true
    }

    // MARK: - Edit target data

    let editedMessage: TSOutgoingMessage
    let targetMessageTimestamp: UInt64

    // MARK: - Overrides

    @objc
    override public var debugDescription: String { "editMessage" }

    @objc
    override var shouldRecordSendLog: Bool { true }

    @objc
    override var contentHint: SealedSenderContentHint { .implicit }

    // MARK: - Initialization

    @objc
    public init(
        thread: TSThread,
        targetMessageTimestamp: UInt64,
        editMessage: TSOutgoingMessage,
        transaction: DBReadTransaction,
    ) {
        self.targetMessageTimestamp = targetMessageTimestamp
        self.editedMessage = editMessage

        let builder: TSOutgoingMessageBuilder = .withDefaultValues(
            thread: thread,
            timestamp: editMessage.timestamp,
        )
        super.init(
            outgoingMessageWith: builder,
            additionalRecipients: [],
            explicitRecipients: [],
            skippedRecipients: [],
            transaction: transaction,
        )
    }

    // MARK: - Builders

    override public func contentBuilder(
        thread: TSThread,
        transaction tx: DBReadTransaction,
    ) -> SSKProtoContentBuilder? {

        let editBuilder = SSKProtoEditMessage.builder()
        let contentBuilder = SSKProtoContent.builder()

        guard let targetDataMessageBuilder = editedMessage.dataMessageBuilder(with: thread, transaction: tx) else {
            owsFailDebug("failed to build outgoing edit data message")
            return nil
        }

        do {
            editBuilder.setDataMessage(try targetDataMessageBuilder.build())
            editBuilder.setTargetSentTimestamp(self.targetMessageTimestamp)

            contentBuilder.setEditMessage(try editBuilder.build())

            return contentBuilder
        } catch {
            owsFailDebug("failed to build protobuf: \(error)")
            return nil
        }
    }

    override public func dataMessageBuilder(
        with thread: TSThread,
        transaction: DBReadTransaction,
    ) -> SSKProtoDataMessageBuilder? {
        return editedMessage.dataMessageBuilder(
            with: thread,
            transaction: transaction,
        )
    }

    override public func buildSyncTranscriptMessage(
        localThread: TSContactThread,
        tx: DBWriteTransaction,
    ) throws -> OutgoingSyncMessage {
        guard let thread = thread(tx: tx) else {
            throw OWSAssertionError("missing thread for interaction")
        }

        return OutgoingEditMessageSyncTranscript(
            localThread: localThread,
            messageThread: thread,
            message: self,
            isRecipientUpdate: false,
            tx: tx,
        )
    }

    /// This override is required to properly update the correct interaction row
    /// when delivery receipts are processed. Without this, the delivery is
    /// registered against the OutgoingEditMessage, which doesn't have a backing
    /// entry in the interactions table. Instead, when updating this message,
    /// ensure that the `recipientAddressStates` are in sync between the
    /// OutgoingEditMessage and its wrapped TSOutgoingMessage.
    override public func anyUpdateOutgoingMessage(
        transaction tx: DBWriteTransaction,
        block: (TSOutgoingMessage) -> Void,
    ) {
        super.anyUpdateOutgoingMessage(transaction: tx, block: block)

        if let editedMessage = TSOutgoingMessage.fetchOutgoingMessageViaCache(uniqueId: editedMessage.uniqueId, transaction: tx) {
            editedMessage.anyUpdateOutgoingMessage(transaction: tx, block: block)
        }
    }
}