From 5c3550a2eeb60c97f838e246e9b85b25db05e3ea Mon Sep 17 00:00:00 2001 From: Guillim Date: Tue, 24 Jun 2025 13:51:03 +0200 Subject: [PATCH] in connected account, refresh-token can fail with network error (#12815) This PR fixes this issue from the connected account refresh token service that is This PR fixes error handling in `handleDriverException` by ensuring that errors resembling `MessageImportDriverException` are correctly detected, even if they are plain objects and not true class instances. This prevents missed exception handling due to failed `instanceof` checks. Was introduced by [this PR](https://github.com/twentyhq/twenty/pull/12233) that did not know all provider cases that can occur. Fixes https://github.com/twentyhq/twenty/issues/12589 --- ...nected-account-refresh-tokens.exception.ts | 1 + ...onnected-account-refresh-tokens.service.ts | 21 +++++++++++++-- .../message-import-driver.exception.ts | 1 + .../jobs/messaging-message-list-fetch.job.ts | 27 ++++++++++++------- ...saging-import-exception-handler.service.ts | 2 +- .../messaging-messages-import.service.ts | 27 ++++++++++++------- 6 files changed, 56 insertions(+), 23 deletions(-) diff --git a/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/exceptions/connected-account-refresh-tokens.exception.ts b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/exceptions/connected-account-refresh-tokens.exception.ts index 67ca06c7e..dc1244c40 100644 --- a/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/exceptions/connected-account-refresh-tokens.exception.ts +++ b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/exceptions/connected-account-refresh-tokens.exception.ts @@ -13,4 +13,5 @@ export enum ConnectedAccountRefreshAccessTokenExceptionCode { REFRESH_TOKEN_NOT_FOUND = 'REFRESH_TOKEN_NOT_FOUND', REFRESH_ACCESS_TOKEN_FAILED = 'REFRESH_ACCESS_TOKEN_FAILED', PROVIDER_NOT_SUPPORTED = 'PROVIDER_NOT_SUPPORTED', + TEMPORARY_NETWORK_ERROR = 'TEMPORARY_NETWORK_ERROR', } diff --git a/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.ts b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.ts index 57d4f5e9f..d4c6e57d5 100644 --- a/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.ts +++ b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.ts @@ -93,9 +93,26 @@ export class ConnectedAccountRefreshTokensService { } } catch (error) { if (error?.name === 'AggregateError') { - this.logger.log(error.message); - this.logger.log(error.name); + const firstErrorCode = error?.errors?.[0]?.code; + const networkErrorCodes = [ + 'ENETUNREACH', + 'ETIMEDOUT', + 'ECONNABORTED', + 'ERR_NETWORK', + ]; + const isTemporaryNetworkError = + networkErrorCodes.includes(firstErrorCode); + + this.logger.log(error?.message); + this.logger.log(firstErrorCode); this.logger.log(error?.errors); + + if (isTemporaryNetworkError) { + throw new ConnectedAccountRefreshAccessTokenException( + `Error refreshing tokens for connected account ${connectedAccount.id.slice(0, 7)} in workspace ${workspaceId.slice(0, 7)}: ${firstErrorCode}`, + ConnectedAccountRefreshAccessTokenExceptionCode.TEMPORARY_NETWORK_ERROR, + ); + } } else { this.logger.log(error); } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception.ts index 839771174..d8ae8a258 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception.ts @@ -14,4 +14,5 @@ export enum MessageImportDriverExceptionCode { UNKNOWN_NETWORK_ERROR = 'UNKNOWN_NETWORK_ERROR', NO_NEXT_SYNC_CURSOR = 'NO_NEXT_SYNC_CURSOR', SYNC_CURSOR_ERROR = 'SYNC_CURSOR_ERROR', + PROVIDER_NOT_SUPPORTED = 'PROVIDER_NOT_SUPPORTED', } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job.ts index 767acfb78..42810ca0a 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job.ts @@ -11,8 +11,10 @@ import { MessageChannelSyncStage, MessageChannelWorkspaceEntity, } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; -import { MessageImportDriverExceptionCode } from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception'; -import { MessageImportExceptionCode } from 'src/modules/messaging/message-import-manager/exceptions/message-import.exception'; +import { + MessageImportDriverException, + MessageImportDriverExceptionCode, +} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception'; import { MessagingFullMessageListFetchService } from 'src/modules/messaging/message-import-manager/services/messaging-full-message-list-fetch.service'; import { MessageImportExceptionHandlerService, @@ -90,6 +92,11 @@ export class MessagingMessageListFetchJob { ); } catch (error) { switch (error.code) { + case ConnectedAccountRefreshAccessTokenExceptionCode.TEMPORARY_NETWORK_ERROR: + throw new MessageImportDriverException( + error.message, + MessageImportDriverExceptionCode.TEMPORARY_ERROR, + ); case ConnectedAccountRefreshAccessTokenExceptionCode.REFRESH_ACCESS_TOKEN_FAILED: case ConnectedAccountRefreshAccessTokenExceptionCode.REFRESH_TOKEN_NOT_FOUND: await this.messagingMonitoringService.track({ @@ -99,15 +106,15 @@ export class MessagingMessageListFetchJob { messageChannelId: messageChannel.id, message: `${error.code}: ${error.reason ?? ''}`, }); - throw { - code: MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS, - message: error.message, - }; + throw new MessageImportDriverException( + error.message, + MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS, + ); case ConnectedAccountRefreshAccessTokenExceptionCode.PROVIDER_NOT_SUPPORTED: - throw { - code: MessageImportExceptionCode.PROVIDER_NOT_SUPPORTED, - message: error.message, - }; + throw new MessageImportDriverException( + error.message, + MessageImportDriverExceptionCode.PROVIDER_NOT_SUPPORTED, + ); default: throw error; } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service.ts index 266508715..ef30db441 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service.ts @@ -45,7 +45,7 @@ export class MessageImportExceptionHandlerService { >, workspaceId: string, ): Promise { - if (exception instanceof MessageImportDriverException) { + if ('code' in exception) { switch (exception.code) { case MessageImportDriverExceptionCode.NOT_FOUND: await this.handleNotFoundException( diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-messages-import.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-messages-import.service.ts index d66eba14f..2103facef 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-messages-import.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-messages-import.service.ts @@ -16,9 +16,11 @@ import { MessageChannelSyncStage, MessageChannelWorkspaceEntity, } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; -import { MessageImportDriverExceptionCode } from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception'; +import { + MessageImportDriverException, + MessageImportDriverExceptionCode, +} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception'; import { MESSAGING_GMAIL_USERS_MESSAGES_GET_BATCH_SIZE } from 'src/modules/messaging/message-import-manager/drivers/gmail/constants/messaging-gmail-users-messages-get-batch-size.constant'; -import { MessageImportExceptionCode } from 'src/modules/messaging/message-import-manager/exceptions/message-import.exception'; import { MessagingGetMessagesService } from 'src/modules/messaging/message-import-manager/services/messaging-get-messages.service'; import { MessageImportExceptionHandlerService, @@ -80,6 +82,11 @@ export class MessagingMessagesImportService { ); } catch (error) { switch (error.code) { + case ConnectedAccountRefreshAccessTokenExceptionCode.TEMPORARY_NETWORK_ERROR: + throw new MessageImportDriverException( + error.message, + MessageImportDriverExceptionCode.TEMPORARY_ERROR, + ); case ConnectedAccountRefreshAccessTokenExceptionCode.REFRESH_ACCESS_TOKEN_FAILED: case ConnectedAccountRefreshAccessTokenExceptionCode.REFRESH_TOKEN_NOT_FOUND: await this.messagingMonitoringService.track({ @@ -89,15 +96,15 @@ export class MessagingMessagesImportService { messageChannelId: messageChannel.id, message: `${error.code}: ${error.reason}`, }); - throw { - code: MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS, - message: error.message, - }; + throw new MessageImportDriverException( + error.message, + MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS, + ); case ConnectedAccountRefreshAccessTokenExceptionCode.PROVIDER_NOT_SUPPORTED: - throw { - code: MessageImportExceptionCode.PROVIDER_NOT_SUPPORTED, - message: error.message, - }; + throw new MessageImportDriverException( + error.message, + MessageImportDriverExceptionCode.PROVIDER_NOT_SUPPORTED, + ); default: this.logger.error( `Error (${error.code}) refreshing access token for account ${connectedAccount.id}`,