diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/google-calendar/utils/parse-gaxios-error.util.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/google-calendar/utils/parse-gaxios-error.util.ts index 33f9a5a09..a1fa42dbb 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/google-calendar/utils/parse-gaxios-error.util.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/google-calendar/utils/parse-gaxios-error.util.ts @@ -4,6 +4,7 @@ import { CalendarEventImportDriverException, CalendarEventImportDriverExceptionCode, } from 'src/modules/calendar/calendar-event-import-manager/drivers/exceptions/calendar-event-import-driver.exception'; +import { MessageNetworkExceptionCode } from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-network.exception'; export const parseGaxiosError = ( error: GaxiosError, @@ -11,11 +12,11 @@ export const parseGaxiosError = ( const { code } = error; switch (code) { - case 'ECONNRESET': - case 'ENOTFOUND': - case 'ECONNABORTED': - case 'ETIMEDOUT': - case 'ERR_NETWORK': + case MessageNetworkExceptionCode.ECONNRESET: + case MessageNetworkExceptionCode.ENOTFOUND: + case MessageNetworkExceptionCode.ECONNABORTED: + case MessageNetworkExceptionCode.ETIMEDOUT: + case MessageNetworkExceptionCode.ERR_NETWORK: return new CalendarEventImportDriverException( error.message, CalendarEventImportDriverExceptionCode.TEMPORARY_ERROR, diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/exceptions/message-network.exception.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/exceptions/message-network.exception.ts new file mode 100644 index 000000000..2a2ee92f6 --- /dev/null +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/exceptions/message-network.exception.ts @@ -0,0 +1,7 @@ +export enum MessageNetworkExceptionCode { + ECONNRESET = 'ECONNRESET', + ENOTFOUND = 'ENOTFOUND', + ECONNABORTED = 'ECONNABORTED', + ETIMEDOUT = 'ETIMEDOUT', + ERR_NETWORK = 'ERR_NETWORK', +} diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gaxios-error.util.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gaxios-error.util.ts index 7adbc0b2c..2050d65c7 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gaxios-error.util.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gaxios-error.util.ts @@ -4,6 +4,7 @@ import { MessageImportDriverException, MessageImportDriverExceptionCode, } from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception'; +import { MessageNetworkExceptionCode } from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-network.exception'; export const parseGaxiosError = ( error: GaxiosError, @@ -11,11 +12,11 @@ export const parseGaxiosError = ( const { code } = error; switch (code) { - case 'ECONNRESET': - case 'ENOTFOUND': - case 'ECONNABORTED': - case 'ETIMEDOUT': - case 'ERR_NETWORK': + case MessageNetworkExceptionCode.ECONNRESET: + case MessageNetworkExceptionCode.ENOTFOUND: + case MessageNetworkExceptionCode.ECONNABORTED: + case MessageNetworkExceptionCode.ETIMEDOUT: + case MessageNetworkExceptionCode.ERR_NETWORK: return new MessageImportDriverException( error.message, MessageImportDriverExceptionCode.TEMPORARY_ERROR, diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-fetch-by-batch.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-fetch-by-batch.service.ts index 79cc8d23a..5ff3c8854 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-fetch-by-batch.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-fetch-by-batch.service.ts @@ -1,16 +1,16 @@ import { Injectable, Logger } from '@nestjs/common'; import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; -import { MicrosoftImportDriverException } from 'src/modules/messaging/message-import-manager/drivers/microsoft/exceptions/microsoft-import-driver.exception'; import { MicrosoftClientProvider } from 'src/modules/messaging/message-import-manager/drivers/microsoft/providers/microsoft-client.provider'; import { MicrosoftGraphBatchResponse } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.interface'; -import { isMicrosoftClientTemporaryError } from 'src/modules/messaging/message-import-manager/drivers/microsoft/utils/is-temporary-error.utils'; +import { MicrosoftHandleErrorService } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-handle-error.service'; @Injectable() export class MicrosoftFetchByBatchService { private readonly logger = new Logger(MicrosoftFetchByBatchService.name); constructor( private readonly microsoftClientProvider: MicrosoftClientProvider, + private readonly microsoftHandleErrorService: MicrosoftHandleErrorService, ) {} async fetchAllByBatches( @@ -52,25 +52,9 @@ export class MicrosoftFetchByBatchService { batchResponses.push(batchResponse); } catch (error) { - if ( - error.body && - typeof error.body === 'string' && - isMicrosoftClientTemporaryError(error.body) - ) { - // TODO: remove this log once we catch better the error codes - this.logger.error( - `Error temporary (${error.code}) fetching messages for account ${connectedAccount.id.slice(0, 8)}`, - ); - this.logger.log(error); - throw new MicrosoftImportDriverException(error.body, error.code, 429); - } else { - // TODO: remove this log once we catch better the error codes - this.logger.error( - `Error unknown (${error.code}) fetching messages for account ${connectedAccount.id.slice(0, 8)}`, - ); - this.logger.log(error); - throw error; - } + this.microsoftHandleErrorService.handleMicrosoftMessageFetchByBatchError( + error, + ); } } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-message-list.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-message-list.service.ts index ba74f0e05..cc76d2d31 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-message-list.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-message-list.service.ts @@ -77,7 +77,12 @@ export class MicrosoftGetMessageListService { .headers({ Prefer: `odata.maxpagesize=${MESSAGING_MICROSOFT_USERS_MESSAGES_LIST_MAX_RESULT}, IdType="ImmutableId"`, }) - .get(); + .get() + .catch((error) => { + this.microsoftHandleErrorService.handleMicrosoftGetMessageListError( + error, + ); + }); const callback: PageIteratorCallback = (data) => { messageExternalIds.push(data.id); @@ -92,7 +97,9 @@ export class MicrosoftGetMessageListService { }); await pageIterator.iterate().catch((error) => { - this.microsoftHandleErrorService.handleMicrosoftMessageFetchError(error); + this.microsoftHandleErrorService.handleMicrosoftGetMessageListError( + error, + ); }); return { @@ -195,7 +202,12 @@ export class MicrosoftGetMessageListService { .headers({ Prefer: `odata.maxpagesize=${MESSAGING_MICROSOFT_USERS_MESSAGES_LIST_MAX_RESULT}, IdType="ImmutableId"`, }) - .get(); + .get() + .catch((error) => { + this.microsoftHandleErrorService.handleMicrosoftGetMessageListError( + error, + ); + }); const callback: PageIteratorCallback = (data) => { if (data['@removed']) { @@ -214,7 +226,9 @@ export class MicrosoftGetMessageListService { }); await pageIterator.iterate().catch((error) => { - this.microsoftHandleErrorService.handleMicrosoftMessageFetchError(error); + this.microsoftHandleErrorService.handleMicrosoftGetMessageListError( + error, + ); }); return { diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.ts index 7e598f93c..536e92937 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.ts @@ -4,6 +4,7 @@ import { isDefined } from 'twenty-shared/utils'; import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { computeMessageDirection } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/compute-message-direction.util'; +import { MicrosoftImportDriverException } from 'src/modules/messaging/message-import-manager/drivers/microsoft/exceptions/microsoft-import-driver.exception'; import { MicrosoftGraphBatchResponse } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.interface'; import { MessageWithParticipants } from 'src/modules/messaging/message-import-manager/types/message'; import { formatAddressObjectAsParticipants } from 'src/modules/messaging/message-import-manager/utils/format-address-object-as-participants.util'; @@ -43,7 +44,7 @@ export class MicrosoftGetMessagesService { return messages; } catch (error) { - this.microsoftHandleErrorService.handleMicrosoftMessageFetchError(error); + this.microsoftHandleErrorService.handleMicrosoftGetMessagesError(error); return []; } @@ -69,8 +70,10 @@ export class MicrosoftGetMessagesService { const messages = parsedResponses.map((response) => { if ('error' in response) { - this.microsoftHandleErrorService.throwMicrosoftBatchError( - response.error, + throw new MicrosoftImportDriverException( + response.error.message, + response.error.code, + response.error.statusCode, ); } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-handle-error.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-handle-error.service.ts index 811fa9bf8..26bfcf8c1 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-handle-error.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-handle-error.service.ts @@ -1,15 +1,33 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, Logger } from '@nestjs/common'; import { MessageImportDriverException, MessageImportDriverExceptionCode, } from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception'; -import { MicrosoftImportDriverException } from 'src/modules/messaging/message-import-manager/drivers/microsoft/exceptions/microsoft-import-driver.exception'; +import { isMicrosoftClientTemporaryError } from 'src/modules/messaging/message-import-manager/drivers/microsoft/utils/is-temporary-error.utils'; +import { parseMicrosoftMessagesImportError } from 'src/modules/messaging/message-import-manager/drivers/microsoft/utils/parse-microsoft-messages-import.util'; @Injectable() export class MicrosoftHandleErrorService { + private readonly logger = new Logger(MicrosoftHandleErrorService.name); + // eslint-disable-next-line @typescript-eslint/no-explicit-any - public handleMicrosoftMessageFetchError(error: any): void { + public handleMicrosoftMessageFetchByBatchError(error: any): void { + // TODO: remove this log once we catch better the error codes + this.logger.error(`Error temporary (${error.code}) fetching messages`); + this.logger.log(error); + + const isBodyString = error.body && typeof error.body === 'string'; + const isTemporaryError = + isBodyString && isMicrosoftClientTemporaryError(error.body); + + if (isTemporaryError) { + throw new MessageImportDriverException( + `code: ${error.code} - body: ${error.body}`, + MessageImportDriverExceptionCode.TEMPORARY_ERROR, + ); + } + if (!error.statusCode) { throw new MessageImportDriverException( `Microsoft Graph API unknown error: ${error}`, @@ -17,25 +35,10 @@ export class MicrosoftHandleErrorService { ); } - if (error.statusCode === 401) { - throw new MessageImportDriverException( - 'Unauthorized access to Microsoft Graph API', - MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS, - ); - } + const exception = parseMicrosoftMessagesImportError(error); - if (error.statusCode === 403) { - throw new MessageImportDriverException( - 'Forbidden access to Microsoft Graph API', - MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS, - ); - } - - if (error.statusCode === 429) { - throw new MessageImportDriverException( - `Microsoft Graph API ${error.code} ${error.statusCode} error: ${error.message}`, - MessageImportDriverExceptionCode.TEMPORARY_ERROR, - ); + if (exception) { + throw exception; } throw new MessageImportDriverException( @@ -45,11 +48,44 @@ export class MicrosoftHandleErrorService { } // eslint-disable-next-line @typescript-eslint/no-explicit-any - public throwMicrosoftBatchError(error: any): void { - throw new MicrosoftImportDriverException( - error.message, - error.code, - error.statusCode, + public handleMicrosoftGetMessageListError(error: any): void { + if (!error.statusCode) { + throw new MessageImportDriverException( + `Microsoft Graph API unknown error: ${error}`, + MessageImportDriverExceptionCode.UNKNOWN, + ); + } + + const exception = parseMicrosoftMessagesImportError(error); + + if (exception) { + throw exception; + } + + throw new MessageImportDriverException( + `Microsoft driver error: ${error.message}`, + MessageImportDriverExceptionCode.UNKNOWN, + ); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public handleMicrosoftGetMessagesError(error: any): void { + if (!error.statusCode) { + throw new MessageImportDriverException( + `Microsoft Graph API unknown error: ${error}`, + MessageImportDriverExceptionCode.UNKNOWN, + ); + } + + const exception = parseMicrosoftMessagesImportError(error); + + if (exception) { + throw exception; + } + + throw new MessageImportDriverException( + `Microsoft driver error: ${error.message}`, + MessageImportDriverExceptionCode.UNKNOWN, ); } } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/utils/parse-microsoft-messages-import.util.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/utils/parse-microsoft-messages-import.util.ts new file mode 100644 index 000000000..37d2c3797 --- /dev/null +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/utils/parse-microsoft-messages-import.util.ts @@ -0,0 +1,51 @@ +import { + MessageImportDriverException, + MessageImportDriverExceptionCode, +} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception'; + +export const parseMicrosoftMessagesImportError = (error: { + statusCode: number; + message?: string; + code?: string; +}): MessageImportDriverException | undefined => { + if (error.statusCode === 401) { + return new MessageImportDriverException( + 'Unauthorized access to Microsoft Graph API', + MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS, + ); + } + + if (error.statusCode === 403) { + return new MessageImportDriverException( + 'Forbidden access to Microsoft Graph API', + MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS, + ); + } + + if (error.statusCode === 404) { + if ( + error.message?.includes( + 'The mailbox is either inactive, soft-deleted, or is hosted on-premise.', + ) + ) { + return new MessageImportDriverException( + `Disabled, deleted, inactive or no licence Microsoft account - code:${error.code}`, + MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS, + ); + } else { + return new MessageImportDriverException( + `Not found - code:${error.code}`, + MessageImportDriverExceptionCode.NOT_FOUND, + ); + } + } + + if (error.statusCode === 429) { + return new MessageImportDriverException( + `Microsoft Graph API ${error.code} ${error.statusCode} error: ${error.message}`, + MessageImportDriverExceptionCode.TEMPORARY_ERROR, + ); + } + + return undefined; +}; 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 d7a831fc3..09fb65fa2 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 @@ -12,6 +12,7 @@ import { MessageImportDriverException, MessageImportDriverExceptionCode, } from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception'; +import { MessageNetworkExceptionCode } from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-network.exception'; import { MessageImportException, MessageImportExceptionCode, @@ -51,6 +52,11 @@ export class MessageImportExceptionHandlerService { ); break; case MessageImportDriverExceptionCode.TEMPORARY_ERROR: + case MessageNetworkExceptionCode.ECONNABORTED: + case MessageNetworkExceptionCode.ENOTFOUND: + case MessageNetworkExceptionCode.ECONNRESET: + case MessageNetworkExceptionCode.ETIMEDOUT: + case MessageNetworkExceptionCode.ERR_NETWORK: await this.handleTemporaryException( syncStep, messageChannel,