186 lines
6.5 KiB
Swift
186 lines
6.5 KiB
Swift
//
|
|
// Copyright 2026 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import Foundation
|
|
import LibSignalClient
|
|
|
|
@objc(OWSSyncMessageRequestResponseMessage)
|
|
public final class OutgoingMessageRequestResponseSyncMessage: OutgoingSyncMessage {
|
|
|
|
public enum ResponseType: UInt64 {
|
|
case accept = 0
|
|
case delete = 1
|
|
case block = 2
|
|
case blockAndDelete = 3
|
|
case spam = 4
|
|
case blockAndSpam = 5
|
|
|
|
fileprivate var asProtoResponseType: SSKProtoSyncMessageMessageRequestResponseType {
|
|
switch self {
|
|
case .accept: .accept
|
|
case .delete: .delete
|
|
case .block: .block
|
|
case .blockAndDelete: .blockAndDelete
|
|
case .spam: .spam
|
|
case .blockAndSpam: .blockAndSpam
|
|
}
|
|
}
|
|
|
|
public var shouldBlockThread: Bool {
|
|
switch self {
|
|
case .block, .blockAndDelete, .blockAndSpam:
|
|
return true
|
|
case .accept, .delete, .spam:
|
|
return false
|
|
}
|
|
}
|
|
|
|
public var shouldDeleteThread: Bool {
|
|
switch self {
|
|
case .delete, .blockAndDelete:
|
|
return true
|
|
case .accept, .block, .spam, .blockAndSpam:
|
|
return false
|
|
}
|
|
}
|
|
|
|
public var shouldReportSpam: Bool {
|
|
switch self {
|
|
case .spam, .blockAndSpam:
|
|
return true
|
|
case .accept, .block, .delete, .blockAndDelete:
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
// v0: The sending thread is also the acted-upon thread.
|
|
// v1: (skipped to avoid ambiguity)
|
|
// v2: The acted-upon thread is stored in groupId/threadAci.
|
|
let version: UInt
|
|
|
|
let groupId: Data?
|
|
let threadAci: Aci?
|
|
let responseType: ResponseType
|
|
|
|
override public class var supportsSecureCoding: Bool { true }
|
|
|
|
override public func encode(with coder: NSCoder) {
|
|
super.encode(with: coder)
|
|
if let groupId {
|
|
coder.encode(groupId, forKey: "groupId")
|
|
}
|
|
coder.encode(NSNumber(value: self.responseType.rawValue), forKey: "responseType")
|
|
if let threadAci {
|
|
coder.encode(threadAci.serviceIdString, forKey: "threadAci")
|
|
}
|
|
coder.encode(NSNumber(value: self.version), forKey: "version")
|
|
}
|
|
|
|
public required init?(coder: NSCoder) {
|
|
guard
|
|
let rawResponseType = coder.decodeObject(of: NSNumber.self, forKey: "responseType"),
|
|
let responseType = ResponseType(rawValue: rawResponseType.uint64Value)
|
|
else {
|
|
return nil
|
|
}
|
|
self.responseType = responseType
|
|
self.groupId = coder.decodeObject(of: NSData.self, forKey: "groupId") as Data?
|
|
let threadAciString = coder.decodeObject(of: NSString.self, forKey: "threadAci") as String?
|
|
if let threadAciString {
|
|
guard let threadAci = Aci.parseFrom(aciString: threadAciString) else {
|
|
return nil
|
|
}
|
|
self.threadAci = threadAci
|
|
} else {
|
|
self.threadAci = nil
|
|
}
|
|
self.version = coder.decodeObject(of: NSNumber.self, forKey: "version")?.uintValue ?? 0
|
|
super.init(coder: coder)
|
|
}
|
|
|
|
override public var hash: Int {
|
|
var hasher = Hasher()
|
|
hasher.combine(super.hash)
|
|
hasher.combine(self.groupId)
|
|
hasher.combine(self.responseType)
|
|
hasher.combine(self.threadAci)
|
|
hasher.combine(self.version)
|
|
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.groupId == object.groupId else { return false }
|
|
guard self.responseType == object.responseType else { return false }
|
|
guard self.threadAci == object.threadAci else { return false }
|
|
guard self.version == object.version else { return false }
|
|
return true
|
|
}
|
|
|
|
init(
|
|
localThread: TSContactThread,
|
|
messageRequestThread: TSThread,
|
|
responseType: ResponseType,
|
|
tx: DBReadTransaction,
|
|
) {
|
|
self.version = 2
|
|
switch messageRequestThread {
|
|
case let thread as TSGroupThread:
|
|
self.groupId = thread.groupId
|
|
self.threadAci = nil
|
|
case let thread as TSContactThread:
|
|
self.groupId = nil
|
|
self.threadAci = thread.contactAddress.aci
|
|
owsAssertDebug(self.threadAci != nil, "must have ACI when responding to a message request")
|
|
default:
|
|
self.groupId = nil
|
|
self.threadAci = nil
|
|
owsFailDebug("can't response to thread type")
|
|
}
|
|
self.responseType = responseType
|
|
super.init(localThread: localThread, tx: tx)
|
|
}
|
|
|
|
override public func syncMessageBuilder(tx: DBReadTransaction) -> SSKProtoSyncMessageBuilder? {
|
|
let messageRequestResponseBuilder = SSKProtoSyncMessageMessageRequestResponse.builder()
|
|
messageRequestResponseBuilder.setType(self.responseType.asProtoResponseType)
|
|
|
|
if let groupId {
|
|
messageRequestResponseBuilder.setGroupID(groupId)
|
|
} else if let threadAci {
|
|
messageRequestResponseBuilder.setThreadAciBinary(threadAci.serviceIdBinary)
|
|
} else if self.version < 2 {
|
|
// Fallback behavior. Messages of this version are no longer created.
|
|
// Eventually, all enqueued messages of this type should be resolved
|
|
// (either because they have been sent or because they ran out of retries).
|
|
let thread = self.thread(tx: tx)
|
|
guard let thread else {
|
|
owsFailDebug("Missing thread for message request response")
|
|
return nil
|
|
}
|
|
|
|
switch thread {
|
|
case let thread as TSGroupThread:
|
|
messageRequestResponseBuilder.setGroupID(thread.groupModel.groupId)
|
|
case let thread as TSContactThread:
|
|
if let threadAci = thread.contactAddress.serviceId as? Aci {
|
|
messageRequestResponseBuilder.setThreadAciBinary(threadAci.serviceIdBinary)
|
|
}
|
|
default:
|
|
owsFailDebug("Thread is invalid type for message request response")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
let builder = SSKProtoSyncMessage.builder()
|
|
builder.setMessageRequestResponse(messageRequestResponseBuilder.buildInfallibly())
|
|
return builder
|
|
}
|
|
|
|
override public var isUrgent: Bool { false }
|
|
}
|