From 36ab596b4b3860f718ccf2c17ea7d89be72b78f4 Mon Sep 17 00:00:00 2001 From: Stephen Hand Date: Thu, 8 Jan 2026 12:41:46 +0000 Subject: [PATCH 1/9] Change call transfer to use custom transfer endpoint. Set up basic transfer to queue --- .../src/transfer/transferStart.ts | 30 ++++++++++++++++++- .../src/services/transferService.ts | 30 +++++++++++++++++++ plugin-hrm-form/src/states/DomainConstants.ts | 2 +- .../src/transfer/setUpTransferActions.tsx | 17 ++++++----- 4 files changed, 69 insertions(+), 10 deletions(-) create mode 100644 plugin-hrm-form/src/services/transferService.ts diff --git a/lambdas/account-scoped/src/transfer/transferStart.ts b/lambdas/account-scoped/src/transfer/transferStart.ts index f0321309da..51322775cf 100644 --- a/lambdas/account-scoped/src/transfer/transferStart.ts +++ b/lambdas/account-scoped/src/transfer/transferStart.ts @@ -26,6 +26,7 @@ import { isErr, newErr, newOk, Result } from '../Result'; import { WorkerInstance } from 'twilio/lib/rest/taskrouter/v1/workspace/worker'; import { getSsmParameter } from '@tech-matters/ssm-cache'; import { retrieveServiceConfigurationAttributes } from '../configuration/aseloConfiguration'; +import { TaskInstance } from 'twilio/lib/rest/taskrouter/v1/workspace/task'; export type Body = { taskSid: TaskSID; @@ -170,6 +171,28 @@ async function increaseChatCapacity( } } +export const transferCallToQueue = async ( + accountSid: AccountSID, + originalTask: TaskInstance, + newAttributes: any, +): Promise => { + const client = await getTwilioClient(accountSid); + const programmableChatTransferWorkflowSid = + await getConversationsTransferWorkflow(accountSid); + + // create New task + const newTask = await client.taskrouter.v1.workspaces + .get(await getWorkspaceSid(accountSid)) + .tasks.create({ + workflowSid: programmableChatTransferWorkflowSid, + taskChannel: originalTask.taskChannelUniqueName, + attributes: JSON.stringify(newAttributes), + priority: 100, + }); + + return newTask.sid as TaskSID; +}; + export const transferStartHandler: AccountScopedHandler = async ( { body: event }: HttpRequest, accountSid: AccountSID, @@ -241,8 +264,13 @@ export const transferStartHandler: AccountScopedHandler = async ( transferTargetType, }; + if (originalTask.taskChannelUniqueName === 'voice') { + const newTaskSid = transferCallToQueue(accountSid, originalTask, newAttributes); + return newOk({ taskSid: newTaskSid }); + } + /** - * Check if is transfering a conversation. + * Check if is transferring a conversation. * It might be better to accept an `isConversation` parameter. * But for now, we can check if a conversation exists given a conversationId. */ diff --git a/plugin-hrm-form/src/services/transferService.ts b/plugin-hrm-form/src/services/transferService.ts new file mode 100644 index 0000000000..7ca48fc873 --- /dev/null +++ b/plugin-hrm-form/src/services/transferService.ts @@ -0,0 +1,30 @@ +/** + * Copyright (C) 2021-2023 Technology Matters + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ + +import fetchProtectedApi from './fetchProtectedApi'; +import { TaskSID, WorkerSID } from '../types/twilio'; + +type TransferChatStartBody = { + taskSid: TaskSID; + targetSid: string; + ignoreAgent: WorkerSID; + mode: 'WARM' | 'COLD'; +}; + +type TransferChatStartReturn = { closed: string; kept: string }; + +export const transferStart = async (body: TransferChatStartBody): Promise => + fetchProtectedApi('transfer/transferStart', body, { useTwilioLambda: true }); diff --git a/plugin-hrm-form/src/states/DomainConstants.ts b/plugin-hrm-form/src/states/DomainConstants.ts index ed3325f916..e27caa7a4f 100644 --- a/plugin-hrm-form/src/states/DomainConstants.ts +++ b/plugin-hrm-form/src/states/DomainConstants.ts @@ -76,7 +76,7 @@ export type ChannelColors = { export const transferModes = { cold: 'COLD', warm: 'WARM', -}; +} as const; export const transferStatuses = { transferring: 'transferring', diff --git a/plugin-hrm-form/src/transfer/setUpTransferActions.tsx b/plugin-hrm-form/src/transfer/setUpTransferActions.tsx index f582e1b590..d2b56cf282 100644 --- a/plugin-hrm-form/src/transfer/setUpTransferActions.tsx +++ b/plugin-hrm-form/src/transfer/setUpTransferActions.tsx @@ -30,17 +30,20 @@ import { import * as TransferHelpers from './transferTaskState'; import { transferModes } from '../states/DomainConstants'; import { recordEvent } from '../fullStory'; -import { transferChatStart } from '../services/ServerlessService'; import { getHrmConfig } from '../hrmConfig'; import { RootState } from '../states'; import { reactivateAseloListeners } from '../conversationListeners'; import selectContactByTaskSid from '../states/contacts/selectContactByTaskSid'; import { ContactState } from '../states/contacts/existingContacts'; import { saveFormSharedState } from './formDataTransfer'; +import { transferStart } from '../services/transferService'; type SetupObject = ReturnType; type ActionPayload = { task: ITask }; -type ActionPayloadWithOptions = ActionPayload & { options: { mode: string }; targetSid: string }; +type ActionPayloadWithOptions = ActionPayload & { + options: { mode: typeof transferModes[keyof typeof transferModes] }; + targetSid: string; +}; const DEFAULT_TRANSFER_MODE = transferModes.cold; export const TransfersNotifications = { @@ -71,7 +74,7 @@ const getStateContactForms = (taskSid: string): ContactState => { }; /** - * Custom override for TransferTask action. Saves the form to share with another counselor (if possible) and then starts the transfer + * Custom override for TransferTask action. Saves the form to ensure it's up to date on the backend (if possible) and then starts the transfer */ const customTransferTask = (setupObject: SetupObject): ReplacedActionFunction => async ( payload: ActionPayloadWithOptions, @@ -110,9 +113,9 @@ const customTransferTask = (setupObject: SetupObject): ReplacedActionFunction => const { conferenceSid } = payload.task.conference || {}; const conferenceSidFromAttributes = payload.task.attributes?.conference?.sid; if (!conferenceSid && !conferenceSidFromAttributes) { - console.log('>> Could not find any conferenceSid'); + console.debug('>> Could not find any conferenceSid'); } else if (conferenceSid && !conferenceSidFromAttributes) { - console.log('>> Updating task attributes with conferenceSid'); + console.debug('>> Updating task attributes with conferenceSid'); // const customer = payload.task.conference?.participants.find(p => p.participantType === 'customer').participantSid; await payload.task.setAttributes({ ...payload.task.attributes, @@ -130,8 +133,6 @@ const customTransferTask = (setupObject: SetupObject): ReplacedActionFunction => if (disableTransfer) { window.alert(Manager.getInstance().strings['Transfer-CannotTransferTooManyParticipants']); - } else { - return safeTransfer(() => original(payload), payload.task); } } @@ -142,7 +143,7 @@ const customTransferTask = (setupObject: SetupObject): ReplacedActionFunction => ignoreAgent: workerSid, }; - return safeTransfer(() => transferChatStart(body), payload.task); + return safeTransfer(() => transferStart(body), payload.task); }; const afterCancelTransfer = (payload: ActionPayload) => TransferHelpers.clearTransferMeta(payload.task); From 07252f8c130744f73e36dcb98e8313ce081041ae Mon Sep 17 00:00:00 2001 From: Stephen Hand Date: Thu, 8 Jan 2026 12:48:15 +0000 Subject: [PATCH 2/9] Delete unused code --- .../src/services/ServerlessService.ts | 25 +------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/plugin-hrm-form/src/services/ServerlessService.ts b/plugin-hrm-form/src/services/ServerlessService.ts index c823298575..f5a59a65b3 100644 --- a/plugin-hrm-form/src/services/ServerlessService.ts +++ b/plugin-hrm-form/src/services/ServerlessService.ts @@ -20,36 +20,13 @@ /* eslint-disable sonarjs/prefer-immediate-return */ /* eslint-disable camelcase */ -import { ITask, Notifications } from '@twilio/flex-ui'; +import { ITask } from '@twilio/flex-ui'; import { DefinitionVersion, loadDefinition } from 'hrm-form-definitions'; import fetchProtectedApi from './fetchProtectedApi'; import type { ChildCSAMReportForm, CounselorCSAMReportForm } from '../states/csam-report/types'; import { getHrmConfig } from '../hrmConfig'; -type TransferChatStartBody = { - taskSid: string; - targetSid: string; - ignoreAgent: string; - mode: string; -}; - -type TrasferChatStartReturn = { closed: string; kept: string }; - -export const transferChatStart = async (body: TransferChatStartBody): Promise => { - try { - const result = await fetchProtectedApi('/transferChatStart', body); - return result; - } catch (err) { - Notifications.showNotification('TransferFailed', { - reason: `Worker ${body.targetSid} is not available.`, - }); - - // propagate the error - throw err; - } -}; - export const issueSyncToken = async (): Promise => { const res = await fetchProtectedApi('/issueSyncToken'); const syncToken = res.token; From 077850083334275752179de6987b4f1423cf8022 Mon Sep 17 00:00:00 2001 From: Stephen Hand Date: Thu, 8 Jan 2026 13:36:09 +0000 Subject: [PATCH 3/9] Add handler for queue voice transfers --- .../account-scoped/src/taskrouter/index.ts | 1 + .../transfer/transferTaskRouterListener.ts | 53 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 lambdas/account-scoped/src/transfer/transferTaskRouterListener.ts diff --git a/lambdas/account-scoped/src/taskrouter/index.ts b/lambdas/account-scoped/src/taskrouter/index.ts index b9aa40c8b1..74c6dd869a 100644 --- a/lambdas/account-scoped/src/taskrouter/index.ts +++ b/lambdas/account-scoped/src/taskrouter/index.ts @@ -21,6 +21,7 @@ import '../task/addCustomerExternalIdTaskRouterListener'; import '../task/addInitialHangUpByTaskRouterListener'; import '../conversation/addTaskSidToChannelAttributesTaskRouterListener'; import '../channelCapture/postSurveyListener'; +import '../transfer/transferTaskRouterListener'; export { handleTaskRouterEvent } from './taskrouterEventHandler'; diff --git a/lambdas/account-scoped/src/transfer/transferTaskRouterListener.ts b/lambdas/account-scoped/src/transfer/transferTaskRouterListener.ts new file mode 100644 index 0000000000..5c17252abc --- /dev/null +++ b/lambdas/account-scoped/src/transfer/transferTaskRouterListener.ts @@ -0,0 +1,53 @@ +/** + * Copyright (C) 2021-2025 Technology Matters + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ +import type { EventFields } from '../taskrouter'; +import { AccountSID } from '@tech-matters/twilio-types'; +import { Twilio } from 'twilio'; +import { registerTaskRouterEventHandler } from '../taskrouter/taskrouterEventHandler'; +import { TASK_QUEUE_ENTERED } from '../taskrouter/eventTypes'; +import { getWorkspaceSid } from '@tech-matters/twilio-configuration'; + +export const handleQueueTransferEvent = async ( + { + TaskAttributes: taskAttributesString, + TaskSid: taskSid, + TaskChannelUniqueName: taskChannelUniqueName, + }: EventFields, + accountSid: AccountSID, + client: Twilio, +): Promise => { + const taskAttributes = JSON.parse(taskAttributesString); + if ( + taskChannelUniqueName !== 'voice' || + taskAttributes?.transferMeta?.transferStatus !== 'accepted' + ) { + return; + } + const { originalTask: originalTaskSid } = taskAttributes.transferMeta; + console.info( + `Handling ${taskChannelUniqueName} transfer for target task ${taskSid} to queue from original task ${originalTaskSid} entering target queue...`, + ); + + await client.taskrouter.v1.workspaces + .get(await getWorkspaceSid(accountSid)) + .tasks.get(originalTaskSid) + .update({ + assignmentStatus: 'completed', + reason: 'task transferred into queue', + }); +}; + +registerTaskRouterEventHandler([TASK_QUEUE_ENTERED], handleQueueTransferEvent); From e80a38f67093f31f2c3fdbb7efc7e93d6148c5ea Mon Sep 17 00:00:00 2001 From: Stephen Hand Date: Fri, 9 Jan 2026 13:55:04 +0000 Subject: [PATCH 4/9] Logging --- lambdas/account-scoped/src/transfer/transferStart.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambdas/account-scoped/src/transfer/transferStart.ts b/lambdas/account-scoped/src/transfer/transferStart.ts index 51322775cf..de124b26a1 100644 --- a/lambdas/account-scoped/src/transfer/transferStart.ts +++ b/lambdas/account-scoped/src/transfer/transferStart.ts @@ -189,7 +189,7 @@ export const transferCallToQueue = async ( attributes: JSON.stringify(newAttributes), priority: 100, }); - + console.debug(`Transfer target task created with sid ${newTask.sid}`); return newTask.sid as TaskSID; }; From 75ff9357bcf2d2f70e4aeadec69101784dbbf6c9 Mon Sep 17 00:00:00 2001 From: Stephen Hand Date: Fri, 9 Jan 2026 15:11:44 +0000 Subject: [PATCH 5/9] Cold queue voice transfer logic POC --- .../src/transfer/transferStart.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/lambdas/account-scoped/src/transfer/transferStart.ts b/lambdas/account-scoped/src/transfer/transferStart.ts index de124b26a1..67bf8968e4 100644 --- a/lambdas/account-scoped/src/transfer/transferStart.ts +++ b/lambdas/account-scoped/src/transfer/transferStart.ts @@ -179,7 +179,22 @@ export const transferCallToQueue = async ( const client = await getTwilioClient(accountSid); const programmableChatTransferWorkflowSid = await getConversationsTransferWorkflow(accountSid); - + const { conference: taskConferenceInfo } = newAttributes; + const conferenceContext = client.conferences.get(taskConferenceInfo.sid); + const originalAgentCallSid = taskConferenceInfo.participants.worker; + const conferenceParticipants = await conferenceContext.participants.list(); + const originalAgentParticipant = conferenceParticipants.find( + p => p.callSid === originalAgentCallSid, + ); + if (originalAgentParticipant) { + await originalAgentParticipant.update({ endConferenceOnExit: false }); + } + await Promise.all( + conferenceParticipants + .filter(p => p.callSid !== originalAgentCallSid) + .map(p => p.update({ hold: true })), + ); + await originalAgentParticipant?.remove(); // create New task const newTask = await client.taskrouter.v1.workspaces .get(await getWorkspaceSid(accountSid)) From 82060fc73f47ada5454798527770392e5c223912 Mon Sep 17 00:00:00 2001 From: Stephen Hand Date: Mon, 12 Jan 2026 11:55:15 +0000 Subject: [PATCH 6/9] Lots of logging for cold queue call transfer --- .../src/transfer/transferStart.ts | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/lambdas/account-scoped/src/transfer/transferStart.ts b/lambdas/account-scoped/src/transfer/transferStart.ts index 67bf8968e4..9d43326559 100644 --- a/lambdas/account-scoped/src/transfer/transferStart.ts +++ b/lambdas/account-scoped/src/transfer/transferStart.ts @@ -171,16 +171,25 @@ async function increaseChatCapacity( } } -export const transferCallToQueue = async ( +export const coldTransferCallToQueue = async ( accountSid: AccountSID, originalTask: TaskInstance, newAttributes: any, ): Promise => { + console.debug( + `[transfer: original task: ${originalTask.sid}] Cold transferring call to queue`, + ); const client = await getTwilioClient(accountSid); const programmableChatTransferWorkflowSid = await getConversationsTransferWorkflow(accountSid); const { conference: taskConferenceInfo } = newAttributes; + console.debug( + `[transfer: original task: ${originalTask.sid}, conference: ${taskConferenceInfo.sid}] Found conference info`, + ); const conferenceContext = client.conferences.get(taskConferenceInfo.sid); + console.debug( + `[transfer: original task: ${originalTask.sid}, conference: ${taskConferenceInfo.sid}] Found conference instance`, + ); const originalAgentCallSid = taskConferenceInfo.participants.worker; const conferenceParticipants = await conferenceContext.participants.list(); const originalAgentParticipant = conferenceParticipants.find( @@ -188,11 +197,25 @@ export const transferCallToQueue = async ( ); if (originalAgentParticipant) { await originalAgentParticipant.update({ endConferenceOnExit: false }); + console.debug( + `[transfer: original task: ${originalTask.sid}, conference: ${taskConferenceInfo.sid}] Found original agent to remove from conference: ${originalAgentParticipant?.callSid}`, + ); + } else { + console.warn( + `[transfer: original task: ${originalTask.sid}, conference: ${taskConferenceInfo.sid}] Could not find original agent to remove from conference`, + conferenceParticipants, + ); } await Promise.all( conferenceParticipants .filter(p => p.callSid !== originalAgentCallSid) - .map(p => p.update({ hold: true })), + .map(p => { + console.debug( + `[transfer: original task: ${originalTask.sid}, conference: ${taskConferenceInfo.sid}] Putting participant on hold: ${p.callSid}`, + p, + ); + return p.update({ hold: true }); + }), ); await originalAgentParticipant?.remove(); // create New task @@ -204,7 +227,9 @@ export const transferCallToQueue = async ( attributes: JSON.stringify(newAttributes), priority: 100, }); - console.debug(`Transfer target task created with sid ${newTask.sid}`); + console.debug( + `[transfer: original task: ${originalTask.sid}, conference: ${taskConferenceInfo.sid}] Transfer target task created with sid ${newTask.sid}`, + ); return newTask.sid as TaskSID; }; @@ -280,7 +305,7 @@ export const transferStartHandler: AccountScopedHandler = async ( }; if (originalTask.taskChannelUniqueName === 'voice') { - const newTaskSid = transferCallToQueue(accountSid, originalTask, newAttributes); + const newTaskSid = coldTransferCallToQueue(accountSid, originalTask, newAttributes); return newOk({ taskSid: newTaskSid }); } From 6021490729bc90d2452b7fd99fdeb1d53b752f54 Mon Sep 17 00:00:00 2001 From: Stephen Hand Date: Mon, 12 Jan 2026 13:00:26 +0000 Subject: [PATCH 7/9] Missing await --- lambdas/account-scoped/src/transfer/transferStart.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lambdas/account-scoped/src/transfer/transferStart.ts b/lambdas/account-scoped/src/transfer/transferStart.ts index 9d43326559..d5bd703006 100644 --- a/lambdas/account-scoped/src/transfer/transferStart.ts +++ b/lambdas/account-scoped/src/transfer/transferStart.ts @@ -305,7 +305,11 @@ export const transferStartHandler: AccountScopedHandler = async ( }; if (originalTask.taskChannelUniqueName === 'voice') { - const newTaskSid = coldTransferCallToQueue(accountSid, originalTask, newAttributes); + const newTaskSid = await coldTransferCallToQueue( + accountSid, + originalTask, + newAttributes, + ); return newOk({ taskSid: newTaskSid }); } From a9352faef719672b0c453688f7f228268cf7ce5c Mon Sep 17 00:00:00 2001 From: Stephen Hand Date: Mon, 12 Jan 2026 16:40:59 +0000 Subject: [PATCH 8/9] Feature flag custom voice transfers --- plugin-hrm-form/src/transfer/setUpTransferActions.tsx | 8 ++++++-- plugin-hrm-form/src/types/FeatureFlags.ts | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/plugin-hrm-form/src/transfer/setUpTransferActions.tsx b/plugin-hrm-form/src/transfer/setUpTransferActions.tsx index d2b56cf282..710e1fcbda 100644 --- a/plugin-hrm-form/src/transfer/setUpTransferActions.tsx +++ b/plugin-hrm-form/src/transfer/setUpTransferActions.tsx @@ -30,7 +30,7 @@ import { import * as TransferHelpers from './transferTaskState'; import { transferModes } from '../states/DomainConstants'; import { recordEvent } from '../fullStory'; -import { getHrmConfig } from '../hrmConfig'; +import { getAseloFeatureFlags, getHrmConfig } from '../hrmConfig'; import { RootState } from '../states'; import { reactivateAseloListeners } from '../conversationListeners'; import selectContactByTaskSid from '../states/contacts/selectContactByTaskSid'; @@ -79,6 +79,7 @@ const getStateContactForms = (taskSid: string): ContactState => { const customTransferTask = (setupObject: SetupObject): ReplacedActionFunction => async ( payload: ActionPayloadWithOptions, original: ActionFunction, + // eslint-disable-next-line sonarjs/cognitive-complexity ) => { const mode = payload.options.mode || DEFAULT_TRANSFER_MODE; @@ -128,11 +129,14 @@ const customTransferTask = (setupObject: SetupObject): ReplacedActionFunction => }, }); } - const disableTransfer = !TransferHelpers.canTransferConference(payload.task); if (disableTransfer) { window.alert(Manager.getInstance().strings['Transfer-CannotTransferTooManyParticipants']); + } else { + const targetType = payload.targetSid.startsWith('WK') ? 'worker' : 'queue'; + const featureFlag = `use_custom_voice_transfers_for_${mode.toLowerCase()}_${targetType}_transfers`; + if (!getAseloFeatureFlags()[featureFlag]) return safeTransfer(() => original(payload), payload.task); } } diff --git a/plugin-hrm-form/src/types/FeatureFlags.ts b/plugin-hrm-form/src/types/FeatureFlags.ts index a92274a098..dc82fdbd39 100644 --- a/plugin-hrm-form/src/types/FeatureFlags.ts +++ b/plugin-hrm-form/src/types/FeatureFlags.ts @@ -47,4 +47,5 @@ export type FeatureFlags = { use_twilio_lambda_for_conference_functions: boolean; // Use the twilio account scoped lambda for conferencing functions use_twilio_lambda_for_conversation_duration: boolean; // Use the twilio account scoped lambda to calculate conversationDuration use_twilio_lambda_for_task_assignment: boolean; // Use the twilio account scoped lambda for getTasksAndReservations, checkTaskAssignment, completeTaskAssignment + use_custom_voice_transfers_for_cold_queue_transfers: boolean; }; From bb857d0858953a0f01486eaffbcb4a2e9db1d9ff Mon Sep 17 00:00:00 2001 From: Stephen Hand Date: Mon, 12 Jan 2026 17:03:52 +0000 Subject: [PATCH 9/9] Feature flag custom chat transfers via lambda --- plugin-hrm-form/src/services/transferService.ts | 3 +++ plugin-hrm-form/src/transfer/setUpTransferActions.tsx | 11 +++++++++-- plugin-hrm-form/src/types/FeatureFlags.ts | 1 + 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/plugin-hrm-form/src/services/transferService.ts b/plugin-hrm-form/src/services/transferService.ts index 7ca48fc873..49e0575454 100644 --- a/plugin-hrm-form/src/services/transferService.ts +++ b/plugin-hrm-form/src/services/transferService.ts @@ -28,3 +28,6 @@ type TransferChatStartReturn = { closed: string; kept: string }; export const transferStart = async (body: TransferChatStartBody): Promise => fetchProtectedApi('transfer/transferStart', body, { useTwilioLambda: true }); + +export const serverlessChatTransferStart = async (body: TransferChatStartBody): Promise => + fetchProtectedApi('transferChatStart', body, { useTwilioLambda: false }); diff --git a/plugin-hrm-form/src/transfer/setUpTransferActions.tsx b/plugin-hrm-form/src/transfer/setUpTransferActions.tsx index 710e1fcbda..3028d36aa5 100644 --- a/plugin-hrm-form/src/transfer/setUpTransferActions.tsx +++ b/plugin-hrm-form/src/transfer/setUpTransferActions.tsx @@ -36,7 +36,7 @@ import { reactivateAseloListeners } from '../conversationListeners'; import selectContactByTaskSid from '../states/contacts/selectContactByTaskSid'; import { ContactState } from '../states/contacts/existingContacts'; import { saveFormSharedState } from './formDataTransfer'; -import { transferStart } from '../services/transferService'; +import { serverlessChatTransferStart, transferStart } from '../services/transferService'; type SetupObject = ReturnType; type ActionPayload = { task: ITask }; @@ -81,6 +81,7 @@ const customTransferTask = (setupObject: SetupObject): ReplacedActionFunction => original: ActionFunction, // eslint-disable-next-line sonarjs/cognitive-complexity ) => { + const featureFlags = getAseloFeatureFlags(); const mode = payload.options.mode || DEFAULT_TRANSFER_MODE; /* @@ -147,7 +148,13 @@ const customTransferTask = (setupObject: SetupObject): ReplacedActionFunction => ignoreAgent: workerSid, }; - return safeTransfer(() => transferStart(body), payload.task); + return safeTransfer( + () => + featureFlags.use_twilio_lambda_for_starting_chat_transfers + ? transferStart(body) + : serverlessChatTransferStart(body), + payload.task, + ); }; const afterCancelTransfer = (payload: ActionPayload) => TransferHelpers.clearTransferMeta(payload.task); diff --git a/plugin-hrm-form/src/types/FeatureFlags.ts b/plugin-hrm-form/src/types/FeatureFlags.ts index dc82fdbd39..c203ea81f4 100644 --- a/plugin-hrm-form/src/types/FeatureFlags.ts +++ b/plugin-hrm-form/src/types/FeatureFlags.ts @@ -48,4 +48,5 @@ export type FeatureFlags = { use_twilio_lambda_for_conversation_duration: boolean; // Use the twilio account scoped lambda to calculate conversationDuration use_twilio_lambda_for_task_assignment: boolean; // Use the twilio account scoped lambda for getTasksAndReservations, checkTaskAssignment, completeTaskAssignment use_custom_voice_transfers_for_cold_queue_transfers: boolean; + use_twilio_lambda_for_starting_chat_transfers: boolean; };