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
This commit is contained in:
Guillim
2025-06-24 13:51:03 +02:00
committed by GitHub
parent 3cee2b796f
commit 5c3550a2ee
6 changed files with 56 additions and 23 deletions

View File

@ -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',
}

View File

@ -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);
}

View File

@ -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',
}

View File

@ -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;
}

View File

@ -45,7 +45,7 @@ export class MessageImportExceptionHandlerService {
>,
workspaceId: string,
): Promise<void> {
if (exception instanceof MessageImportDriverException) {
if ('code' in exception) {
switch (exception.code) {
case MessageImportDriverExceptionCode.NOT_FOUND:
await this.handleNotFoundException(

View File

@ -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}`,