|
5 | 5 | import CoreData
|
6 | 6 | import Foundation
|
7 | 7 |
|
8 |
| -public class DefaultMessageInterceptor: SendMessageInterceptor { |
9 |
| - public let client: ChatClient |
10 |
| - private let database: DatabaseContainer |
11 |
| - private let apiClient: APIClient |
12 |
| - |
13 |
| - public init(client: ChatClient) { |
14 |
| - self.client = client |
15 |
| - database = client.databaseContainer |
16 |
| - apiClient = client.apiClient |
17 |
| - } |
18 |
| - |
19 |
| - public func sendMessage( |
20 |
| - _ message: ChatMessage, |
21 |
| - options: SendMessageOptions, |
22 |
| - completion: @escaping (Result<SendMessageResponse, Error>) -> Void |
23 |
| - ) { |
24 |
| - database.backgroundReadOnlyContext.perform { [weak self] in |
25 |
| - guard let messageDTO = self?.database.backgroundReadOnlyContext.message(id: message.id), |
26 |
| - let messageCid = messageDTO.cid, |
27 |
| - let cid = try? ChannelId(cid: messageCid) |
28 |
| - else { |
29 |
| - completion(.failure(MessageRepositoryError.messageDoesNotExist)) |
30 |
| - return |
31 |
| - } |
32 |
| - |
33 |
| - let requestBody = messageDTO.asRequestBody() as MessageRequestBody |
34 |
| - let endpoint: Endpoint<MessagePayload.Boxed> = .sendMessage( |
35 |
| - cid: cid, |
36 |
| - messagePayload: requestBody, |
37 |
| - skipPush: options.skipPush, |
38 |
| - skipEnrichUrl: options.skipEnrichUrl |
39 |
| - ) |
40 |
| - self?.apiClient.request(endpoint: endpoint) { result in |
41 |
| - self?.handleSentMessage(result, cid: cid, messageId: message.id) { result in |
42 |
| - let newResult = result |
43 |
| - .map { |
44 |
| - SendMessageResponse(message: $0) |
45 |
| - }.mapError { |
46 |
| - $0 as Error |
47 |
| - } |
48 |
| - completion(newResult) |
49 |
| - } |
50 |
| - } |
51 |
| - } |
52 |
| - } |
53 |
| - |
54 |
| - func saveSuccessfullySentMessage( |
55 |
| - cid: ChannelId, |
56 |
| - message: MessagePayload, |
57 |
| - completion: @escaping (Result<ChatMessage, Error>) -> Void |
58 |
| - ) { |
59 |
| - var messageModel: ChatMessage! |
60 |
| - database.write({ |
61 |
| - let messageDTO = try $0.saveMessage( |
62 |
| - payload: message, |
63 |
| - for: cid, |
64 |
| - syncOwnReactions: false, |
65 |
| - skipDraftUpdate: false, |
66 |
| - cache: nil |
67 |
| - ) |
68 |
| - if messageDTO.localMessageState == .sending || messageDTO.localMessageState == .sendingFailed { |
69 |
| - messageDTO.markMessageAsSent() |
70 |
| - } |
71 |
| - messageModel = try messageDTO.asModel() |
72 |
| - }, completion: { |
73 |
| - if let error = $0 { |
74 |
| - log.error("Error saving sent message with id \(message.id): \(error)", subsystems: .offlineSupport) |
75 |
| - completion(.failure(error)) |
76 |
| - } else { |
77 |
| - completion(.success(messageModel)) |
78 |
| - } |
79 |
| - }) |
80 |
| - } |
81 |
| - |
82 |
| - /// Handles the result when sending the message to the server. |
83 |
| - private func handleSentMessage( |
84 |
| - _ result: Result<MessagePayload.Boxed, Error>, |
85 |
| - cid: ChannelId, |
86 |
| - messageId: MessageId, |
87 |
| - completion: @escaping (Result<ChatMessage, MessageRepositoryError>) -> Void |
88 |
| - ) { |
89 |
| - switch result { |
90 |
| - case let .success(payload): |
91 |
| - saveSuccessfullySentMessage(cid: cid, message: payload.message) { result in |
92 |
| - switch result { |
93 |
| - case let .success(message): |
94 |
| - completion(.success(message)) |
95 |
| - case let .failure(error): |
96 |
| - completion(.failure(.failedToSendMessage(error))) |
97 |
| - } |
98 |
| - } |
99 |
| - case let .failure(error): |
100 |
| - handleSendingMessageError( |
101 |
| - error, |
102 |
| - messageId: messageId, |
103 |
| - completion: completion |
104 |
| - ) |
105 |
| - } |
106 |
| - } |
107 |
| - |
108 |
| - /// Handles the result when the message is intercepted. |
109 |
| - private func handleInterceptedMessage( |
110 |
| - _ result: Result<SendMessageResponse, Error>, |
111 |
| - messageId: MessageId, |
112 |
| - completion: @escaping (Result<ChatMessage, MessageRepositoryError>) -> Void |
113 |
| - ) { |
114 |
| - switch result { |
115 |
| - case let .success(response): |
116 |
| - let message = response.message |
117 |
| - database.write { session in |
118 |
| - guard let messageDTO = session.message(id: message.id) else { return } |
119 |
| - if message.localState == nil { |
120 |
| - messageDTO.markMessageAsSent() |
121 |
| - } |
122 |
| - } |
123 |
| - completion(.success(message)) |
124 |
| - case let .failure(error): |
125 |
| - handleSendingMessageError( |
126 |
| - error, |
127 |
| - messageId: messageId, |
128 |
| - completion: completion |
129 |
| - ) |
130 |
| - } |
131 |
| - } |
132 |
| - |
133 |
| - private func handleSendingMessageError( |
134 |
| - _ error: Error, |
135 |
| - messageId: MessageId, |
136 |
| - completion: @escaping (Result<ChatMessage, MessageRepositoryError>) -> Void |
137 |
| - ) { |
138 |
| - log.error("Sending the message with id \(messageId) failed with error: \(error)") |
139 |
| - |
140 |
| - if let clientError = error as? ClientError, let errorPayload = clientError.errorPayload { |
141 |
| - // If the message already exists on the server we do not want to mark it as failed, |
142 |
| - // since this will cause an unrecoverable state, where the user will keep resending |
143 |
| - // the message and it will always fail. Right now, the only way to check this error is |
144 |
| - // by checking a combination of the error code and description, since there is no special |
145 |
| - // error code for duplicated messages. |
146 |
| - let isDuplicatedMessageError = errorPayload.code == 4 && errorPayload.message.contains("already exists") |
147 |
| - if isDuplicatedMessageError { |
148 |
| - database.write({ |
149 |
| - let messageDTO = $0.message(id: messageId) |
150 |
| - messageDTO?.markMessageAsSent() |
151 |
| - }, completion: { _ in |
152 |
| - completion(.failure(.failedToSendMessage(error))) |
153 |
| - }) |
154 |
| - return |
155 |
| - } |
156 |
| - } |
157 |
| - |
158 |
| - markMessageAsFailedToSend(id: messageId) { |
159 |
| - completion(.failure(.failedToSendMessage(error))) |
160 |
| - } |
161 |
| - } |
162 |
| - |
163 |
| - private func markMessageAsFailedToSend(id: MessageId, completion: @escaping () -> Void) { |
164 |
| - database.write({ |
165 |
| - let dto = $0.message(id: id) |
166 |
| - if dto?.localMessageState == .sending { |
167 |
| - dto?.markMessageAsFailed() |
168 |
| - } |
169 |
| - }, completion: { |
170 |
| - if let error = $0 { |
171 |
| - log.error( |
172 |
| - "Error changing localMessageState message with id \(id) to `sendingFailed`: \(error)", |
173 |
| - subsystems: .offlineSupport |
174 |
| - ) |
175 |
| - } |
176 |
| - completion() |
177 |
| - }) |
178 |
| - } |
179 |
| -} |
180 |
| - |
181 | 8 | enum MessageRepositoryError: Error {
|
182 | 9 | case messageDoesNotExist
|
183 | 10 | case messageNotPendingSend
|
|
0 commit comments