mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:31:27 +00:00
- automatically retry failed requests as soon as the network is availble again - remove manual retry options
This commit is contained in:
committed by
Stefan Ceriu
parent
53fc5df55b
commit
6c1647b851
@@ -105,7 +105,7 @@ class AuthenticationServiceProxy: AuthenticationServiceProxyProtocol {
|
||||
initialDeviceName: initialDeviceName,
|
||||
deviceId: deviceID)
|
||||
|
||||
let refreshToken = try? await client.session().refreshToken
|
||||
let refreshToken = try? client.session().refreshToken
|
||||
|
||||
if refreshToken != nil {
|
||||
MXLog.warning("Refresh token found for a non oidc session, can't restore session, logging out")
|
||||
|
||||
@@ -45,6 +45,9 @@ class ClientProxy: ClientProxyProtocol {
|
||||
// periphery:ignore - required for instance retention in the rust codebase
|
||||
private var verificationStateListenerTaskHandle: TaskHandle?
|
||||
|
||||
// periphery:ignore - required for instance retention in the rust codebase
|
||||
private var sendingQueueListenerTaskHandle: TaskHandle?
|
||||
|
||||
private var delegateHandle: TaskHandle?
|
||||
|
||||
// These following summary providers both operate on the same allRooms() list but
|
||||
@@ -161,6 +164,17 @@ class ClientProxy: ClientProxyProtocol {
|
||||
verificationStateListenerTaskHandle = client.encryption().verificationStateListener(listener: VerificationStateListenerProxy { [weak self] verificationState in
|
||||
self?.updateVerificationState(verificationState)
|
||||
})
|
||||
|
||||
sendingQueueListenerTaskHandle = client.subscribeToSendingQueueStatus(listener: SendingQueueStatusListenerProxy { [weak self] enabled in
|
||||
guard let self else { return }
|
||||
|
||||
MXLog.error("Sending queue status changed to enabled: \(enabled)")
|
||||
|
||||
if enabled == false,
|
||||
networkMonitor.reachabilityPublisher.value == .reachable {
|
||||
setSendingQueueEnabled(true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func updateVerificationState(_ verificationState: VerificationState) {
|
||||
@@ -578,6 +592,11 @@ class ClientProxy: ClientProxyProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func setSendingQueueEnabled(_ enabled: Bool) {
|
||||
MXLog.info("Setting sending queue to enabled: \(enabled)")
|
||||
client.enableSendingQueue(enable: enabled)
|
||||
}
|
||||
|
||||
// MARK: Ignored users
|
||||
|
||||
func ignoreUser(_ userID: String) async -> Result<Void, ClientProxyError> {
|
||||
@@ -784,7 +803,7 @@ class ClientProxy: ClientProxyProtocol {
|
||||
if roomListItem?.isTimelineInitialized() == false {
|
||||
try await roomListItem?.initTimeline(eventTypeFilter: eventFilters, internalIdPrefix: nil)
|
||||
}
|
||||
let fullRoom = try await roomListItem?.fullRoom()
|
||||
let fullRoom = try roomListItem?.fullRoom()
|
||||
|
||||
return (roomListItem, fullRoom)
|
||||
} catch {
|
||||
@@ -920,6 +939,18 @@ private class IgnoredUsersListenerProxy: IgnoredUsersListener {
|
||||
}
|
||||
}
|
||||
|
||||
private class SendingQueueStatusListenerProxy: SendingQueueStatusListener {
|
||||
private let onUpdateClosure: (Bool) -> Void
|
||||
|
||||
init(onUpdateClosure: @escaping (Bool) -> Void) {
|
||||
self.onUpdateClosure = onUpdateClosure
|
||||
}
|
||||
|
||||
func onUpdate(newValue: Bool) {
|
||||
onUpdateClosure(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
private extension RoomPreviewDetails {
|
||||
init(_ roomPreview: RoomPreview) {
|
||||
self = RoomPreviewDetails(roomID: roomPreview.roomId,
|
||||
|
||||
@@ -160,6 +160,8 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
|
||||
func roomDirectorySearchProxy() -> RoomDirectorySearchProxyProtocol
|
||||
|
||||
func resolveRoomAlias(_ alias: String) async -> Result<ResolvedRoomAlias, ClientProxyError>
|
||||
|
||||
func setSendingQueueEnabled(_ enabled: Bool)
|
||||
|
||||
// MARK: - Ignored users
|
||||
|
||||
|
||||
+5
-21
@@ -92,11 +92,11 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
||||
intentionalMentions: IntentionalMentions) async { }
|
||||
|
||||
func toggleReaction(_ reaction: String, to itemID: TimelineItemIdentifier) async { }
|
||||
|
||||
func editMessage(_ newMessage: String,
|
||||
html: String?,
|
||||
original itemID: TimelineItemIdentifier,
|
||||
intentionalMentions: IntentionalMentions) async { }
|
||||
|
||||
func edit(_ timelineItemID: TimelineItemIdentifier,
|
||||
message: String,
|
||||
html: String?,
|
||||
intentionalMentions: IntentionalMentions) async { }
|
||||
|
||||
func redact(_ itemID: TimelineItemIdentifier) async { }
|
||||
|
||||
@@ -109,23 +109,7 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
||||
}
|
||||
|
||||
func retryDecryption(for sessionID: String) async { }
|
||||
|
||||
func retrySending(itemID: TimelineItemIdentifier) async {
|
||||
guard let transactionID = itemID.transactionID else {
|
||||
return
|
||||
}
|
||||
|
||||
await roomProxy?.timeline.retrySend(transactionID: transactionID)
|
||||
}
|
||||
|
||||
func cancelSending(itemID: TimelineItemIdentifier) async {
|
||||
guard let transactionID = itemID.transactionID else {
|
||||
return
|
||||
}
|
||||
|
||||
await roomProxy?.timeline.cancelSend(transactionID: transactionID)
|
||||
}
|
||||
|
||||
func eventTimestamp(for itemID: TimelineItemIdentifier) -> Date? {
|
||||
timelineItemsTimestamp[itemID] ?? .now
|
||||
}
|
||||
|
||||
+23
-58
@@ -131,7 +131,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
return
|
||||
}
|
||||
|
||||
_ = await roomProxy.timeline.sendReadReceipt(for: eventID, type: receiptType)
|
||||
_ = await activeTimeline.sendReadReceipt(for: eventID, type: receiptType)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,10 +162,10 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
return
|
||||
}
|
||||
|
||||
switch await roomProxy.timeline.sendMessage(message,
|
||||
html: html,
|
||||
inReplyTo: inReplyTo,
|
||||
intentionalMentions: intentionalMentions) {
|
||||
switch await activeTimeline.sendMessage(message,
|
||||
html: html,
|
||||
inReplyTo: inReplyTo,
|
||||
intentionalMentions: intentionalMentions) {
|
||||
case .success:
|
||||
MXLog.info("Finished sending message")
|
||||
case .failure(let error):
|
||||
@@ -188,38 +188,27 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func editMessage(_ newMessage: String,
|
||||
html: String?,
|
||||
original itemID: TimelineItemIdentifier,
|
||||
intentionalMentions: IntentionalMentions) async {
|
||||
func edit(_ timelineItemID: TimelineItemIdentifier,
|
||||
message: String,
|
||||
html: String?,
|
||||
intentionalMentions: IntentionalMentions) async {
|
||||
MXLog.info("Edit message in \(roomID)")
|
||||
if let timelineItem = timelineItems.firstUsingStableID(itemID),
|
||||
let item = timelineItem as? EventBasedTimelineItemProtocol,
|
||||
item.hasFailedToSend {
|
||||
MXLog.info("Editing a failed echo, will cancel and resend it as a new message")
|
||||
await cancelSending(itemID: itemID)
|
||||
await sendMessage(newMessage, html: html, intentionalMentions: intentionalMentions)
|
||||
} else if let eventID = itemID.eventID {
|
||||
switch await activeTimeline.editMessage(newMessage,
|
||||
html: html,
|
||||
original: eventID,
|
||||
intentionalMentions: intentionalMentions) {
|
||||
case .success:
|
||||
MXLog.info("Finished editing message")
|
||||
case .failure(let error):
|
||||
MXLog.error("Failed editing message with error: \(error)")
|
||||
}
|
||||
} else {
|
||||
MXLog.error("Editing failed: missing identifiers")
|
||||
|
||||
switch await activeTimeline.edit(timelineItemID,
|
||||
message: message,
|
||||
html: html,
|
||||
intentionalMentions: intentionalMentions) {
|
||||
case .success:
|
||||
MXLog.info("Finished editing message")
|
||||
case .failure(let error):
|
||||
MXLog.error("Failed editing message with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func redact(_ itemID: TimelineItemIdentifier) async {
|
||||
func redact(_ timelineItemID: TimelineItemIdentifier) async {
|
||||
MXLog.info("Send redaction in \(roomID)")
|
||||
guard let eventID = itemID.eventID else {
|
||||
return
|
||||
}
|
||||
switch await roomProxy.redact(eventID) {
|
||||
|
||||
switch await activeTimeline.redact(timelineItemID, reason: nil) {
|
||||
case .success:
|
||||
MXLog.info("Finished redacting message")
|
||||
case .failure(let error):
|
||||
@@ -227,12 +216,8 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func messageEventContent(for itemID: TimelineItemIdentifier) async -> RoomMessageEventContentWithoutRelation? {
|
||||
guard let eventID = itemID.eventID else {
|
||||
MXLog.warning("The item doesn't have an event ID.")
|
||||
return nil
|
||||
}
|
||||
return await activeTimeline.messageEventContent(for: eventID)
|
||||
func messageEventContent(for timelineItemID: TimelineItemIdentifier) async -> RoomMessageEventContentWithoutRelation? {
|
||||
await activeTimeline.messageEventContent(for: timelineItemID)
|
||||
}
|
||||
|
||||
// Handle this parallel to the timeline items so we're not forced
|
||||
@@ -256,26 +241,6 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
await activeTimeline.retryDecryption(for: sessionID)
|
||||
}
|
||||
|
||||
func retrySending(itemID: TimelineItemIdentifier) async {
|
||||
guard let transactionID = itemID.transactionID else {
|
||||
MXLog.error("Failed Retry Send: missing transaction ID")
|
||||
return
|
||||
}
|
||||
|
||||
MXLog.info("Retry sending in \(roomID)")
|
||||
await roomProxy.timeline.retrySend(transactionID: transactionID)
|
||||
}
|
||||
|
||||
func cancelSending(itemID: TimelineItemIdentifier) async {
|
||||
guard let transactionID = itemID.transactionID else {
|
||||
MXLog.error("Failed Cancel Send: missing transaction ID")
|
||||
return
|
||||
}
|
||||
|
||||
MXLog.info("Cancelling send in \(roomID)")
|
||||
await roomProxy.timeline.cancelSend(transactionID: transactionID)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
/// The cancellable used to update the timeline items.
|
||||
|
||||
+4
-8
@@ -59,10 +59,10 @@ protocol RoomTimelineControllerProtocol {
|
||||
inReplyTo itemID: TimelineItemIdentifier?,
|
||||
intentionalMentions: IntentionalMentions) async
|
||||
|
||||
func editMessage(_ newMessage: String,
|
||||
html: String?,
|
||||
original itemID: TimelineItemIdentifier,
|
||||
intentionalMentions: IntentionalMentions) async
|
||||
func edit(_ timelineItemID: TimelineItemIdentifier,
|
||||
message: String,
|
||||
html: String?,
|
||||
intentionalMentions: IntentionalMentions) async
|
||||
|
||||
func toggleReaction(_ reaction: String, to itemID: TimelineItemIdentifier) async
|
||||
|
||||
@@ -74,10 +74,6 @@ protocol RoomTimelineControllerProtocol {
|
||||
|
||||
func retryDecryption(for sessionID: String) async
|
||||
|
||||
func retrySending(itemID: TimelineItemIdentifier) async
|
||||
|
||||
func cancelSending(itemID: TimelineItemIdentifier) async
|
||||
|
||||
func eventTimestamp(for itemID: TimelineItemIdentifier) -> Date?
|
||||
}
|
||||
|
||||
|
||||
@@ -78,11 +78,8 @@ class EventTimelineItemProxy {
|
||||
}
|
||||
|
||||
switch localSendState {
|
||||
case .notSentYet:
|
||||
case .notSentYet, .sendingFailed:
|
||||
return .sending
|
||||
// cancelled is exactly like sendingFailed but does not contain an error
|
||||
case .sendingFailed, .cancelled:
|
||||
return .sendingFailed
|
||||
case .sent:
|
||||
return .sent
|
||||
}
|
||||
|
||||
@@ -41,9 +41,9 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
return buildEncryptedTimelineItem(eventItemProxy, encryptedMessage, isOutgoing)
|
||||
case .redactedMessage:
|
||||
return buildRedactedTimelineItem(eventItemProxy, isOutgoing)
|
||||
case .sticker(let body, let imageInfo, let urlString):
|
||||
guard let url = URL(string: urlString) else {
|
||||
MXLog.error("Invalid sticker url string: \(urlString)")
|
||||
case .sticker(let body, let imageInfo, let mediaSource):
|
||||
guard let url = URL(string: mediaSource.url()) else {
|
||||
MXLog.error("Invalid sticker url string: \(mediaSource.url())")
|
||||
return buildUnsupportedTimelineItem(eventItemProxy, "m.sticker", "Invalid Sticker URL", isOutgoing)
|
||||
}
|
||||
|
||||
|
||||
@@ -33,11 +33,6 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
private let forwardPaginationStatusSubject = CurrentValueSubject<PaginationStatus, Never>(.timelineEndReached)
|
||||
private let timelineUpdatesSubject = PassthroughSubject<[TimelineDiff], Never>()
|
||||
|
||||
private let actionsSubject = PassthroughSubject<TimelineProxyAction, Never>()
|
||||
var actions: AnyPublisher<TimelineProxyAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
let isLive: Bool
|
||||
|
||||
private var innerTimelineProvider: RoomTimelineProviderProtocol!
|
||||
@@ -83,38 +78,6 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
paginationStatePublisher: paginationStatePublisher)
|
||||
}
|
||||
|
||||
func cancelSend(transactionID: String) async {
|
||||
MXLog.info("Cancelling sending for transaction ID: \(transactionID)")
|
||||
|
||||
return await Task.dispatch(on: messageSendingDispatchQueue) {
|
||||
self.timeline.cancelSend(txnId: transactionID)
|
||||
MXLog.info("Finished cancelling sending for transaction ID: \(transactionID)")
|
||||
}
|
||||
}
|
||||
|
||||
func editMessage(_ message: String,
|
||||
html: String?,
|
||||
original eventID: String,
|
||||
intentionalMentions: IntentionalMentions) async -> Result<Void, TimelineProxyError> {
|
||||
MXLog.info("Editing message with original event ID: \(eventID)")
|
||||
|
||||
let messageContent = buildMessageContentFor(message,
|
||||
html: html,
|
||||
intentionalMentions: intentionalMentions.toRustMentions())
|
||||
|
||||
do {
|
||||
let originalEvent = try await timeline.getEventTimelineItemByEventId(eventId: eventID)
|
||||
try await timeline.edit(newContent: messageContent, editItem: originalEvent)
|
||||
|
||||
MXLog.info("Finished editing message with original event ID: \(eventID)")
|
||||
|
||||
return .success(())
|
||||
} catch {
|
||||
MXLog.error("Failed editing message with original event ID: \(eventID) with error: \(error)")
|
||||
return .failure(.failedEditingMessage)
|
||||
}
|
||||
}
|
||||
|
||||
func fetchDetails(for eventID: String) {
|
||||
Task {
|
||||
do {
|
||||
@@ -127,17 +90,8 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func messageEventContent(for eventID: String) async -> RoomMessageEventContentWithoutRelation? {
|
||||
MXLog.info("Fetching event content for \(eventID)")
|
||||
|
||||
do {
|
||||
let result = try await timeline.getTimelineEventContentByEventId(eventId: eventID)
|
||||
MXLog.info("Finished fetching event content for eventID: \(eventID)")
|
||||
return result
|
||||
} catch {
|
||||
MXLog.error("Failed fetching event content for eventID: \(eventID) with error: \(error)")
|
||||
return nil
|
||||
}
|
||||
func messageEventContent(for timelineItemID: TimelineItemIdentifier) async -> RoomMessageEventContentWithoutRelation? {
|
||||
await timelineProvider.itemProxies.firstEventTimelineItemUsingID(timelineItemID)?.content().asMessage()?.content()
|
||||
}
|
||||
|
||||
func paginateBackwards(requestSize: UInt16) async -> Result<Void, TimelineProxyError> {
|
||||
@@ -150,7 +104,7 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
return .success(())
|
||||
} catch {
|
||||
MXLog.error("Failed paginating backwards with error: \(error)")
|
||||
return .failure(.failedPaginatingBackwards)
|
||||
return .failure(.sdkError(error))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +113,7 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
// We need it to make sure we send a valid status after a failure.
|
||||
guard forwardPaginationStatusSubject.value == .idle else {
|
||||
MXLog.error("Attempting to paginate forwards when already at the end.")
|
||||
return .failure(.failedPaginatingBackwards)
|
||||
return .failure(.failedPaginatingEndReached)
|
||||
}
|
||||
|
||||
MXLog.info("Paginating forwards")
|
||||
@@ -174,7 +128,7 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
} catch {
|
||||
MXLog.error("Failed paginating forwards with error: \(error)")
|
||||
forwardPaginationStatusSubject.send(.idle)
|
||||
return .failure(.failedPaginatingBackwards)
|
||||
return .failure(.sdkError(error))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,15 +141,59 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func retrySend(transactionID: String) async {
|
||||
MXLog.info("Retrying sending for transactionID: \(transactionID)")
|
||||
func edit(_ timelineItemID: TimelineItemIdentifier,
|
||||
message: String, html: String?,
|
||||
intentionalMentions: IntentionalMentions) async -> Result<Void, TimelineProxyError> {
|
||||
MXLog.info("Editing timeline item: \(timelineItemID)")
|
||||
|
||||
return await Task.dispatch(on: messageSendingDispatchQueue) {
|
||||
self.timeline.retrySend(txnId: transactionID)
|
||||
MXLog.info("Finished retrying sending for transactionID: \(transactionID)")
|
||||
guard let eventTimelineItem = await timelineProvider.itemProxies.firstEventTimelineItemUsingID(timelineItemID) else {
|
||||
MXLog.error("Unknown timeline item: \(timelineItemID)")
|
||||
return .failure(.failedEditing)
|
||||
}
|
||||
|
||||
let messageContent = buildMessageContentFor(message,
|
||||
html: html,
|
||||
intentionalMentions: intentionalMentions.toRustMentions())
|
||||
|
||||
do {
|
||||
try await timeline.edit(newContent: messageContent, editItem: eventTimelineItem)
|
||||
|
||||
MXLog.info("Finished editing timeline item: \(timelineItemID)")
|
||||
|
||||
return .success(())
|
||||
} catch {
|
||||
MXLog.error("Failed editing timeline item: \(timelineItemID) with error: \(error)")
|
||||
return .failure(.sdkError(error))
|
||||
}
|
||||
}
|
||||
|
||||
func redact(_ timelineItemID: TimelineItemIdentifier, reason: String?) async -> Result<Void, TimelineProxyError> {
|
||||
MXLog.info("Redacting timeline item: \(timelineItemID)")
|
||||
|
||||
guard let eventTimelineItem = await timelineProvider.itemProxies.firstEventTimelineItemUsingID(timelineItemID) else {
|
||||
MXLog.error("Unknown timeline item: \(timelineItemID)")
|
||||
return .failure(.failedRedacting)
|
||||
}
|
||||
|
||||
do {
|
||||
let success = try await timeline.redactEvent(item: eventTimelineItem, reason: reason)
|
||||
|
||||
guard success else {
|
||||
MXLog.error("Failed redacting timeline item: \(timelineItemID)")
|
||||
return .failure(.failedRedacting)
|
||||
}
|
||||
|
||||
MXLog.info("Redacted timeline item: \(timelineItemID)")
|
||||
|
||||
return .success(())
|
||||
} catch {
|
||||
MXLog.error("Failed redacting timeline item: \(timelineItemID) with error: \(error)")
|
||||
return .failure(.sdkError(error))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Sending
|
||||
|
||||
func sendAudio(url: URL,
|
||||
audioInfo: AudioInfo,
|
||||
progressSubject: CurrentValueSubject<Double, Never>?,
|
||||
@@ -213,11 +211,9 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
MXLog.info("Finished sending audio")
|
||||
} catch {
|
||||
MXLog.error("Failed sending audio with error: \(error)")
|
||||
return .failure(.failedSendingMedia)
|
||||
return .failure(.sdkError(error))
|
||||
}
|
||||
|
||||
actionsSubject.send(.sentMessage)
|
||||
|
||||
return .success(())
|
||||
}
|
||||
|
||||
@@ -238,11 +234,9 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
MXLog.info("Finished sending file")
|
||||
} catch {
|
||||
MXLog.error("Failed sending file with error: \(error)")
|
||||
return .failure(.failedSendingMedia)
|
||||
return .failure(.sdkError(error))
|
||||
}
|
||||
|
||||
actionsSubject.send(.sentMessage)
|
||||
|
||||
return .success(())
|
||||
}
|
||||
|
||||
@@ -264,11 +258,9 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
MXLog.info("Finished sending image")
|
||||
} catch {
|
||||
MXLog.error("Failed sending image with error: \(error)")
|
||||
return .failure(.failedSendingMedia)
|
||||
return .failure(.sdkError(error))
|
||||
}
|
||||
|
||||
actionsSubject.send(.sentMessage)
|
||||
|
||||
return .success(())
|
||||
}
|
||||
|
||||
@@ -279,19 +271,15 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
assetType: AssetType?) async -> Result<Void, TimelineProxyError> {
|
||||
MXLog.info("Sending location")
|
||||
|
||||
return await Task.dispatch(on: messageSendingDispatchQueue) {
|
||||
self.timeline.sendLocation(body: body,
|
||||
geoUri: geoURI.string,
|
||||
description: description,
|
||||
zoomLevel: zoomLevel,
|
||||
assetType: assetType)
|
||||
|
||||
self.actionsSubject.send(.sentMessage)
|
||||
|
||||
MXLog.info("Finished sending location")
|
||||
|
||||
return .success(())
|
||||
}
|
||||
await timeline.sendLocation(body: body,
|
||||
geoUri: geoURI.string,
|
||||
description: description,
|
||||
zoomLevel: zoomLevel,
|
||||
assetType: assetType)
|
||||
|
||||
MXLog.info("Finished sending location")
|
||||
|
||||
return .success(())
|
||||
}
|
||||
|
||||
func sendVideo(url: URL,
|
||||
@@ -312,11 +300,9 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
MXLog.info("Finished sending video")
|
||||
} catch {
|
||||
MXLog.error("Failed sending video with error: \(error)")
|
||||
return .failure(.failedSendingMedia)
|
||||
return .failure(.sdkError(error))
|
||||
}
|
||||
|
||||
actionsSubject.send(.sentMessage)
|
||||
|
||||
return .success(())
|
||||
}
|
||||
|
||||
@@ -338,11 +324,9 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
MXLog.info("Finished sending voice message")
|
||||
} catch {
|
||||
MXLog.error("Failed sending vocie message with error: \(error)")
|
||||
return .failure(.failedSendingMedia)
|
||||
return .failure(.sdkError(error))
|
||||
}
|
||||
|
||||
actionsSubject.send(.sentMessage)
|
||||
|
||||
return .success(())
|
||||
}
|
||||
|
||||
@@ -366,36 +350,34 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
try await timeline.sendReply(msg: messageContent, replyItem: replyItem)
|
||||
MXLog.info("Finished sending reply to eventID: \(eventID)")
|
||||
} else {
|
||||
timeline.send(msg: messageContent)
|
||||
_ = try await timeline.send(msg: messageContent)
|
||||
MXLog.info("Finished sending message")
|
||||
}
|
||||
} catch {
|
||||
if let eventID {
|
||||
MXLog.error("Failed sending reply to eventID: \(eventID)")
|
||||
MXLog.error("Failed sending reply to eventID: \(eventID) with error: \(error)")
|
||||
} else {
|
||||
MXLog.error("Failed sending message")
|
||||
MXLog.error("Failed sending message with error: \(error)")
|
||||
}
|
||||
|
||||
return .failure(.failedSendingMessage)
|
||||
return .failure(.sdkError(error))
|
||||
}
|
||||
|
||||
actionsSubject.send(.sentMessage)
|
||||
|
||||
return .success(())
|
||||
}
|
||||
|
||||
func sendMessageEventContent(_ messageContent: RoomMessageEventContentWithoutRelation) async -> Result<Void, TimelineProxyError> {
|
||||
MXLog.info("Sending message content")
|
||||
|
||||
return await Task.dispatch(on: messageSendingDispatchQueue) {
|
||||
self.timeline.send(msg: messageContent)
|
||||
|
||||
self.actionsSubject.send(.sentMessage)
|
||||
|
||||
MXLog.info("Finished sending message content")
|
||||
|
||||
return .success(())
|
||||
|
||||
do {
|
||||
_ = try await timeline.send(msg: messageContent)
|
||||
} catch {
|
||||
MXLog.error("Failed sending message with error: \(error)")
|
||||
}
|
||||
|
||||
MXLog.info("Finished sending message content")
|
||||
|
||||
return .success(())
|
||||
}
|
||||
|
||||
func sendReadReceipt(for eventID: String, type: ReceiptType) async -> Result<Void, TimelineProxyError> {
|
||||
@@ -407,7 +389,7 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
return .success(())
|
||||
} catch {
|
||||
MXLog.error("Failed sending read receipt for eventID: \(eventID) with error: \(error)")
|
||||
return .failure(.failedSendingReadReceipt)
|
||||
return .failure(.sdkError(error))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -420,7 +402,7 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
return .success(())
|
||||
} catch {
|
||||
MXLog.error("Failed toggling reaction for eventID: \(eventID)")
|
||||
return .failure(.failedSendingReaction)
|
||||
return .failure(.sdkError(error))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,19 +411,15 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
func createPoll(question: String, answers: [String], pollKind: Poll.Kind) async -> Result<Void, TimelineProxyError> {
|
||||
MXLog.info("Creating poll")
|
||||
|
||||
return await Task.dispatch(on: .global()) {
|
||||
do {
|
||||
try self.timeline.createPoll(question: question, answers: answers, maxSelections: 1, pollKind: .init(pollKind: pollKind))
|
||||
|
||||
self.actionsSubject.send(.sentMessage)
|
||||
|
||||
MXLog.info("Finished creating poll")
|
||||
|
||||
return .success(())
|
||||
} catch {
|
||||
MXLog.error("Failed creating poll with error: \(error)")
|
||||
return .failure(.failedCreatingPoll)
|
||||
}
|
||||
do {
|
||||
try await timeline.createPoll(question: question, answers: answers, maxSelections: 1, pollKind: .init(pollKind: pollKind))
|
||||
|
||||
MXLog.info("Finished creating poll")
|
||||
|
||||
return .success(())
|
||||
} catch {
|
||||
MXLog.error("Failed creating poll with error: \(error)")
|
||||
return .failure(.sdkError(error))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -461,7 +439,7 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
return .success(())
|
||||
} catch {
|
||||
MXLog.error("Failed editing poll with eventID: \(eventID) with error: \(error)")
|
||||
return .failure(.failedEditingPoll)
|
||||
return .failure(.sdkError(error))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -477,7 +455,7 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
return .success(())
|
||||
} catch {
|
||||
MXLog.error("Failed ending poll with eventID: \(pollStartID) with error: \(error)")
|
||||
return .failure(.failedEndingPoll)
|
||||
return .failure(.sdkError(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -485,17 +463,15 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
func sendPollResponse(pollStartID: String, answers: [String]) async -> Result<Void, TimelineProxyError> {
|
||||
MXLog.info("Sending response for poll with eventID: \(pollStartID)")
|
||||
|
||||
return await Task.dispatch(on: .global()) {
|
||||
do {
|
||||
try self.timeline.sendPollResponse(pollStartId: pollStartID, answers: answers)
|
||||
|
||||
MXLog.info("Finished sending response for poll with eventID: \(pollStartID)")
|
||||
|
||||
return .success(())
|
||||
} catch {
|
||||
MXLog.error("Failed sending response for poll with eventID: \(pollStartID) with error: \(error)")
|
||||
return .failure(.failedSendingPollResponse)
|
||||
}
|
||||
do {
|
||||
try await timeline.sendPollResponse(pollStartId: pollStartID, answers: answers)
|
||||
|
||||
MXLog.info("Finished sending response for poll with eventID: \(pollStartID)")
|
||||
|
||||
return .success(())
|
||||
} catch {
|
||||
MXLog.error("Failed sending response for poll with eventID: \(pollStartID) with error: \(error)")
|
||||
return .failure(.sdkError(error))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -607,3 +583,20 @@ private extension MatrixRustSDK.PollKind {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Array where Element == TimelineItemProxy {
|
||||
func firstEventTimelineItemUsingID(_ id: TimelineItemIdentifier) -> EventTimelineItem? {
|
||||
var eventTimelineItemProxy: EventTimelineItemProxy?
|
||||
|
||||
for item in self {
|
||||
if case let .event(eventTimelineItem) = item {
|
||||
if eventTimelineItem.id == id {
|
||||
eventTimelineItemProxy = eventTimelineItem
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return eventTimelineItemProxy?.item
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,55 +18,41 @@ import Combine
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
enum TimelineProxyError: Error, Equatable {
|
||||
case failedEditingMessage
|
||||
case failedPaginatingBackwards
|
||||
case failedSendingMessage
|
||||
case failedSendingReaction
|
||||
case failedSendingReadReceipt
|
||||
case failedSendingMedia
|
||||
enum TimelineProxyError: Error {
|
||||
case sdkError(Error)
|
||||
|
||||
// Polls
|
||||
case failedCreatingPoll
|
||||
case failedEditingPoll
|
||||
case failedEndingPoll
|
||||
case failedSendingPollResponse
|
||||
}
|
||||
|
||||
enum TimelineProxyAction {
|
||||
case sentMessage
|
||||
case failedEditing
|
||||
case failedRedacting
|
||||
case failedPaginatingEndReached
|
||||
}
|
||||
|
||||
// sourcery: AutoMockable
|
||||
protocol TimelineProxyProtocol {
|
||||
var actions: AnyPublisher<TimelineProxyAction, Never> { get }
|
||||
|
||||
var isLive: Bool { get }
|
||||
|
||||
var timelineProvider: RoomTimelineProviderProtocol { get }
|
||||
|
||||
func subscribeForUpdates() async
|
||||
|
||||
/// Cancels a failed message given its transaction ID from the timeline
|
||||
func cancelSend(transactionID: String) async
|
||||
|
||||
func editMessage(_ message: String,
|
||||
html: String?,
|
||||
original eventID: String,
|
||||
intentionalMentions: IntentionalMentions) async -> Result<Void, TimelineProxyError>
|
||||
|
||||
func fetchDetails(for eventID: String)
|
||||
|
||||
func messageEventContent(for eventID: String) async -> RoomMessageEventContentWithoutRelation?
|
||||
func messageEventContent(for timelineItemID: TimelineItemIdentifier) async -> RoomMessageEventContentWithoutRelation?
|
||||
|
||||
func retryDecryption(for sessionID: String) async
|
||||
|
||||
/// Retries sending a failed message given its transaction ID
|
||||
func retrySend(transactionID: String) async
|
||||
|
||||
func paginateBackwards(requestSize: UInt16) async -> Result<Void, TimelineProxyError>
|
||||
func paginateForwards(requestSize: UInt16) async -> Result<Void, TimelineProxyError>
|
||||
|
||||
func edit(_ timelineItemID: TimelineItemIdentifier,
|
||||
message: String,
|
||||
html: String?,
|
||||
intentionalMentions: IntentionalMentions) async -> Result<Void, TimelineProxyError>
|
||||
|
||||
func redact(_ timelineItemID: TimelineItemIdentifier,
|
||||
reason: String?) async -> Result<Void, TimelineProxyError>
|
||||
|
||||
// MARK: - Sending
|
||||
|
||||
func sendAudio(url: URL,
|
||||
audioInfo: AudioInfo,
|
||||
progressSubject: CurrentValueSubject<Double, Never>?,
|
||||
|
||||
@@ -68,7 +68,7 @@ class UserSessionStore: UserSessionStoreProtocol {
|
||||
|
||||
func userSession(for client: Client, passphrase: String?) async -> Result<UserSessionProtocol, UserSessionStoreError> {
|
||||
do {
|
||||
let session = try await client.session()
|
||||
let session = try client.session()
|
||||
let userID = try client.userId()
|
||||
let clientProxy = await setupProxyForClient(client)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user