I believe that some emails with invalid characters are breaking the sync process. this PR attempts to create a "safeParseAddress" function. Hopefully this will change current behavior of a single email breaking the entire sync process to the sync process "skipping" an invalid email address and continuing on. I opened this because of issues explained in #12336 --------- Co-authored-by: guillim <guigloo@msn.com>
This commit is contained in:
@ -40,11 +40,22 @@ export const parseAndFormatGmailMessage = (
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toParticipants = to ?? deliveredTo;
|
||||||
|
|
||||||
const participants = [
|
const participants = [
|
||||||
...formatAddressObjectAsParticipants(from, 'from'),
|
...(from
|
||||||
...formatAddressObjectAsParticipants(to ?? deliveredTo, 'to'),
|
? formatAddressObjectAsParticipants([{ address: from }], 'from')
|
||||||
...formatAddressObjectAsParticipants(cc, 'cc'),
|
: []),
|
||||||
...formatAddressObjectAsParticipants(bcc, 'bcc'),
|
...(toParticipants
|
||||||
|
? formatAddressObjectAsParticipants(
|
||||||
|
[{ address: toParticipants, name: '' }],
|
||||||
|
'to',
|
||||||
|
)
|
||||||
|
: []),
|
||||||
|
...(cc ? formatAddressObjectAsParticipants([{ address: cc }], 'cc') : []),
|
||||||
|
...(bcc
|
||||||
|
? formatAddressObjectAsParticipants([{ address: bcc }], 'bcc')
|
||||||
|
: []),
|
||||||
];
|
];
|
||||||
|
|
||||||
const textWithoutReplyQuotations = text
|
const textWithoutReplyQuotations = text
|
||||||
@ -57,7 +68,7 @@ export const parseAndFormatGmailMessage = (
|
|||||||
subject: subject || '',
|
subject: subject || '',
|
||||||
messageThreadExternalId: threadId,
|
messageThreadExternalId: threadId,
|
||||||
receivedAt: new Date(parseInt(internalDate)),
|
receivedAt: new Date(parseInt(internalDate)),
|
||||||
direction: computeMessageDirection(from[0].address || '', connectedAccount),
|
direction: computeMessageDirection(from || '', connectedAccount),
|
||||||
participants,
|
participants,
|
||||||
text: sanitizeString(textWithoutReplyQuotations),
|
text: sanitizeString(textWithoutReplyQuotations),
|
||||||
attachments,
|
attachments,
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
|
|
||||||
import addressparser from 'addressparser';
|
|
||||||
import { gmail_v1 } from 'googleapis';
|
import { gmail_v1 } from 'googleapis';
|
||||||
|
|
||||||
import { getAttachmentData } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/get-attachment-data.util';
|
import { getAttachmentData } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/get-attachment-data.util';
|
||||||
import { getBodyData } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/get-body-data.util';
|
import { getBodyData } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/get-body-data.util';
|
||||||
import { getPropertyFromHeaders } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/get-property-from-headers.util';
|
import { getPropertyFromHeaders } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/get-property-from-headers.util';
|
||||||
|
import { safeParseEmailAddressAddress } from 'src/modules/messaging/message-import-manager/utils/safe-parse.util';
|
||||||
|
|
||||||
export const parseGmailMessage = (message: gmail_v1.Schema$Message) => {
|
export const parseGmailMessage = (message: gmail_v1.Schema$Message) => {
|
||||||
const subject = getPropertyFromHeaders(message, 'Subject');
|
const subject = getPropertyFromHeaders(message, 'Subject');
|
||||||
@ -36,11 +36,13 @@ export const parseGmailMessage = (message: gmail_v1.Schema$Message) => {
|
|||||||
historyId,
|
historyId,
|
||||||
internalDate,
|
internalDate,
|
||||||
subject,
|
subject,
|
||||||
from: rawFrom ? addressparser(rawFrom) : undefined,
|
from: rawFrom ? safeParseEmailAddressAddress(rawFrom) : undefined,
|
||||||
deliveredTo: rawDeliveredTo ? addressparser(rawDeliveredTo) : undefined,
|
deliveredTo: rawDeliveredTo
|
||||||
to: rawTo ? addressparser(rawTo) : undefined,
|
? safeParseEmailAddressAddress(rawDeliveredTo)
|
||||||
cc: rawCc ? addressparser(rawCc) : undefined,
|
: undefined,
|
||||||
bcc: rawBcc ? addressparser(rawBcc) : undefined,
|
to: rawTo ? safeParseEmailAddressAddress(rawTo) : undefined,
|
||||||
|
cc: rawCc ? safeParseEmailAddressAddress(rawCc) : undefined,
|
||||||
|
bcc: rawBcc ? safeParseEmailAddressAddress(rawBcc) : undefined,
|
||||||
text,
|
text,
|
||||||
attachments,
|
attachments,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { EmailAddress } from 'addressparser';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||||
@ -9,6 +10,7 @@ import { MicrosoftImportDriverException } from 'src/modules/messaging/message-im
|
|||||||
import { MicrosoftGraphBatchResponse } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.interface';
|
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 { 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';
|
import { formatAddressObjectAsParticipants } from 'src/modules/messaging/message-import-manager/utils/format-address-object-as-participants.util';
|
||||||
|
import { safeParseEmailAddress } from 'src/modules/messaging/message-import-manager/utils/safe-parse.util';
|
||||||
|
|
||||||
import { MicrosoftFetchByBatchService } from './microsoft-fetch-by-batch.service';
|
import { MicrosoftFetchByBatchService } from './microsoft-fetch-by-batch.service';
|
||||||
import { MicrosoftHandleErrorService } from './microsoft-handle-error.service';
|
import { MicrosoftHandleErrorService } from './microsoft-handle-error.service';
|
||||||
@ -78,37 +80,42 @@ export class MicrosoftGetMessagesService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const participants = [
|
const safeParseFrom = response?.from?.emailAddress
|
||||||
...formatAddressObjectAsParticipants(
|
? [safeParseEmailAddress(response.from.emailAddress)]
|
||||||
response?.from?.emailAddress,
|
: [];
|
||||||
'from',
|
|
||||||
),
|
|
||||||
...formatAddressObjectAsParticipants(
|
|
||||||
response?.toRecipients
|
|
||||||
?.filter(isDefined)
|
|
||||||
// @ts-expect-error legacy noImplicitAny
|
|
||||||
.map((recipient) => recipient.emailAddress),
|
|
||||||
'to',
|
|
||||||
),
|
|
||||||
...formatAddressObjectAsParticipants(
|
|
||||||
response?.ccRecipients
|
|
||||||
?.filter(isDefined)
|
|
||||||
// @ts-expect-error legacy noImplicitAny
|
|
||||||
.map((recipient) => recipient.emailAddress),
|
|
||||||
'cc',
|
|
||||||
),
|
|
||||||
...formatAddressObjectAsParticipants(
|
|
||||||
response?.bccRecipients
|
|
||||||
?.filter(isDefined)
|
|
||||||
// @ts-expect-error legacy noImplicitAny
|
|
||||||
.map((recipient) => recipient.emailAddress),
|
|
||||||
'bcc',
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
const safeParticipantsFormat = participants.filter((participant) => {
|
const safeParseTo = response?.toRecipients
|
||||||
return participant.handle.includes('@');
|
?.filter(isDefined)
|
||||||
});
|
.map((recipient: { emailAddress: EmailAddress }) =>
|
||||||
|
safeParseEmailAddress(recipient.emailAddress),
|
||||||
|
);
|
||||||
|
|
||||||
|
const safeParseCc = response?.ccRecipients
|
||||||
|
?.filter(isDefined)
|
||||||
|
.map((recipient: { emailAddress: EmailAddress }) =>
|
||||||
|
safeParseEmailAddress(recipient.emailAddress),
|
||||||
|
);
|
||||||
|
|
||||||
|
const safeParseBcc = response?.bccRecipients
|
||||||
|
?.filter(isDefined)
|
||||||
|
.map((recipient: { emailAddress: EmailAddress }) =>
|
||||||
|
safeParseEmailAddress(recipient.emailAddress),
|
||||||
|
);
|
||||||
|
|
||||||
|
const participants = [
|
||||||
|
...(safeParseFrom
|
||||||
|
? formatAddressObjectAsParticipants(safeParseFrom, 'from')
|
||||||
|
: []),
|
||||||
|
...(safeParseTo
|
||||||
|
? formatAddressObjectAsParticipants(safeParseTo, 'to')
|
||||||
|
: []),
|
||||||
|
...(safeParseCc
|
||||||
|
? formatAddressObjectAsParticipants(safeParseCc, 'cc')
|
||||||
|
: []),
|
||||||
|
...(safeParseBcc
|
||||||
|
? formatAddressObjectAsParticipants(safeParseBcc, 'bcc')
|
||||||
|
: []),
|
||||||
|
];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
externalId: response.id,
|
externalId: response.id,
|
||||||
@ -124,7 +131,7 @@ export class MicrosoftGetMessagesService {
|
|||||||
connectedAccount,
|
connectedAccount,
|
||||||
)
|
)
|
||||||
: MessageDirection.INCOMING,
|
: MessageDirection.INCOMING,
|
||||||
participants: safeParticipantsFormat,
|
participants,
|
||||||
attachments: [],
|
attachments: [],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@ -0,0 +1,4 @@
|
|||||||
|
export type EmailAddress = {
|
||||||
|
address: string;
|
||||||
|
name?: string;
|
||||||
|
};
|
||||||
@ -23,11 +23,42 @@ describe('formatAddressObjectAsParticipants', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return an empty array if address object is undefined', () => {
|
it('should return an empty array if address object handle has no @', () => {
|
||||||
const addressObject = undefined;
|
const addressObject = {
|
||||||
|
name: 'John Doe',
|
||||||
|
address: 'john.doe',
|
||||||
|
};
|
||||||
|
|
||||||
const result = formatAddressObjectAsParticipants(addressObject, 'to');
|
const result = formatAddressObjectAsParticipants([addressObject], 'to');
|
||||||
|
|
||||||
expect(result).toEqual([]);
|
expect(result).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return an empty array if address object handle is empty', () => {
|
||||||
|
const addressObject = {
|
||||||
|
name: 'John Doe',
|
||||||
|
address: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = formatAddressObjectAsParticipants([addressObject], 'to');
|
||||||
|
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a lowewrcase handle if the handle is not lowercase', () => {
|
||||||
|
const addressObject = {
|
||||||
|
name: 'John Doe',
|
||||||
|
address: 'John.Doe@example.com',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = formatAddressObjectAsParticipants([addressObject], 'to');
|
||||||
|
|
||||||
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
role: 'to',
|
||||||
|
handle: 'john.doe@example.com',
|
||||||
|
displayName: 'John Doe',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,36 +1,33 @@
|
|||||||
import addressparser from 'addressparser';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
import { Participant } from 'src/modules/messaging/message-import-manager/drivers/gmail/types/gmail-message.type';
|
import { Participant } from 'src/modules/messaging/message-import-manager/drivers/gmail/types/gmail-message.type';
|
||||||
|
import { EmailAddress } from 'src/modules/messaging/message-import-manager/types/email-address';
|
||||||
const formatAddressObjectAsArray = (
|
|
||||||
addressObject: addressparser.EmailAddress | addressparser.EmailAddress[],
|
|
||||||
): addressparser.EmailAddress[] => {
|
|
||||||
return Array.isArray(addressObject) ? addressObject : [addressObject];
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeSpacesAndLowerCase = (email: string): string => {
|
const removeSpacesAndLowerCase = (email: string): string => {
|
||||||
return email.replace(/\s/g, '').toLowerCase();
|
return email.replace(/\s/g, '').toLowerCase();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formatAddressObjectAsParticipants = (
|
export const formatAddressObjectAsParticipants = (
|
||||||
addressObject:
|
addressObjects: EmailAddress[],
|
||||||
| addressparser.EmailAddress
|
|
||||||
| addressparser.EmailAddress[]
|
|
||||||
| undefined,
|
|
||||||
role: 'from' | 'to' | 'cc' | 'bcc',
|
role: 'from' | 'to' | 'cc' | 'bcc',
|
||||||
): Participant[] => {
|
): Participant[] => {
|
||||||
if (!addressObject) return [];
|
|
||||||
const addressObjects = formatAddressObjectAsArray(addressObject);
|
|
||||||
|
|
||||||
const participants = addressObjects.map((addressObject) => {
|
const participants = addressObjects.map((addressObject) => {
|
||||||
const address = addressObject.address;
|
const address = addressObject.address;
|
||||||
|
|
||||||
|
if (!isDefined(address)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!address.includes('@')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
role,
|
role,
|
||||||
handle: address ? removeSpacesAndLowerCase(address) : '',
|
handle: removeSpacesAndLowerCase(address),
|
||||||
displayName: addressObject.name || '',
|
displayName: addressObject.name || '',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return participants.flat();
|
return participants.filter(isDefined) as Participant[];
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,28 @@
|
|||||||
|
import { Logger } from '@nestjs/common';
|
||||||
|
|
||||||
|
import addressparser from 'addressparser';
|
||||||
|
|
||||||
|
import { EmailAddress } from 'src/modules/messaging/message-import-manager/types/email-address';
|
||||||
|
|
||||||
|
export const safeParseEmailAddressAddress = (
|
||||||
|
address: string,
|
||||||
|
): string | undefined => {
|
||||||
|
const logger = new Logger(safeParseEmailAddressAddress.name);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return addressparser(address)[0].address;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Error parsing address: ${address}`, error);
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const safeParseEmailAddress = (
|
||||||
|
emailAddress: EmailAddress,
|
||||||
|
): EmailAddress => {
|
||||||
|
return {
|
||||||
|
address: safeParseEmailAddressAddress(emailAddress.address) || '',
|
||||||
|
name: emailAddress.name,
|
||||||
|
};
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user