Skip to content

Commit dc4ff44

Browse files
authored
fix(conversation): add retry attempts for conversationNotFound Error [WPB-18227] (#19768)
1 parent 593c16d commit dc4ff44

File tree

2 files changed

+58
-18
lines changed

2 files changed

+58
-18
lines changed

src/script/repositories/conversation/ConversationRepository.ts

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ import {ClientMLSError, ClientMLSErrorLabel} from '@wireapp/core/lib/messagingPr
5757
import {amplify} from 'amplify';
5858
import {StatusCodes as HTTP_STATUS} from 'http-status-codes';
5959
import {container} from 'tsyringe';
60-
import {flatten} from 'underscore';
60+
import {flatten, isError} from 'underscore';
6161

6262
import {Account} from '@wireapp/core';
6363
import {Asset as ProtobufAsset, Confirmation, LegalHoldStatus} from '@wireapp/protocol-messaging';
@@ -611,23 +611,34 @@ export class ConversationRepository {
611611
await this.updateParticipatingUserEntities(conversationEntity);
612612
await this.saveConversation(conversationEntity);
613613

614-
fetching_conversations[conversationId].forEach(({resolveFn}) => resolveFn(conversationEntity));
614+
for (const {resolveFn} of fetching_conversations[conversationId]) {
615+
resolveFn(conversationEntity);
616+
}
615617
delete fetching_conversations[conversationId];
616618

617619
return conversationEntity;
618-
} catch (originalError) {
619-
if (originalError.code === HTTP_STATUS.NOT_FOUND) {
620-
this.deleteConversationLocally(qualifiedId, false);
621-
}
622-
const error = new ConversationError(
623-
ConversationError.TYPE.CONVERSATION_NOT_FOUND,
624-
ConversationError.MESSAGE.CONVERSATION_NOT_FOUND,
625-
originalError,
626-
);
627-
fetching_conversations[conversationId].forEach(({rejectFn}) => rejectFn(error));
628-
delete fetching_conversations[conversationId];
620+
} catch (originalError: unknown) {
621+
if (isError(originalError)) {
622+
const code =
623+
originalError && typeof originalError === 'object' && 'code' in originalError ? originalError.code : null;
624+
this.logger.error(originalError.message);
625+
if (code === HTTP_STATUS.NOT_FOUND) {
626+
await this.deleteConversationLocally(qualifiedId, false);
627+
}
628+
const error = new ConversationError(
629+
ConversationError.TYPE.CONVERSATION_NOT_FOUND,
630+
ConversationError.MESSAGE.CONVERSATION_NOT_FOUND,
631+
originalError,
632+
);
629633

630-
throw error;
634+
for (const {rejectFn} of fetching_conversations[conversationId]) {
635+
rejectFn(error);
636+
}
637+
638+
delete fetching_conversations[conversationId];
639+
throw error;
640+
}
641+
throw new Error('unkown error encountered', {cause: originalError});
631642
}
632643
}
633644

src/script/view_model/ContentViewModel.ts

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {QualifiedId} from '@wireapp/api-client/lib/user/';
2222
import {amplify} from 'amplify';
2323
import ko from 'knockout';
2424
import {container} from 'tsyringe';
25+
import {isError} from 'underscore';
2526

2627
import {WebAppEvents} from '@wireapp/webapp-events';
2728

@@ -218,8 +219,25 @@ export class ContentViewModel {
218219
);
219220
}
220221

221-
private isConversationNotFoundError(error: any): boolean {
222-
return error.type === ConversationError.TYPE.CONVERSATION_NOT_FOUND;
222+
private isConversationNotFoundError(error: unknown): boolean {
223+
return isError(error) && 'type' in error && error.type === ConversationError.TYPE.CONVERSATION_NOT_FOUND;
224+
}
225+
226+
private async retryFetchConversationWithBackoff(
227+
conversationId: QualifiedId,
228+
maxRetries: number = 3,
229+
initialDelayMs: number = 100,
230+
): Promise<boolean> {
231+
for (let attempt = 0; attempt < maxRetries; attempt++) {
232+
try {
233+
await new Promise(resolve => setTimeout(resolve, initialDelayMs * (attempt + 1)));
234+
await this.conversationRepository.fetchBackendConversationEntityById(conversationId);
235+
return true;
236+
} catch (error) {
237+
this.logger.warn(`Retry attempt ${attempt + 1}/${maxRetries} failed for conversation fetch`, error);
238+
}
239+
}
240+
return false;
223241
}
224242

225243
/**
@@ -264,7 +282,6 @@ export class ContentViewModel {
264282
}
265283

266284
const isOpenedConversation = this.isConversationOpen(conversationEntity, isActiveConversation);
267-
268285
this.handleConversationState(isOpenedConversation, openNotificationSettings, conversationEntity);
269286
if (!isActiveConversation) {
270287
this.conversationState.activeConversation(conversationEntity);
@@ -274,8 +291,20 @@ export class ContentViewModel {
274291
const messageEntity = openFirstSelfMention ? conversationEntity.getFirstUnreadSelfMention() : exposeMessageEntity;
275292
this.changeConversation(conversationEntity, messageEntity);
276293
this.showAndNavigate(conversationEntity, openNotificationSettings, filePath);
277-
} catch (error: any) {
294+
} catch (error: unknown) {
278295
if (this.isConversationNotFoundError(error)) {
296+
// Retry fetching the conversation to handle race conditions
297+
const fetchSucceeded = await this.retryFetchConversationWithBackoff({
298+
id: conversation.domain,
299+
domain: conversation.domain,
300+
});
301+
302+
if (fetchSucceeded) {
303+
// Conversation was found after retry, attempt to show it again
304+
return this.showConversation(conversation, options);
305+
}
306+
307+
// All retries failed, show the error modal
279308
return this.showConversationNotFoundErrorModal();
280309
}
281310

0 commit comments

Comments
 (0)