Require acknowledgement to send to verified users who have unsigned devices or have changed their identity. (#3215)

* Refactor HeroImage style.

* Add a screen to resolve (crypto-related) timeline item send failures.

* Refactor send failures.

* Update the SDK.
This commit is contained in:
Doug
2024-09-10 09:56:11 +01:00
committed by GitHub
parent f6c8aae09e
commit cc4942fb78
86 changed files with 1422 additions and 158 deletions
@@ -20,7 +20,7 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
private var innerPinnedEventsTimelineTask: Task<TimelineProxyProtocol?, Never>?
var pinnedEventsTimeline: TimelineProxyProtocol? {
get async {
// Check if is alrrady available.
// Check if is already available.
if let innerPinnedEventsTimeline {
return innerPinnedEventsTimeline
// Otherwise check if there is already a task loading it, and wait for it.
@@ -35,7 +35,10 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
}
do {
let timeline = try await TimelineProxy(timeline: room.pinnedEventsTimeline(internalIdPrefix: nil, maxEventsToLoad: 100), kind: .pinned)
let timeline = try await TimelineProxy(timeline: room.pinnedEventsTimeline(internalIdPrefix: nil,
maxEventsToLoad: 100,
maxConcurrentRequests: 10),
kind: .pinned)
await timeline.subscribeForUpdates()
innerPinnedEventsTimeline = timeline
return timeline
@@ -377,6 +380,51 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
}
}
func resend(itemID: TimelineItemIdentifier) async -> Result<Void, RoomProxyError> {
guard let transactionID = itemID.transactionID else {
MXLog.error("Attempting to resend an item that has no transaction ID: \(itemID)")
return .failure(.missingTransactionID)
}
do {
try await room.tryResend(transactionId: transactionID)
return .success(())
} catch {
MXLog.error("Failed resending \(transactionID) with error: \(error)")
return .failure(.sdkError(error))
}
}
func ignoreDeviceTrustAndResend(devices: [String: [String]], itemID: TimelineItemIdentifier) async -> Result<Void, RoomProxyError> {
guard let transactionID = itemID.transactionID else {
MXLog.error("Attempting to resend an item that has no transaction ID: \(itemID)")
return .failure(.missingTransactionID)
}
do {
try await room.ignoreDeviceTrustAndResend(devices: devices, transactionId: transactionID)
return .success(())
} catch {
MXLog.error("Failed trusting devices \(devices) and resending \(transactionID) with error: \(error)")
return .failure(.sdkError(error))
}
}
func withdrawVerificationAndResend(userIDs: [String], itemID: TimelineItemIdentifier) async -> Result<Void, RoomProxyError> {
guard let transactionID = itemID.transactionID else {
MXLog.error("Attempting to resend an item that has no transaction ID: \(itemID)")
return .failure(.missingTransactionID)
}
do {
try await room.withdrawVerificationAndResend(userIds: userIDs, transactionId: transactionID)
return .success(())
} catch {
MXLog.error("Failed withdrawing verification of \(userIDs) and resending \(transactionID) with error: \(error)")
return .failure(.sdkError(error))
}
}
// MARK: - Room flags
func flagAsUnread(_ isUnread: Bool) async -> Result<Void, RoomProxyError> {
@@ -15,6 +15,7 @@ enum RoomProxyError: Error {
case invalidURL
case invalidMedia
case eventNotFound
case missingTransactionID
}
enum RoomProxyType {
@@ -111,6 +112,12 @@ protocol JoinedRoomProxyProtocol: RoomProxyProtocol {
/// https://spec.matrix.org/v1.9/client-server-api/#typing-notifications
@discardableResult func sendTypingNotification(isTyping: Bool) async -> Result<Void, RoomProxyError>
func resend(itemID: TimelineItemIdentifier) async -> Result<Void, RoomProxyError>
func ignoreDeviceTrustAndResend(devices: [String: [String]], itemID: TimelineItemIdentifier) async -> Result<Void, RoomProxyError>
func withdrawVerificationAndResend(userIDs: [String], itemID: TimelineItemIdentifier) async -> Result<Void, RoomProxyError>
// MARK: - Room Flags
func flagAsUnread(_ isUnread: Bool) async -> Result<Void, RoomProxyError>
@@ -59,13 +59,7 @@ enum TimelineItemProxy {
enum TimelineItemDeliveryStatus: Hashable {
case sending
case sent
case sendingFailed(SendFailureReason)
enum SendFailureReason: Hashable {
case verifiedUserHasUnsignedDevice(devices: [String: [String]])
case verifiedUserChangedIdentity(users: [String])
case unknown
}
case sendingFailed(TimelineItemSendFailure)
var isSendingFailed: Bool {
switch self {
@@ -75,6 +69,24 @@ enum TimelineItemDeliveryStatus: Hashable {
}
}
/// The reason a timeline item failed to send.
enum TimelineItemSendFailure: Hashable {
enum VerifiedUser: Hashable {
case hasUnsignedDevice(devices: [String: [String]])
case changedIdentity(users: [String])
var affectedUserIDs: [String] {
switch self {
case .hasUnsignedDevice(let devices): Array(devices.keys)
case .changedIdentity(let users): users
}
}
}
case verifiedUser(VerifiedUser)
case unknown
}
/// A light wrapper around event timeline items returned from Rust.
class EventTimelineItemProxy {
let item: MatrixRustSDK.EventTimelineItem
@@ -98,9 +110,9 @@ class EventTimelineItemProxy {
case .sent:
return .sent
case .verifiedUserHasUnsignedDevice(devices: let devices):
return .sendingFailed(.verifiedUserHasUnsignedDevice(devices: devices))
return .sendingFailed(.verifiedUser(.hasUnsignedDevice(devices: devices)))
case .verifiedUserChangedIdentity(users: let users):
return .sendingFailed(.verifiedUserChangedIdentity(users: users))
return .sendingFailed(.verifiedUser(.changedIdentity(users: users)))
case .crossSigningNotSetup, .sendingFromUnverifiedDevice:
return .sendingFailed(.unknown)
}