From 48a8707558638995cf46256b22684d8e068e6ba5 Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Thu, 21 Nov 2024 18:17:54 -0300 Subject: [PATCH 1/8] Do not throw an error when an invalid custom field is found on visitor to contact migration --- .../livechat/server/lib/contacts/createContact.ts | 2 +- .../server/lib/contacts/validateCustomFields.ts | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/meteor/app/livechat/server/lib/contacts/createContact.ts b/apps/meteor/app/livechat/server/lib/contacts/createContact.ts index efdcacdbf4ad..cd6eb493bed2 100644 --- a/apps/meteor/app/livechat/server/lib/contacts/createContact.ts +++ b/apps/meteor/app/livechat/server/lib/contacts/createContact.ts @@ -33,7 +33,7 @@ export async function createContact({ } const allowedCustomFields = await getAllowedCustomFields(); - const customFields = validateCustomFields(allowedCustomFields, receivedCustomFields); + const customFields = validateCustomFields(allowedCustomFields, receivedCustomFields, { onlyFilterFields: true }); return LivechatContacts.insertContact({ name, diff --git a/apps/meteor/app/livechat/server/lib/contacts/validateCustomFields.ts b/apps/meteor/app/livechat/server/lib/contacts/validateCustomFields.ts index e389d1b34ac9..3580fdcb98c4 100644 --- a/apps/meteor/app/livechat/server/lib/contacts/validateCustomFields.ts +++ b/apps/meteor/app/livechat/server/lib/contacts/validateCustomFields.ts @@ -6,13 +6,13 @@ import { i18n } from '../../../../utils/lib/i18n'; export function validateCustomFields( allowedCustomFields: AtLeast[], customFields: Record, - options?: { ignoreAdditionalFields?: boolean }, + { ignoreAdditionalFields = false, onlyFilterFields = false }: { ignoreAdditionalFields?: boolean; onlyFilterFields?: boolean } = {}, ): Record { const validValues: Record = {}; for (const cf of allowedCustomFields) { if (!customFields.hasOwnProperty(cf._id)) { - if (cf.required) { + if (cf.required && !onlyFilterFields) { throw new Error(i18n.t('error-invalid-custom-field-value', { field: cf.label })); } continue; @@ -20,7 +20,7 @@ export function validateCustomFields( const cfValue: string = trim(customFields[cf._id]); if (!cfValue || typeof cfValue !== 'string') { - if (cf.required) { + if (cf.required && !onlyFilterFields) { throw new Error(i18n.t('error-invalid-custom-field-value', { field: cf.label })); } continue; @@ -29,6 +29,10 @@ export function validateCustomFields( if (cf.regexp) { const regex = new RegExp(cf.regexp); if (!regex.test(cfValue)) { + if (onlyFilterFields) { + continue; + } + throw new Error(i18n.t('error-invalid-custom-field-value', { field: cf.label })); } } @@ -36,7 +40,7 @@ export function validateCustomFields( validValues[cf._id] = cfValue; } - if (!options?.ignoreAdditionalFields) { + if (!ignoreAdditionalFields) { const allowedCustomFieldIds = new Set(allowedCustomFields.map((cf) => cf._id)); for (const key in customFields) { if (!allowedCustomFieldIds.has(key)) { From 0fd01adbbf6a2fadd933211539d14b6f37ca045f Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Thu, 21 Nov 2024 18:18:07 -0300 Subject: [PATCH 2/8] Add unit tests --- .../lib/contacts/validateCustomFields.spec.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/apps/meteor/app/livechat/server/lib/contacts/validateCustomFields.spec.ts b/apps/meteor/app/livechat/server/lib/contacts/validateCustomFields.spec.ts index 0dcd478176db..82b967cd35f6 100644 --- a/apps/meteor/app/livechat/server/lib/contacts/validateCustomFields.spec.ts +++ b/apps/meteor/app/livechat/server/lib/contacts/validateCustomFields.spec.ts @@ -14,6 +14,12 @@ describe('validateCustomFields', () => { expect(() => validateCustomFields(mockCustomFields, {})).to.throw(); }); + it('should not throw an error if a required custom field is missing, but the onlyFilterFields option is provided', () => { + expect(() => validateCustomFields(mockCustomFields, {}, { onlyFilterFields: true })) + .not.to.throw() + .and.to.equal({}); + }); + it('should NOT throw an error when a non-required custom field is missing', () => { const allowedCustomFields = [{ _id: 'field1', label: 'Field 1', required: false }]; const customFields = {}; @@ -25,6 +31,12 @@ describe('validateCustomFields', () => { expect(() => validateCustomFields(mockCustomFields, { cf1: 'invalid' })).to.throw(); }); + it('should not throw an error if a custom field value does not match the regexp, but the onlyFilterFields option is provided', () => { + expect(() => validateCustomFields(mockCustomFields, { cf1: 'invalid' }, { onlyFilterFields: true })) + .not.to.throw() + .and.to.equal({}); + }); + it('should handle an empty customFields input without throwing an error', () => { const allowedCustomFields = [{ _id: 'field1', label: 'Field 1', required: false }]; const customFields = {}; @@ -38,4 +50,13 @@ describe('validateCustomFields', () => { expect(() => validateCustomFields(allowedCustomFields, customFields)).to.throw(); }); + + it('should not throw an error if a extra custom field is passed, but the onlyFilterFields option is provided', () => { + const allowedCustomFields = [{ _id: 'field1', label: 'Field 1', required: false }]; + const customFields = { field2: 'value' }; + + expect(() => validateCustomFields(allowedCustomFields, customFields, { onlyFilterFields: true })) + .not.to.throw() + .and.to.equal({}); + }); }); From 917720cb115768550b58074c58de1e6a84bfd411 Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Fri, 22 Nov 2024 16:17:19 -0300 Subject: [PATCH 3/8] validate custom fields on mapVisitorToContact --- .../server/lib/contacts/createContact.ts | 2 +- .../lib/contacts/mapVisitorToContact.spec.ts | 3 +++ .../lib/contacts/mapVisitorToContact.ts | 5 ++++- .../lib/contacts/validateCustomFields.spec.ts | 22 ------------------- 4 files changed, 8 insertions(+), 24 deletions(-) diff --git a/apps/meteor/app/livechat/server/lib/contacts/createContact.ts b/apps/meteor/app/livechat/server/lib/contacts/createContact.ts index cd6eb493bed2..efdcacdbf4ad 100644 --- a/apps/meteor/app/livechat/server/lib/contacts/createContact.ts +++ b/apps/meteor/app/livechat/server/lib/contacts/createContact.ts @@ -33,7 +33,7 @@ export async function createContact({ } const allowedCustomFields = await getAllowedCustomFields(); - const customFields = validateCustomFields(allowedCustomFields, receivedCustomFields, { onlyFilterFields: true }); + const customFields = validateCustomFields(allowedCustomFields, receivedCustomFields); return LivechatContacts.insertContact({ name, diff --git a/apps/meteor/app/livechat/server/lib/contacts/mapVisitorToContact.spec.ts b/apps/meteor/app/livechat/server/lib/contacts/mapVisitorToContact.spec.ts index 65f26edfb83c..81196e4b6421 100644 --- a/apps/meteor/app/livechat/server/lib/contacts/mapVisitorToContact.spec.ts +++ b/apps/meteor/app/livechat/server/lib/contacts/mapVisitorToContact.spec.ts @@ -6,11 +6,13 @@ import sinon from 'sinon'; import type { CreateContactParams } from './createContact'; const getContactManagerIdByUsername = sinon.stub(); +const getAllowedCustomFields = sinon.stub(); const { mapVisitorToContact } = proxyquire.noCallThru().load('./mapVisitorToContact', { './getContactManagerIdByUsername': { getContactManagerIdByUsername, }, + './getAllowedCustomFields': { getAllowedCustomFields }, }); const testDate = new Date(); @@ -142,6 +144,7 @@ describe('mapVisitorToContact', () => { return undefined; }); + getAllowedCustomFields.returns([]); }); const index = 0; diff --git a/apps/meteor/app/livechat/server/lib/contacts/mapVisitorToContact.ts b/apps/meteor/app/livechat/server/lib/contacts/mapVisitorToContact.ts index ad1ac994ce0f..961382171ac6 100644 --- a/apps/meteor/app/livechat/server/lib/contacts/mapVisitorToContact.ts +++ b/apps/meteor/app/livechat/server/lib/contacts/mapVisitorToContact.ts @@ -1,7 +1,9 @@ import type { ILivechatVisitor, IOmnichannelSource } from '@rocket.chat/core-typings'; import type { CreateContactParams } from './createContact'; +import { getAllowedCustomFields } from './getAllowedCustomFields'; import { getContactManagerIdByUsername } from './getContactManagerIdByUsername'; +import { validateCustomFields } from './validateCustomFields'; export async function mapVisitorToContact(visitor: ILivechatVisitor, source: IOmnichannelSource): Promise { return { @@ -24,7 +26,8 @@ export async function mapVisitorToContact(visitor: ILivechatVisitor, source: IOm details: source, }, ], - customFields: visitor.livechatData, + customFields: + visitor.livechatData && validateCustomFields(await getAllowedCustomFields(), visitor.livechatData, { onlyFilterFields: true }), lastChat: visitor.lastChat, contactManager: visitor.contactManager?.username && (await getContactManagerIdByUsername(visitor.contactManager.username)), }; diff --git a/apps/meteor/app/livechat/server/lib/contacts/validateCustomFields.spec.ts b/apps/meteor/app/livechat/server/lib/contacts/validateCustomFields.spec.ts index 82b967cd35f6..960067755b18 100644 --- a/apps/meteor/app/livechat/server/lib/contacts/validateCustomFields.spec.ts +++ b/apps/meteor/app/livechat/server/lib/contacts/validateCustomFields.spec.ts @@ -20,23 +20,10 @@ describe('validateCustomFields', () => { .and.to.equal({}); }); - it('should NOT throw an error when a non-required custom field is missing', () => { - const allowedCustomFields = [{ _id: 'field1', label: 'Field 1', required: false }]; - const customFields = {}; - - expect(() => validateCustomFields(allowedCustomFields, customFields)).not.to.throw(); - }); - it('should throw an error if a custom field value does not match the regexp', () => { expect(() => validateCustomFields(mockCustomFields, { cf1: 'invalid' })).to.throw(); }); - it('should not throw an error if a custom field value does not match the regexp, but the onlyFilterFields option is provided', () => { - expect(() => validateCustomFields(mockCustomFields, { cf1: 'invalid' }, { onlyFilterFields: true })) - .not.to.throw() - .and.to.equal({}); - }); - it('should handle an empty customFields input without throwing an error', () => { const allowedCustomFields = [{ _id: 'field1', label: 'Field 1', required: false }]; const customFields = {}; @@ -50,13 +37,4 @@ describe('validateCustomFields', () => { expect(() => validateCustomFields(allowedCustomFields, customFields)).to.throw(); }); - - it('should not throw an error if a extra custom field is passed, but the onlyFilterFields option is provided', () => { - const allowedCustomFields = [{ _id: 'field1', label: 'Field 1', required: false }]; - const customFields = { field2: 'value' }; - - expect(() => validateCustomFields(allowedCustomFields, customFields, { onlyFilterFields: true })) - .not.to.throw() - .and.to.equal({}); - }); }); From 0117f352f9bc4b4c685aff318aff4446d24977d8 Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Fri, 22 Nov 2024 16:53:12 -0300 Subject: [PATCH 4/8] re-add validateCustomFields tests --- .../lib/contacts/validateCustomFields.spec.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/apps/meteor/app/livechat/server/lib/contacts/validateCustomFields.spec.ts b/apps/meteor/app/livechat/server/lib/contacts/validateCustomFields.spec.ts index 960067755b18..82b967cd35f6 100644 --- a/apps/meteor/app/livechat/server/lib/contacts/validateCustomFields.spec.ts +++ b/apps/meteor/app/livechat/server/lib/contacts/validateCustomFields.spec.ts @@ -20,10 +20,23 @@ describe('validateCustomFields', () => { .and.to.equal({}); }); + it('should NOT throw an error when a non-required custom field is missing', () => { + const allowedCustomFields = [{ _id: 'field1', label: 'Field 1', required: false }]; + const customFields = {}; + + expect(() => validateCustomFields(allowedCustomFields, customFields)).not.to.throw(); + }); + it('should throw an error if a custom field value does not match the regexp', () => { expect(() => validateCustomFields(mockCustomFields, { cf1: 'invalid' })).to.throw(); }); + it('should not throw an error if a custom field value does not match the regexp, but the onlyFilterFields option is provided', () => { + expect(() => validateCustomFields(mockCustomFields, { cf1: 'invalid' }, { onlyFilterFields: true })) + .not.to.throw() + .and.to.equal({}); + }); + it('should handle an empty customFields input without throwing an error', () => { const allowedCustomFields = [{ _id: 'field1', label: 'Field 1', required: false }]; const customFields = {}; @@ -37,4 +50,13 @@ describe('validateCustomFields', () => { expect(() => validateCustomFields(allowedCustomFields, customFields)).to.throw(); }); + + it('should not throw an error if a extra custom field is passed, but the onlyFilterFields option is provided', () => { + const allowedCustomFields = [{ _id: 'field1', label: 'Field 1', required: false }]; + const customFields = { field2: 'value' }; + + expect(() => validateCustomFields(allowedCustomFields, customFields, { onlyFilterFields: true })) + .not.to.throw() + .and.to.equal({}); + }); }); From c77c8b6e703c27a1768089e9650f42a115292e26 Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Fri, 22 Nov 2024 16:59:09 -0300 Subject: [PATCH 5/8] add unit tests to mapVisitorToContact --- .../lib/contacts/mapVisitorToContact.spec.ts | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/apps/meteor/app/livechat/server/lib/contacts/mapVisitorToContact.spec.ts b/apps/meteor/app/livechat/server/lib/contacts/mapVisitorToContact.spec.ts index 81196e4b6421..a2cfbebdd49f 100644 --- a/apps/meteor/app/livechat/server/lib/contacts/mapVisitorToContact.spec.ts +++ b/apps/meteor/app/livechat/server/lib/contacts/mapVisitorToContact.spec.ts @@ -132,6 +132,54 @@ const dataMap: [Partial, IOmnichannelSource, CreateContactPara contactManager: undefined, }, ], + + [ + { + _id: 'visitor1', + username: 'Username', + lastChat: { + _id: 'last-chat-id', + ts: testDate, + }, + livechatData: { + customFieldId: 'customFieldValue', + invalidCustomFieldId: 'invalidCustomFieldValue', + }, + }, + { + type: OmnichannelSourceType.WIDGET, + }, + { + name: 'Username', + emails: undefined, + phones: undefined, + unknown: false, + channels: [ + { + name: 'sms', + visitor: { + visitorId: 'visitor1', + source: { + type: OmnichannelSourceType.WIDGET, + }, + }, + blocked: false, + verified: false, + details: { + type: OmnichannelSourceType.WIDGET, + }, + }, + ], + customFields: { + customFieldId: 'customFieldValue', + }, + lastChat: { + _id: 'last-chat-id', + ts: testDate, + }, + contactManager: undefined, + }, + ], ]; describe('mapVisitorToContact', () => { @@ -144,7 +192,7 @@ describe('mapVisitorToContact', () => { return undefined; }); - getAllowedCustomFields.returns([]); + getAllowedCustomFields.resolves([{ _id: 'customFieldId', label: 'custom-field-label' }]); }); const index = 0; From 57c1f0b3e46fb6549fdedf92718703afad9ad6ec Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Fri, 22 Nov 2024 18:24:30 -0300 Subject: [PATCH 6/8] improve nerw property name --- .../server/lib/contacts/mapVisitorToContact.ts | 2 +- .../server/lib/contacts/validateCustomFields.spec.ts | 12 ++++++------ .../server/lib/contacts/validateCustomFields.ts | 11 +++++++---- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/apps/meteor/app/livechat/server/lib/contacts/mapVisitorToContact.ts b/apps/meteor/app/livechat/server/lib/contacts/mapVisitorToContact.ts index 961382171ac6..6d2903649a39 100644 --- a/apps/meteor/app/livechat/server/lib/contacts/mapVisitorToContact.ts +++ b/apps/meteor/app/livechat/server/lib/contacts/mapVisitorToContact.ts @@ -27,7 +27,7 @@ export async function mapVisitorToContact(visitor: ILivechatVisitor, source: IOm }, ], customFields: - visitor.livechatData && validateCustomFields(await getAllowedCustomFields(), visitor.livechatData, { onlyFilterFields: true }), + visitor.livechatData && validateCustomFields(await getAllowedCustomFields(), visitor.livechatData, { ignoreValidationErrors: true }), lastChat: visitor.lastChat, contactManager: visitor.contactManager?.username && (await getContactManagerIdByUsername(visitor.contactManager.username)), }; diff --git a/apps/meteor/app/livechat/server/lib/contacts/validateCustomFields.spec.ts b/apps/meteor/app/livechat/server/lib/contacts/validateCustomFields.spec.ts index 82b967cd35f6..39684a62fd91 100644 --- a/apps/meteor/app/livechat/server/lib/contacts/validateCustomFields.spec.ts +++ b/apps/meteor/app/livechat/server/lib/contacts/validateCustomFields.spec.ts @@ -14,8 +14,8 @@ describe('validateCustomFields', () => { expect(() => validateCustomFields(mockCustomFields, {})).to.throw(); }); - it('should not throw an error if a required custom field is missing, but the onlyFilterFields option is provided', () => { - expect(() => validateCustomFields(mockCustomFields, {}, { onlyFilterFields: true })) + it('should not throw an error if a required custom field is missing, but the ignoreValidationErrors option is provided', () => { + expect(() => validateCustomFields(mockCustomFields, {}, { ignoreValidationErrors: true })) .not.to.throw() .and.to.equal({}); }); @@ -31,8 +31,8 @@ describe('validateCustomFields', () => { expect(() => validateCustomFields(mockCustomFields, { cf1: 'invalid' })).to.throw(); }); - it('should not throw an error if a custom field value does not match the regexp, but the onlyFilterFields option is provided', () => { - expect(() => validateCustomFields(mockCustomFields, { cf1: 'invalid' }, { onlyFilterFields: true })) + it('should not throw an error if a custom field value does not match the regexp, but the ignoreValidationErrors option is provided', () => { + expect(() => validateCustomFields(mockCustomFields, { cf1: 'invalid' }, { ignoreValidationErrors: true })) .not.to.throw() .and.to.equal({}); }); @@ -51,11 +51,11 @@ describe('validateCustomFields', () => { expect(() => validateCustomFields(allowedCustomFields, customFields)).to.throw(); }); - it('should not throw an error if a extra custom field is passed, but the onlyFilterFields option is provided', () => { + it('should not throw an error if a extra custom field is passed, but the ignoreValidationErrors option is provided', () => { const allowedCustomFields = [{ _id: 'field1', label: 'Field 1', required: false }]; const customFields = { field2: 'value' }; - expect(() => validateCustomFields(allowedCustomFields, customFields, { onlyFilterFields: true })) + expect(() => validateCustomFields(allowedCustomFields, customFields, { ignoreValidationErrors: true })) .not.to.throw() .and.to.equal({}); }); diff --git a/apps/meteor/app/livechat/server/lib/contacts/validateCustomFields.ts b/apps/meteor/app/livechat/server/lib/contacts/validateCustomFields.ts index 3580fdcb98c4..4efede65b266 100644 --- a/apps/meteor/app/livechat/server/lib/contacts/validateCustomFields.ts +++ b/apps/meteor/app/livechat/server/lib/contacts/validateCustomFields.ts @@ -6,13 +6,16 @@ import { i18n } from '../../../../utils/lib/i18n'; export function validateCustomFields( allowedCustomFields: AtLeast[], customFields: Record, - { ignoreAdditionalFields = false, onlyFilterFields = false }: { ignoreAdditionalFields?: boolean; onlyFilterFields?: boolean } = {}, + { + ignoreAdditionalFields = false, + ignoreValidationErrors = false, + }: { ignoreAdditionalFields?: boolean; ignoreValidationErrors?: boolean } = {}, ): Record { const validValues: Record = {}; for (const cf of allowedCustomFields) { if (!customFields.hasOwnProperty(cf._id)) { - if (cf.required && !onlyFilterFields) { + if (cf.required && !ignoreValidationErrors) { throw new Error(i18n.t('error-invalid-custom-field-value', { field: cf.label })); } continue; @@ -20,7 +23,7 @@ export function validateCustomFields( const cfValue: string = trim(customFields[cf._id]); if (!cfValue || typeof cfValue !== 'string') { - if (cf.required && !onlyFilterFields) { + if (cf.required && !ignoreValidationErrors) { throw new Error(i18n.t('error-invalid-custom-field-value', { field: cf.label })); } continue; @@ -29,7 +32,7 @@ export function validateCustomFields( if (cf.regexp) { const regex = new RegExp(cf.regexp); if (!regex.test(cfValue)) { - if (onlyFilterFields) { + if (ignoreValidationErrors) { continue; } From c6cf199eb2281aeee72cc2d0ad1c152cf5362bf0 Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Mon, 25 Nov 2024 19:08:05 -0300 Subject: [PATCH 7/8] also ignore additional fields error on visitor migration to contact --- .../app/livechat/server/lib/contacts/mapVisitorToContact.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/meteor/app/livechat/server/lib/contacts/mapVisitorToContact.ts b/apps/meteor/app/livechat/server/lib/contacts/mapVisitorToContact.ts index 6d2903649a39..1afc48b682b9 100644 --- a/apps/meteor/app/livechat/server/lib/contacts/mapVisitorToContact.ts +++ b/apps/meteor/app/livechat/server/lib/contacts/mapVisitorToContact.ts @@ -27,7 +27,11 @@ export async function mapVisitorToContact(visitor: ILivechatVisitor, source: IOm }, ], customFields: - visitor.livechatData && validateCustomFields(await getAllowedCustomFields(), visitor.livechatData, { ignoreValidationErrors: true }), + visitor.livechatData && + validateCustomFields(await getAllowedCustomFields(), visitor.livechatData, { + ignoreAdditionalFields: true, + ignoreValidationErrors: true, + }), lastChat: visitor.lastChat, contactManager: visitor.contactManager?.username && (await getContactManagerIdByUsername(visitor.contactManager.username)), }; From e4f94c039b69406176714f496700553302f01f67 Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Mon, 25 Nov 2024 19:09:39 -0300 Subject: [PATCH 8/8] do not create conflicts on contact merge --- .../meteor/app/livechat/server/lib/contacts/ContactMerger.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/apps/meteor/app/livechat/server/lib/contacts/ContactMerger.ts b/apps/meteor/app/livechat/server/lib/contacts/ContactMerger.ts index a4bd703f1473..48b6dcc41f9a 100644 --- a/apps/meteor/app/livechat/server/lib/contacts/ContactMerger.ts +++ b/apps/meteor/app/livechat/server/lib/contacts/ContactMerger.ts @@ -262,8 +262,6 @@ export class ContactMerger { customFieldsPerName.get(customField.type)?.push(customField); } - const customFieldConflicts: CustomFieldAndValue[] = []; - for (const [key, customFields] of customFieldsPerName) { const fieldName = key.replace('customFields.', ''); @@ -274,8 +272,6 @@ export class ContactMerger { dataToSet[key] = first.value; } } - - customFieldConflicts.push(...customFields); } const allConflicts: ILivechatContactConflictingField[] = @@ -284,7 +280,6 @@ export class ContactMerger { : [ ...newNames.map((name): ILivechatContactConflictingField => ({ field: 'name', value: name })), ...newManagers.map((manager): ILivechatContactConflictingField => ({ field: 'manager', value: manager as string })), - ...customFieldConflicts.map(({ type, value }): ILivechatContactConflictingField => ({ field: type, value })), ]; // Phones, Emails and Channels are simply added to the contact's existing list