From 9b7706761a1ed2f11d8a5c72907443e0a46d211d Mon Sep 17 00:00:00 2001 From: Stephen Hand Date: Fri, 27 Mar 2026 16:26:15 +0000 Subject: [PATCH 1/7] Ensure conversation participants have correct names in chat window --- .../src/components/MessageBubble.tsx | 26 ++++++++++++------- .../src/sessionDataHandler.ts | 13 +++++----- .../translationsSrc/en.json | 5 +++- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/aselo-webchat-react-app/src/components/MessageBubble.tsx b/aselo-webchat-react-app/src/components/MessageBubble.tsx index c8ac40cb1e..4da3602e2c 100644 --- a/aselo-webchat-react-app/src/components/MessageBubble.tsx +++ b/aselo-webchat-react-app/src/components/MessageBubble.tsx @@ -37,6 +37,7 @@ import { readStatusStyles, bubbleAndAvatarContainerStyles, } from './styles/MessageBubble.styles'; +import LocalizedTemplate from '../localization/LocalizedTemplate'; const doubleDigit = (number: number) => `${number < 10 ? 0 : ''}${number}`; @@ -56,14 +57,12 @@ export const MessageBubble = ({ }) => { const [read, setRead] = useState(false); const [isMouseDown, setIsMouseDown] = useState(false); - const { conversationsClient, participants, fileAttachmentConfig, participantNames } = useSelector( - (state: AppState) => ({ - conversationsClient: state.chat.conversationsClient, - participants: state.chat.participants, - fileAttachmentConfig: state.config.fileAttachment, - participantNames: state.chat.participantNames, - }), - ); + const { conversationsClient, participants, fileAttachmentConfig } = useSelector((state: AppState) => ({ + conversationsClient: state.chat.conversationsClient, + participants: state.chat.participants, + fileAttachmentConfig: state.config.fileAttachment, + participantNames: state.chat.participantNames, + })); const messageRef = useRef(null); const belongsToCurrentUser = message.author === conversationsClient?.user.identity; @@ -129,7 +128,14 @@ export const MessageBubble = ({ }; // const author = users?.find((u) => u.identity === message.author)?.friendlyName || message.author; - const name = participantNames ? participantNames[message.participantSid] : ''; + let name: string; + if (belongsToCurrentUser) { + name = 'MessagePhase-MessageBubble-OwnMessageSenderName'; + } else if (message.participantSid) { + name = 'MessagePhase-MessageBubble-OtherParticipantMessageSenderName'; + } else { + name = message.author; + } return ( - {name} + {belongsToCurrentUser ? 'You sent at' : `${name} sent at`} diff --git a/aselo-webchat-react-app/src/sessionDataHandler.ts b/aselo-webchat-react-app/src/sessionDataHandler.ts index 257d3543c1..5cf495f6ce 100644 --- a/aselo-webchat-react-app/src/sessionDataHandler.ts +++ b/aselo-webchat-react-app/src/sessionDataHandler.ts @@ -19,9 +19,10 @@ import { LocalStorageUtil } from './utils/LocalStorage'; import { generateMixPanelHeaders, generateSecurityHeaders } from './utils/generateHeaders'; import { ConfigState } from './store/definitions'; import { store } from './store/store'; +import { localizeKey } from './localization/localizeKey'; export const LOCALSTORAGE_SESSION_ITEM_ID = 'TWILIO_WEBCHAT_WIDGET'; -const CUSTOMER_DEFAULT_NAME = 'Customer'; +const CUSTOMER_DEFAULT_NAME_KEY = 'Conversation-Participants-CustomerDefaultName'; type SessionDataStorage = TokenResponse & { loginTimestamp: string | null; @@ -189,19 +190,19 @@ class SessionDataHandler { }); try { + const { config } = store.getState(); const payload: InitWebchatAPIPayload = { DeploymentKey: this.getDeploymentKey(), - CustomerFriendlyName: (formData?.friendlyName as string) || CUSTOMER_DEFAULT_NAME, + CustomerFriendlyName: + (formData?.friendlyName as string) || + localizeKey(config.translations[config.currentLocale ?? config.defaultLocale])(CUSTOMER_DEFAULT_NAME_KEY), PreEngagementData: JSON.stringify(formData), }; if (customerIdentity) { payload.Identity = customerIdentity; } - newTokenData = await contactBackend(store.getState().config)( - '/webchatAuthentication/initWebchat', - payload, - ); + newTokenData = await contactBackend(config)('/webchatAuthentication/initWebchat', payload); } catch (e) { logger.error('No results from server'); throw Error('No results from server'); diff --git a/aselo-webchat-react-app/translationsSrc/en.json b/aselo-webchat-react-app/translationsSrc/en.json index 9df5323256..14b6c5240b 100644 --- a/aselo-webchat-react-app/translationsSrc/en.json +++ b/aselo-webchat-react-app/translationsSrc/en.json @@ -1,9 +1,12 @@ { + "Conversation-Participants-CustomerDefaultName": "Anonymous", "Header-CloseChatButtons-EndChatButtonLabel": "End Chat", "Header-CloseChatButtons-EndChatConfirmDialogMessageFromChat": "End the current chat? Your messages will be lost.", "Header-CloseChatButtons-EndChatConfirmDialogMessageFromPreEngagement": "Abandon the current chat?", "Header-CloseChatButtons-QuickExitButtonLabel": "Quick Exit", "Header-CloseChatButtons-QuickExitDescription": "Need to leave immediately?", "Header-TitleBar-Title": "Live Chat", - "MessagePhase-MessageList-ChatStartMessage": "Chat Started" + "MessagePhase-MessageList-ChatStartMessage": "Chat Started", + "MessagePhase-MessageBubble-OtherParticipantMessageSenderName": "Counsellor", + "MessagePhase-MessageBubble-OwnMessageSenderName": "You" } \ No newline at end of file From ea16514ae234fd3a200da668623b4e7fbcd8644e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Mar 2026 18:03:38 +0000 Subject: [PATCH 2/7] Initial plan for fixing failing MessageBubble tests Agent-Logs-Url: https://github.com/techmatters/flex-plugins/sessions/783c2c53-643e-4714-9a8b-bb1fe99abd53 Co-authored-by: stephenhand <1694716+stephenhand@users.noreply.github.com> --- aselo-webchat-react-app/package-lock.json | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/aselo-webchat-react-app/package-lock.json b/aselo-webchat-react-app/package-lock.json index 55f1df8059..f510682242 100644 --- a/aselo-webchat-react-app/package-lock.json +++ b/aselo-webchat-react-app/package-lock.json @@ -107,12 +107,12 @@ "eslint-import-resolver-typescript": "^2.4.0", "eslint-plugin-import": "^2.22.0", "eslint-plugin-prettier": "^3.1.4", - "jest": "^27.4.5", + "jest": "^30.3.0", "jest-config": "^28.1.3", "jest-each": "^28.1.1", "jest-junit": "^16.0.0", "prettier": "^2.3.2", - "ts-jest": "^27.1.2", + "ts-jest": "^29.4.6", "ts-node": "^10.1.0", "typescript": "^4.3.5" } @@ -31416,6 +31416,23 @@ } } }, + "node_modules/tailwindcss/node_modules/yaml": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/tapable": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", From 068b329473dbaa44d81df140f18b4b86bbf6ffe8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Mar 2026 18:09:38 +0000 Subject: [PATCH 3/7] Fix failing MessageBubble unit tests due to localization changes - Use localizeKey directly in MessageBubble.tsx instead of LocalizedTemplate component to get translated name for both visual text and screen reader text - Fix screen reader text to use translated name (was showing raw i18n key) - Fix null guard: use message.author || '' to prevent Mustache.render error - Update MessageBubble.test.tsx: add translations/defaultLocale to mock state, update assertions to check for 'You'/'Counsellor' instead of user friendly names Agent-Logs-Url: https://github.com/techmatters/flex-plugins/sessions/783c2c53-643e-4714-9a8b-bb1fe99abd53 Co-authored-by: stephenhand <1694716+stephenhand@users.noreply.github.com> --- .../src/components/MessageBubble.tsx | 27 +++++++++++-------- .../__tests__/MessageBubble.test.tsx | 15 ++++++++--- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/aselo-webchat-react-app/src/components/MessageBubble.tsx b/aselo-webchat-react-app/src/components/MessageBubble.tsx index 4da3602e2c..9caaf52197 100644 --- a/aselo-webchat-react-app/src/components/MessageBubble.tsx +++ b/aselo-webchat-react-app/src/components/MessageBubble.tsx @@ -37,7 +37,8 @@ import { readStatusStyles, bubbleAndAvatarContainerStyles, } from './styles/MessageBubble.styles'; -import LocalizedTemplate from '../localization/LocalizedTemplate'; +import { selectCurrentTranslations } from '../store/config.reducer'; +import { localizeKey } from '../localization/localizeKey'; const doubleDigit = (number: number) => `${number < 10 ? 0 : ''}${number}`; @@ -57,12 +58,15 @@ export const MessageBubble = ({ }) => { const [read, setRead] = useState(false); const [isMouseDown, setIsMouseDown] = useState(false); - const { conversationsClient, participants, fileAttachmentConfig } = useSelector((state: AppState) => ({ - conversationsClient: state.chat.conversationsClient, - participants: state.chat.participants, - fileAttachmentConfig: state.config.fileAttachment, - participantNames: state.chat.participantNames, - })); + const { conversationsClient, participants, fileAttachmentConfig, currentTranslations } = useSelector( + (state: AppState) => ({ + conversationsClient: state.chat.conversationsClient, + participants: state.chat.participants, + fileAttachmentConfig: state.config.fileAttachment, + participantNames: state.chat.participantNames, + currentTranslations: selectCurrentTranslations(state), + }), + ); const messageRef = useRef(null); const belongsToCurrentUser = message.author === conversationsClient?.user.identity; @@ -134,8 +138,9 @@ export const MessageBubble = ({ } else if (message.participantSid) { name = 'MessagePhase-MessageBubble-OtherParticipantMessageSenderName'; } else { - name = message.author; + name = message.author || ''; } + const translatedName = localizeKey(currentTranslations)(name); return ( - - + + {translatedName} - {belongsToCurrentUser ? 'You sent at' : `${name} sent at`} + {`${translatedName} sent at`} {`${doubleDigit(message.dateCreated.getHours())}:${doubleDigit(message.dateCreated.getMinutes())}`} diff --git a/aselo-webchat-react-app/src/components/__tests__/MessageBubble.test.tsx b/aselo-webchat-react-app/src/components/__tests__/MessageBubble.test.tsx index 649e990005..fe54a4c384 100644 --- a/aselo-webchat-react-app/src/components/__tests__/MessageBubble.test.tsx +++ b/aselo-webchat-react-app/src/components/__tests__/MessageBubble.test.tsx @@ -37,7 +37,16 @@ const user2 = { sid: '62563cwqdc213cec23', }; const defaultState = { - config: { fileAttachment: fileAttachmentConfig }, + config: { + fileAttachment: fileAttachmentConfig, + translations: { + 'en-US': { + 'MessagePhase-MessageBubble-OwnMessageSenderName': 'You', + 'MessagePhase-MessageBubble-OtherParticipantMessageSenderName': 'Counsellor', + }, + }, + defaultLocale: 'en-US', + }, chat: { conversationsClient: { user: user1 }, participants: [{ identity: user1.identity }, { identity: user2.identity }], @@ -115,7 +124,7 @@ describe('Message Bubble', () => { />, ); - expect(queryByText(user1.friendlyName)).toBeInTheDocument(); + expect(queryByText('You')).toBeInTheDocument(); }); it('renders the message body', () => { @@ -172,7 +181,7 @@ describe('Message Bubble', () => { />, ); - expect(getByText(`${user2.friendlyName} sent at`)).toBeInTheDocument(); + expect(getByText('Counsellor sent at')).toBeInTheDocument(); }); it("renders 'is read' and icon when message read by other participant", () => { From 23bc1a103ca96788c3c57d2ac74b3c07879ccefa Mon Sep 17 00:00:00 2001 From: Stephen Hand Date: Tue, 31 Mar 2026 11:42:29 +0100 Subject: [PATCH 4/7] Update friendlyName of user in conversations created in Twilio Lambda. createConversation now uses Result type --- aselo-webchat-react-app/package.json | 5 ++- .../src/services/configService.ts | 5 ++- .../src/conversation/createConversation.ts | 19 +++++++-- .../src/customChannels/customChannelToFlex.ts | 41 ++++++++++--------- .../instagram/instagramToFlex.ts | 2 +- .../src/customChannels/line/lineToFlex.ts | 2 +- .../src/customChannels/modica/modicaToFlex.ts | 2 +- .../src/webchatAuthentication/initWebchat.ts | 16 +++++--- .../customChannels/line/lineToFlex.test.ts | 7 ++++ .../modica/modicaToFlex.test.ts | 9 +++- .../telegram/telegramToFlex.test.ts | 7 ++++ .../webchatAuthentication/initWebchat.test.ts | 14 ++++--- .../as/v1/webchat/PreEngagementForm.json | 7 ++++ 13 files changed, 96 insertions(+), 40 deletions(-) diff --git a/aselo-webchat-react-app/package.json b/aselo-webchat-react-app/package.json index e8ced9a5d0..9682ab57ec 100644 --- a/aselo-webchat-react-app/package.json +++ b/aselo-webchat-react-app/package.json @@ -84,7 +84,7 @@ "scripts": { "preinstall": "cd ../lambdas/packages/hrm-form-definitions && npm ci && npm run build && cd ../hrm-types && npm ci && npm run build", "start": "react-app-rewired start", - "start:as_dev": "cross-env REACT_APP_CONFIG_URL='http://localhost:9090/as/development.json' run-p dev:merge-configs dev:local-config-server start", + "start:as_dev": "cross-env REACT_APP_CONFIG_URL='http://localhost:9090/as/development.json' REACT_APP_FORM_DEFINITIONS_BASE_URL='http://localhost:8091' run-p dev:merge-configs dev:local-config-server dev:local-form-definition-server start", "build": "react-app-rewired build", "lint": "eslint --ext js --ext jsx --ext ts --ext tsx src/", "lint:fix": "npm run lint -- --fix", @@ -97,7 +97,8 @@ "eject": "react-app-rewired eject", "test:e2e": "cypress open", "dev:merge-configs": "npm run mergeConfigs development as", - "dev:local-config-server": "npx http-server ./mergedConfigs -p 9090 --cors -c-1" + "dev:local-config-server": "npx http-server ./mergedConfigs -p 9090 --cors -c-1", + "dev:local-form-definition-server": "npx http-server ../lambdas/packages/hrm-form-definitions/form-definitions -p 8091 --cors -c-1" }, "browserslist": [ ">0.2%", diff --git a/aselo-webchat-react-app/src/services/configService.ts b/aselo-webchat-react-app/src/services/configService.ts index 3dc62d0eb4..d360386bab 100644 --- a/aselo-webchat-react-app/src/services/configService.ts +++ b/aselo-webchat-react-app/src/services/configService.ts @@ -25,7 +25,10 @@ export const getDefinitionVersion = async ({ }): Promise<{ preEngagementForm: PreEngagementForm; }> => { - const formDefinitionsBaseUrl = buildFormDefinitionsBaseUrlGetter({ environment })(definitionVersionId); + const formDefinitionsBaseUrl = buildFormDefinitionsBaseUrlGetter({ + environment, + configuredFormDefinitionsBaseUrl: process.env.REACT_APP_FORM_DEFINITIONS_BASE_URL, + })(definitionVersionId); // eslint-disable-next-line const definition = await loadWebchatDefinition(formDefinitionsBaseUrl); return definition; diff --git a/lambdas/account-scoped/src/conversation/createConversation.ts b/lambdas/account-scoped/src/conversation/createConversation.ts index 2a4ddc5989..c3e169d130 100644 --- a/lambdas/account-scoped/src/conversation/createConversation.ts +++ b/lambdas/account-scoped/src/conversation/createConversation.ts @@ -17,6 +17,7 @@ import { Twilio } from 'twilio'; import { ConversationSID } from '@tech-matters/twilio-types'; import { AseloCustomChannel } from '../customChannels/aseloCustomChannels'; +import { newErr, newOk, Result } from '../Result'; const CONVERSATION_CLOSE_TIMEOUT = 'P3D'; // ISO 8601 duration format https://en.wikipedia.org/wiki/ISO_8601 export type CreateFlexConversationParams = { @@ -47,7 +48,12 @@ export const createConversation = async ( additionalConversationAttributes, testSessionId, }: CreateFlexConversationParams, -): Promise<{ conversationSid: ConversationSID; error?: Error }> => { +): Promise< + Result< + { conversationSid?: ConversationSID; cause: Error }, + { conversationSid: ConversationSID } + > +> => { if (testSessionId) { console.info( 'testSessionId specified. All outgoing messages will be sent to the test API.', @@ -67,6 +73,9 @@ export const createConversation = async ( await conversationContext.participants.create({ identity: uniqueUserName, }); + await client.conversations.v1.users + .get(uniqueUserName) + .update({ friendlyName: senderScreenName }); const channelAttributes = JSON.parse((await conversationContext.fetch()).attributes); console.debug('channelAttributes prior to update', channelAttributes); @@ -91,7 +100,6 @@ export const createConversation = async ( 'configuration.filters': ['onMessageAdded'], }); if (onMessageAddedWebhookUrl) { - /* const onMessageAdded = */ await conversationContext.webhooks.create({ target: 'webhook', 'configuration.method': 'POST', @@ -100,8 +108,11 @@ export const createConversation = async ( }); } } catch (err) { - return { conversationSid, error: err as Error }; + return newErr({ + message: `Create conversation failed: ${(err as Error)?.message}`, + error: { conversationSid, cause: err as Error }, + }); } - return { conversationSid }; + return newOk({ conversationSid }); }; diff --git a/lambdas/account-scoped/src/customChannels/customChannelToFlex.ts b/lambdas/account-scoped/src/customChannels/customChannelToFlex.ts index 7abfbd0ea5..5f0c7ed2ec 100644 --- a/lambdas/account-scoped/src/customChannels/customChannelToFlex.ts +++ b/lambdas/account-scoped/src/customChannels/customChannelToFlex.ts @@ -21,6 +21,7 @@ import { createConversation, CreateFlexConversationParams, } from '../conversation/createConversation'; +import { isErr } from '../Result'; export const findExistingConversation = async ( client: Twilio, @@ -75,7 +76,7 @@ export const removeConversation = async ( }: { conversationSid: ConversationSID; }, -) => client.conversations.v1.conversations(conversationSid).remove(); +) => client.conversations.v1.conversations.get(conversationSid).remove(); export { AseloCustomChannel, isAseloCustomChannel } from './aseloCustomChannels'; @@ -125,26 +126,28 @@ export const sendConversationMessageToFlex = async ( let conversationSid = await findExistingConversation(client, uniqueUserName); if (!conversationSid) { - const { conversationSid: newConversationSid, error } = await createConversation( - client, - { - studioFlowSid, - channelType, - twilioNumber, - uniqueUserName, - senderScreenName, - onMessageAddedWebhookUrl, - conversationFriendlyName, - testSessionId, - }, - ); + const createConverationResult = await createConversation(client, { + studioFlowSid, + channelType, + twilioNumber, + uniqueUserName, + senderScreenName, + onMessageAddedWebhookUrl, + conversationFriendlyName, + testSessionId, + }); - if (error) { - await removeConversation(client, { - conversationSid: newConversationSid, - }); - throw error; + if (isErr(createConverationResult)) { + const { conversationSid: newConversationSid, cause } = + createConverationResult.error; + if (newConversationSid) { + await removeConversation(client, { + conversationSid: newConversationSid, + }); + } + throw cause; } + const { conversationSid: newConversationSid } = createConverationResult.data; conversationSid = newConversationSid; } diff --git a/lambdas/account-scoped/src/customChannels/instagram/instagramToFlex.ts b/lambdas/account-scoped/src/customChannels/instagram/instagramToFlex.ts index 5231a99599..c9c5a7e397 100644 --- a/lambdas/account-scoped/src/customChannels/instagram/instagramToFlex.ts +++ b/lambdas/account-scoped/src/customChannels/instagram/instagramToFlex.ts @@ -120,7 +120,7 @@ export const instagramToFlexHandler: AccountScopedHandler = async ( const channelType = AseloCustomChannel.Instagram; const chatFriendlyName = `${channelType}:${senderExternalId}`; const uniqueUserName = `${channelType}:${senderExternalId}`; - const senderScreenName = uniqueUserName; // TODO: see if we can use ig handle somehow + const senderScreenName = senderExternalId; // TODO: see if we can use ig handle somehow const messageAttributes = JSON.stringify({ messageExternalId }); const onMessageSentWebhookUrl = `${process.env.WEBHOOK_BASE_URL}/lambda/twilio/account-scoped/${accountSid}/customChannels/instagram/flexToInstagram?recipientId=${senderExternalId}`; const studioFlowSid = await getChannelStudioFlowSid( diff --git a/lambdas/account-scoped/src/customChannels/line/lineToFlex.ts b/lambdas/account-scoped/src/customChannels/line/lineToFlex.ts index a4cdc06871..37aaa6d5b9 100644 --- a/lambdas/account-scoped/src/customChannels/line/lineToFlex.ts +++ b/lambdas/account-scoped/src/customChannels/line/lineToFlex.ts @@ -136,7 +136,7 @@ export const lineToFlexHandler: AccountScopedHandler = async ( const senderExternalId = messageEvent.source.userId; // The child ID on Line const chatFriendlyName = `${channelType}:${senderExternalId}`; const uniqueUserName = `${channelType}:${senderExternalId}`; - const senderScreenName = 'child'; + const senderScreenName = senderExternalId; const onMessageSentWebhookUrl = `${process.env.WEBHOOK_BASE_URL}/lambda/twilio/account-scoped/${accountSid}/customChannels/line/flexToLine?recipientId=${senderExternalId}`; console.debug( 'LineToFlex: sending message from', diff --git a/lambdas/account-scoped/src/customChannels/modica/modicaToFlex.ts b/lambdas/account-scoped/src/customChannels/modica/modicaToFlex.ts index 6721656caf..8db9599512 100644 --- a/lambdas/account-scoped/src/customChannels/modica/modicaToFlex.ts +++ b/lambdas/account-scoped/src/customChannels/modica/modicaToFlex.ts @@ -46,7 +46,7 @@ export const modicaToFlexHandler: AccountScopedHandler = async ( const senderExternalId = source; // The child phone number const chatFriendlyName = senderExternalId; const uniqueUserName = `${channelType}:${senderExternalId}`; - const senderScreenName = 'child'; + const senderScreenName = senderExternalId; const onMessageSentWebhookUrl = `${process.env.WEBHOOK_BASE_URL}/lambda/twilio/account-scoped/${accountSid}/customChannels/modica/flexToModica?recipientId=${senderExternalId}`; const studioFlowSid = await getChannelStudioFlowSid( accountSid, diff --git a/lambdas/account-scoped/src/webchatAuthentication/initWebchat.ts b/lambdas/account-scoped/src/webchatAuthentication/initWebchat.ts index a6860ddc9e..49bd391d86 100644 --- a/lambdas/account-scoped/src/webchatAuthentication/initWebchat.ts +++ b/lambdas/account-scoped/src/webchatAuthentication/initWebchat.ts @@ -42,7 +42,7 @@ const contactWebchatOrchestrator = async ({ console.info(`Creating new conversation via the API with sender ID: ${senderId}`); const client = await getTwilioClient(accountSid); - const { conversationSid, error } = await createConversation(client, { + const result = await createConversation(client, { channelType: 'web', conversationFriendlyName: customerFriendlyName, senderScreenName: customerFriendlyName, @@ -55,16 +55,22 @@ const contactWebchatOrchestrator = async ({ from: customerFriendlyName, }, }); - if (error) { - console.error('Error creating web conversation', accountSid, error); + if (isErr(result)) { + const { conversationSid, cause } = result.error; + console.error( + `Error creating web conversation ${conversationSid}`, + accountSid, + cause, + ); return newErr({ - message: error.message, + message: result.message, error: { statusCode: 500, - cause: error, + cause, }, }); } + const { conversationSid } = result.data; console.info( `Created new conversation ${conversationSid} via the API with sender ID: ${senderId}`, ); diff --git a/lambdas/account-scoped/tests/unit/customChannels/line/lineToFlex.test.ts b/lambdas/account-scoped/tests/unit/customChannels/line/lineToFlex.test.ts index 43e8dd9f6f..dc01f96555 100644 --- a/lambdas/account-scoped/tests/unit/customChannels/line/lineToFlex.test.ts +++ b/lambdas/account-scoped/tests/unit/customChannels/line/lineToFlex.test.ts @@ -117,6 +117,13 @@ describe('LineToFlex', () => { participantConversations: { list: () => [{ conversationState: 'active' }], }, + users: { + get: (identifier: string) => { + return { + update: jest.fn().mockResolvedValue({ identifier }), + }; + }, + }, }, }, }; diff --git a/lambdas/account-scoped/tests/unit/customChannels/modica/modicaToFlex.test.ts b/lambdas/account-scoped/tests/unit/customChannels/modica/modicaToFlex.test.ts index d7bb3c8c5e..363efdbafd 100644 --- a/lambdas/account-scoped/tests/unit/customChannels/modica/modicaToFlex.test.ts +++ b/lambdas/account-scoped/tests/unit/customChannels/modica/modicaToFlex.test.ts @@ -72,6 +72,7 @@ const validBody = ({ }); describe('ModicaToFlex', () => { + const userUpdate = jest.fn(); beforeEach(() => { mockTwilioClient = { conversations: { @@ -89,6 +90,13 @@ describe('ModicaToFlex', () => { participantConversations: { list: () => [{ conversationState: 'active' }], }, + users: { + get: (identifier: string) => { + return { + update: userUpdate.mockResolvedValue({ identifier }), + }; + }, + }, }, }, }; @@ -116,7 +124,6 @@ describe('ModicaToFlex', () => { { body: validBody() } as HttpRequest, ACCOUNT_SID, ); - expect(result).toBeDefined(); expect(isOk(result)).toBe(true); }); diff --git a/lambdas/account-scoped/tests/unit/customChannels/telegram/telegramToFlex.test.ts b/lambdas/account-scoped/tests/unit/customChannels/telegram/telegramToFlex.test.ts index d8b3eac2b7..1076c4ed1b 100644 --- a/lambdas/account-scoped/tests/unit/customChannels/telegram/telegramToFlex.test.ts +++ b/lambdas/account-scoped/tests/unit/customChannels/telegram/telegramToFlex.test.ts @@ -103,6 +103,13 @@ describe('TelegramToFlex', () => { participantConversations: { list: () => [{ conversationState: 'active' }], }, + users: { + get: (identifier: string) => { + return { + update: jest.fn().mockResolvedValue({ identifier }), + }; + }, + }, }, }, }; diff --git a/lambdas/account-scoped/tests/unit/webchatAuthentication/initWebchat.test.ts b/lambdas/account-scoped/tests/unit/webchatAuthentication/initWebchat.test.ts index 5c449bf744..f983a01021 100644 --- a/lambdas/account-scoped/tests/unit/webchatAuthentication/initWebchat.test.ts +++ b/lambdas/account-scoped/tests/unit/webchatAuthentication/initWebchat.test.ts @@ -16,7 +16,7 @@ import { initWebchatHandler } from '../../../src/webchatAuthentication/initWebchat'; import { TEST_ACCOUNT_SID, TEST_CONVERSATION_SID } from '../../testTwilioValues'; -import { isErr, isOk } from '../../../src/Result'; +import { isErr, isOk, newErr, newOk } from '../../../src/Result'; jest.mock('@tech-matters/twilio-configuration', () => ({ getTwilioClient: jest.fn(), @@ -72,7 +72,9 @@ describe('initWebchatHandler', () => { jest.clearAllMocks(); mockGetTwilioClient.mockResolvedValue(mockTwilioClient); mockGetChannelStudioFlowSid.mockResolvedValue(TEST_STUDIO_FLOW_SID); - mockCreateConversation.mockResolvedValue({ conversationSid: TEST_CONVERSATION_SID }); + mockCreateConversation.mockResolvedValue( + newOk({ conversationSid: TEST_CONVERSATION_SID }), + ); mockCreateToken.mockResolvedValue(TEST_TOKEN); }); @@ -123,15 +125,17 @@ describe('initWebchatHandler', () => { } }); - it('returns an error result when createConversation throws', async () => { - mockCreateConversation.mockRejectedValue(new Error('Conversation creation failed')); + it('returns an error result when createConversation returns error result', async () => { + mockCreateConversation.mockResolvedValue( + newErr({ message: 'Boom', error: { cause: new Error('Shakalakka') } }), + ); const request = createMockRequest(); const result = await initWebchatHandler(request, TEST_ACCOUNT_SID); expect(isErr(result)).toBe(true); if (isErr(result)) { expect(result.error.statusCode).toBe(500); - expect(result.message).toBe('Conversation creation failed'); + expect(result.message).toBe('Boom'); } expect(mockCreateToken).not.toHaveBeenCalled(); }); diff --git a/lambdas/packages/hrm-form-definitions/form-definitions/as/v1/webchat/PreEngagementForm.json b/lambdas/packages/hrm-form-definitions/form-definitions/as/v1/webchat/PreEngagementForm.json index 039c816f2d..97ba2a595e 100644 --- a/lambdas/packages/hrm-form-definitions/form-definitions/as/v1/webchat/PreEngagementForm.json +++ b/lambdas/packages/hrm-form-definitions/form-definitions/as/v1/webchat/PreEngagementForm.json @@ -2,6 +2,13 @@ "description": "PreEngagementDescription", "submitLabel": "LetsChat", "fields": [ + { + "type": "input", + "name": "friendlyName", + "label": "Nickname", + "placeholder": "GuestName", + "required": true + }, { "type": "input", "name": "firstName", From 2eec22b7681ef85311975901c82cc6f415489ac2 Mon Sep 17 00:00:00 2001 From: Stephen Hand Date: Wed, 1 Apr 2026 08:52:21 +0100 Subject: [PATCH 5/7] Update aselo-webchat-react-app/src/components/MessageBubble.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- aselo-webchat-react-app/src/components/MessageBubble.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/aselo-webchat-react-app/src/components/MessageBubble.tsx b/aselo-webchat-react-app/src/components/MessageBubble.tsx index 9caaf52197..587bc383bb 100644 --- a/aselo-webchat-react-app/src/components/MessageBubble.tsx +++ b/aselo-webchat-react-app/src/components/MessageBubble.tsx @@ -63,7 +63,6 @@ export const MessageBubble = ({ conversationsClient: state.chat.conversationsClient, participants: state.chat.participants, fileAttachmentConfig: state.config.fileAttachment, - participantNames: state.chat.participantNames, currentTranslations: selectCurrentTranslations(state), }), ); From bd74e456a3d8ede0231b485e320a024d04da0681 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Apr 2026 07:58:52 +0000 Subject: [PATCH 6/7] Move conversations.create() inside try/catch so all failures return a Result The initial `client.conversations.v1.conversations.create()` call was outside the try/catch, causing createConversation to throw instead of returning a newErr Result when that call failed. Split into two try/catch blocks: the first wraps the initial conversation creation (returning newErr without a conversationSid on failure), and the second wraps all subsequent setup (returning newErr with the conversationSid). Agent-Logs-Url: https://github.com/techmatters/flex-plugins/sessions/6db22729-5826-4470-9436-24b2a825ee89 Co-authored-by: stephenhand <1694716+stephenhand@users.noreply.github.com> --- .../src/conversation/createConversation.ts | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/lambdas/account-scoped/src/conversation/createConversation.ts b/lambdas/account-scoped/src/conversation/createConversation.ts index c3e169d130..0a584aaa69 100644 --- a/lambdas/account-scoped/src/conversation/createConversation.ts +++ b/lambdas/account-scoped/src/conversation/createConversation.ts @@ -60,12 +60,20 @@ export const createConversation = async ( ); } - const conversationInstance = await client.conversations.v1.conversations.create({ - xTwilioWebhookEnabled: 'true', - friendlyName: conversationFriendlyName, - uniqueName: `${channelType}/${uniqueUserName}/${Date.now()}`, - }); - const conversationSid = conversationInstance.sid as ConversationSID; + let conversationSid: ConversationSID; + try { + const conversationInstance = await client.conversations.v1.conversations.create({ + xTwilioWebhookEnabled: 'true', + friendlyName: conversationFriendlyName, + uniqueName: `${channelType}/${uniqueUserName}/${Date.now()}`, + }); + conversationSid = conversationInstance.sid as ConversationSID; + } catch (err) { + return newErr({ + message: `Create conversation failed: ${(err as Error)?.message}`, + error: { cause: err as Error }, + }); + } try { const conversationContext = From 9eda17623cd3c79473735096a28d725b05d5cbe4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Apr 2026 08:48:18 +0000 Subject: [PATCH 7/7] Fix typo in customChannelToFlex.ts and add localizeKey fallback in sessionDataHandler.ts - Fix typo: createConverationResult -> createConversationResult in customChannelToFlex.ts - Add safe fallback in sessionDataHandler.ts: if localizeKey returns the raw key string unchanged (meaning the key is not in the current locale translations), fall back to the hardcoded 'Anonymous' default instead of using the raw key Agent-Logs-Url: https://github.com/techmatters/flex-plugins/sessions/7bddef15-9291-4150-b998-7d199b6e58ae Co-authored-by: stephenhand <1694716+stephenhand@users.noreply.github.com> --- aselo-webchat-react-app/src/sessionDataHandler.ts | 12 +++++++++--- .../src/customChannels/customChannelToFlex.ts | 8 ++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/aselo-webchat-react-app/src/sessionDataHandler.ts b/aselo-webchat-react-app/src/sessionDataHandler.ts index 5cf495f6ce..e7c4297c49 100644 --- a/aselo-webchat-react-app/src/sessionDataHandler.ts +++ b/aselo-webchat-react-app/src/sessionDataHandler.ts @@ -193,9 +193,15 @@ class SessionDataHandler { const { config } = store.getState(); const payload: InitWebchatAPIPayload = { DeploymentKey: this.getDeploymentKey(), - CustomerFriendlyName: - (formData?.friendlyName as string) || - localizeKey(config.translations[config.currentLocale ?? config.defaultLocale])(CUSTOMER_DEFAULT_NAME_KEY), + CustomerFriendlyName: (() => { + const localizedName = localizeKey(config.translations[config.currentLocale ?? config.defaultLocale])( + CUSTOMER_DEFAULT_NAME_KEY, + ); + return ( + (formData?.friendlyName as string) || + (localizedName === CUSTOMER_DEFAULT_NAME_KEY ? 'Anonymous' : localizedName) + ); + })(), PreEngagementData: JSON.stringify(formData), }; diff --git a/lambdas/account-scoped/src/customChannels/customChannelToFlex.ts b/lambdas/account-scoped/src/customChannels/customChannelToFlex.ts index 5f0c7ed2ec..c07b0e7b4a 100644 --- a/lambdas/account-scoped/src/customChannels/customChannelToFlex.ts +++ b/lambdas/account-scoped/src/customChannels/customChannelToFlex.ts @@ -126,7 +126,7 @@ export const sendConversationMessageToFlex = async ( let conversationSid = await findExistingConversation(client, uniqueUserName); if (!conversationSid) { - const createConverationResult = await createConversation(client, { + const createConversationResult = await createConversation(client, { studioFlowSid, channelType, twilioNumber, @@ -137,9 +137,9 @@ export const sendConversationMessageToFlex = async ( testSessionId, }); - if (isErr(createConverationResult)) { + if (isErr(createConversationResult)) { const { conversationSid: newConversationSid, cause } = - createConverationResult.error; + createConversationResult.error; if (newConversationSid) { await removeConversation(client, { conversationSid: newConversationSid, @@ -147,7 +147,7 @@ export const sendConversationMessageToFlex = async ( } throw cause; } - const { conversationSid: newConversationSid } = createConverationResult.data; + const { conversationSid: newConversationSid } = createConversationResult.data; conversationSid = newConversationSid; }