From 511fd4d7aaed9dc5cd4665a216b28f279347e3b0 Mon Sep 17 00:00:00 2001 From: ruwinirathnamalala Date: Fri, 20 Mar 2026 18:38:33 +0530 Subject: [PATCH 1/4] #932 bug fix - Broken Links & HTML Artifacts in Message to Client --- .../hbs/bot_responses_to_messages.handlebars | 2 +- .../components/Flow/NodeTypes/StepNode.tsx | 13 +++++++++++- GUI/src/components/Markdowify/index.tsx | 13 +++++++++++- GUI/src/components/chat/bot-message.tsx | 1 - GUI/src/components/chat/chat.module.scss | 20 +++++++++++++++++++ GUI/src/services/service-builder.ts | 6 +++--- GUI/src/utils/string-util.ts | 20 +++++++++++++++++++ 7 files changed, 68 insertions(+), 7 deletions(-) diff --git a/DSL/DMapper/services/hbs/bot_responses_to_messages.handlebars b/DSL/DMapper/services/hbs/bot_responses_to_messages.handlebars index aa0230198..d0aacf50e 100644 --- a/DSL/DMapper/services/hbs/bot_responses_to_messages.handlebars +++ b/DSL/DMapper/services/hbs/bot_responses_to_messages.handlebars @@ -2,7 +2,7 @@ {{#each data.botMessages}} { "chatId": "{{../data.chatId}}", - "content": "{{filterControlCharacters result}}", + "content": "{{{filterControlCharacters (escapeQuotes result)}}}", "buttons": "[{{#each ../data.buttons}}{\"title\": \"{{#if (eq title true)}}Yes{{else if (eq title false)}}No{{else}}{{{title}}}{{/if}}\",\"payload\": \"{{{payload}}}\"}{{#unless @last}},{{/unless}}{{/each}}]", "authorTimestamp": "{{../data.authorTimestamp}}", "authorId": "{{../data.authorId}}", diff --git a/GUI/src/components/Flow/NodeTypes/StepNode.tsx b/GUI/src/components/Flow/NodeTypes/StepNode.tsx index e33b8407f..9aae6a268 100644 --- a/GUI/src/components/Flow/NodeTypes/StepNode.tsx +++ b/GUI/src/components/Flow/NodeTypes/StepNode.tsx @@ -3,10 +3,12 @@ import ExclamationBadge from 'components/ExclamationBadge'; import Track from 'components/Track'; import { FC, memo, useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import sanitizeHtml from 'sanitize-html'; import useServiceStore from 'store/new-services.store'; import { StepType } from 'types'; import { NodeDataProps } from 'types/service-flow'; import { validateStep } from 'utils/flow-utils'; +import { decodeHtmlEntities } from 'utils/string-util'; type StepNodeProps = { data: NodeDataProps; @@ -21,8 +23,17 @@ const StepNode: FC = ({ data }) => { fontWeight: 500, }; const createMarkup = (text: string) => { + const decodedText = decodeHtmlEntities(text); + const sanitizedText = sanitizeHtml(decodedText, { + allowedTags: ['a', 'b', 'strong', 'i', 'em', 'u', 'p', 'br', 'ul', 'ol', 'li', 'code'], + allowedAttributes: { + a: ['href', 'target', 'rel'], + }, + disallowedTagsMode: 'discard', + }); + return { - __html: text, + __html: sanitizedText, }; }; diff --git a/GUI/src/components/Markdowify/index.tsx b/GUI/src/components/Markdowify/index.tsx index e4b192529..7ff568a4e 100644 --- a/GUI/src/components/Markdowify/index.tsx +++ b/GUI/src/components/Markdowify/index.tsx @@ -72,6 +72,16 @@ const LinkPreview: React.FC<{ const hasSpecialFormat = (m: string) => m.includes('\n\n') && m.indexOf('.') > 0 && m.indexOf(':') > m.indexOf('.'); +const htmlLinkToMarkdown = (value: string): string => + value.replaceAll( + /]*href\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))[^>]*>([\s\S]*?)<\/a>/gi, + (_, href1: string, href2: string, href3: string, label: string) => { + const href = (href1 ?? href2 ?? href3 ?? '').trim(); + const text = sanitizeHtml(label ?? '', { allowedTags: [], allowedAttributes: {} }).trim() || href; + return href ? `[${text}](${href})` : text; + }, + ); + function formatMessage(message?: string): string { const sanitizedMessage = sanitizeHtml(message ?? ''); @@ -87,8 +97,9 @@ function formatMessage(message?: string): string { dataImagePattern, (_, prefix, dataUrl) => `${prefix}[image](${dataUrl})`, ); + const markdownLinksMessage = htmlLinkToMarkdown(finalMessage); - return finalMessage + return markdownLinksMessage .replaceAll(/&#x([0-9A-F]+);/gi, (_, hex: string) => String.fromCharCode(parseInt(hex, 16))) .replaceAll('&', '&') .replaceAll('>', '>') diff --git a/GUI/src/components/chat/bot-message.tsx b/GUI/src/components/chat/bot-message.tsx index 068d6fdab..db3441a23 100644 --- a/GUI/src/components/chat/bot-message.tsx +++ b/GUI/src/components/chat/bot-message.tsx @@ -22,7 +22,6 @@ interface ChatMessageProps { const BotMessage = ({ message }: ChatMessageProps) => { const renderContent = useCallback(() => { - if (message.message.startsWith('

')) return message.message.replace('

', '').replace('

', ''); return ; }, [message.message]); diff --git a/GUI/src/components/chat/chat.module.scss b/GUI/src/components/chat/chat.module.scss index 6de0ece85..c93a29663 100644 --- a/GUI/src/components/chat/chat.module.scss +++ b/GUI/src/components/chat/chat.module.scss @@ -141,6 +141,16 @@ .content { border-radius: 6px 48px 48px 29px; background-color: blue; + color: white; + + a { + color: #a8d4ff; + text-decoration: underline; + + &:visited { + color: #c8a8ff; + } + } } .icon { @@ -378,6 +388,16 @@ .main { .content { background-color: get-color(sapphire-blue-12); + color: var(--dark-bg-extra-light); + + a { + color: #a8d4ff; + text-decoration: underline; + + &:visited { + color: #c8a8ff; + } + } } } } diff --git a/GUI/src/services/service-builder.ts b/GUI/src/services/service-builder.ts index 27e706935..713682072 100644 --- a/GUI/src/services/service-builder.ts +++ b/GUI/src/services/service-builder.ts @@ -12,6 +12,7 @@ import { Assign } from 'types/assign'; import { EndpointData } from 'types/endpoint'; import { NodeDataProps } from 'types/service-flow'; import { + decodeHtmlEntities, getLastDigits, isNumericString, removeTrailingUnderscores, @@ -602,11 +603,10 @@ function handleTextField( }); const spacePlaceholder = '___SPACE___'; + const rawMessage = typeof parentNode.data.message === 'string' ? decodeHtmlEntities(parentNode.data.message) : ''; const markdownMessage = htmlToMarkdown .translate( - typeof parentNode.data.message === 'string' - ? parentNode.data.message.replace('{{', '${').replace('}}', '}').replaceAll(' ', spacePlaceholder) - : '', + rawMessage.replace('{{', '${').replace('}}', '}').replaceAll(' ', spacePlaceholder), ) .replaceAll(spacePlaceholder, ' ') .replaceAll(/\\([-~>[\]_*#().!`=<\\])/g, String.raw`\\$1`); diff --git a/GUI/src/utils/string-util.ts b/GUI/src/utils/string-util.ts index 291061fc1..1d7957a97 100644 --- a/GUI/src/utils/string-util.ts +++ b/GUI/src/utils/string-util.ts @@ -151,3 +151,23 @@ export function removeWrapperQuotes(str: string): string { return str.substring(start, end + 1); } + +export function decodeHtmlEntities(value: string): string { + if (typeof value !== 'string' || value.length === 0) return value; + + if (typeof document !== 'undefined') { + const textarea = document.createElement('textarea'); + textarea.innerHTML = value; + return textarea.value; + } + + return value + .replaceAll(/&#x([0-9A-F]+);/gi, (_, hex: string) => String.fromCharCode(parseInt(hex, 16))) + .replaceAll(/&#(\d+);/g, (_, code: string) => String.fromCharCode(parseInt(code, 10))) + .replaceAll('&', '&') + .replaceAll('>', '>') + .replaceAll('<', '<') + .replaceAll('"', '"') + .replaceAll(''', "'") + .replaceAll(''', "'"); +} From 60dd974fffb28a9548712947bd10ace700d47e70 Mon Sep 17 00:00:00 2001 From: ruwinirathnamalala Date: Fri, 20 Mar 2026 19:39:57 +0530 Subject: [PATCH 2/4] PR sonar issues fixed --- GUI/src/components/Flow/NodeTypes/StepNode.tsx | 2 +- GUI/src/components/Markdowify/index.tsx | 6 +++--- GUI/src/components/chat/chat.module.scss | 8 ++++---- GUI/src/utils/string-util.ts | 10 +++++----- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/GUI/src/components/Flow/NodeTypes/StepNode.tsx b/GUI/src/components/Flow/NodeTypes/StepNode.tsx index 9aae6a268..ec2edd0e7 100644 --- a/GUI/src/components/Flow/NodeTypes/StepNode.tsx +++ b/GUI/src/components/Flow/NodeTypes/StepNode.tsx @@ -63,7 +63,7 @@ const StepNode: FC = ({ data }) => { }, [data, endpoints]); useEffect(() => { - void updateIsTestedAndPassed(); + updateIsTestedAndPassed(); }, [updateIsTestedAndPassed]); return ( diff --git a/GUI/src/components/Markdowify/index.tsx b/GUI/src/components/Markdowify/index.tsx index 7ff568a4e..9feed90b7 100644 --- a/GUI/src/components/Markdowify/index.tsx +++ b/GUI/src/components/Markdowify/index.tsx @@ -74,7 +74,7 @@ const hasSpecialFormat = (m: string) => m.includes('\n\n') && m.indexOf('.') > 0 const htmlLinkToMarkdown = (value: string): string => value.replaceAll( - /]*href\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))[^>]*>([\s\S]*?)<\/a>/gi, + /]*href\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))[^>]*>((?:[^<]|<(?!\/a>))*)<\/a>/gi, (_, href1: string, href2: string, href3: string, label: string) => { const href = (href1 ?? href2 ?? href3 ?? '').trim(); const text = sanitizeHtml(label ?? '', { allowedTags: [], allowedAttributes: {} }).trim() || href; @@ -100,7 +100,7 @@ function formatMessage(message?: string): string { const markdownLinksMessage = htmlLinkToMarkdown(finalMessage); return markdownLinksMessage - .replaceAll(/&#x([0-9A-F]+);/gi, (_, hex: string) => String.fromCharCode(parseInt(hex, 16))) + .replaceAll(/&#x([0-9A-F]+);/gi, (_, hex: string) => String.fromCodePoint(Number.parseInt(hex, 16))) .replaceAll('&', '&') .replaceAll('>', '>') .replaceAll('<', '<') @@ -116,7 +116,7 @@ function formatMessage(message?: string): string { return `${prefix}${year}. `; } } - return `${prefix}${year}\\. `; + return String.raw`${prefix}${year}\. `; }) .replaceAll(/(?<=\n)\d+\.\s/g, hasSpecialFormat(finalMessage) ? '\n\n$&' : '$&') .replaceAll(/^(\s+)/g, (match) => match.replaceAll(' ', ' ')); diff --git a/GUI/src/components/chat/chat.module.scss b/GUI/src/components/chat/chat.module.scss index c93a29663..ba7fff03f 100644 --- a/GUI/src/components/chat/chat.module.scss +++ b/GUI/src/components/chat/chat.module.scss @@ -233,7 +233,7 @@ pointer-events: none; user-select: none; background-color: #f5f5f5; - color: #999; + color: #696969; } } @@ -278,17 +278,17 @@ font-weight: bold; &.success { - color: rgb(0, 138, 0); + color: rgb(0, 100, 0); background: #0f02; } &.error { - color: rgb(170, 0, 0); + color: rgb(153, 0, 0); background: #f002; } &.info { - color: rgb(0, 0, 203); + color: rgb(0, 0, 153); background: #00f2; } diff --git a/GUI/src/utils/string-util.ts b/GUI/src/utils/string-util.ts index 1d7957a97..007fc034a 100644 --- a/GUI/src/utils/string-util.ts +++ b/GUI/src/utils/string-util.ts @@ -24,7 +24,7 @@ export const templateToString = (value: string | number) => { }; export const toSnakeCase = (value: string) => { - return value.toLowerCase().trim().replace(/\s+/g, '_').replace(/-+/g, '_').replace(/_+/g, '_'); + return value.toLowerCase().trim().replaceAll(/\s+/g, '_').replaceAll(/-+/g, '_').replaceAll(/_+/g, '_'); }; export const fromSnakeCase = (value: string) => { @@ -96,7 +96,7 @@ export function removeNestedTemplates(str: string): string { changed = false; iterationCount++; - str = str.replace( + str = str.replaceAll( /\$\{([^${}]*)\$\{([^}]*)\}([^}]*)\}/g, (match: string, p1: string, p2: string, p3: string): string => { changed = true; @@ -116,7 +116,7 @@ export function removeNestedTemplates(str: string): string { changed = false; iterationCount++; - str = str.replace( + str = str.replaceAll( /\$\{([^{}]*)\{([^}]*)\}([^}]*)\}/g, (match: string, p1: string, p2: string, p3: string): string => { changed = true; @@ -162,8 +162,8 @@ export function decodeHtmlEntities(value: string): string { } return value - .replaceAll(/&#x([0-9A-F]+);/gi, (_, hex: string) => String.fromCharCode(parseInt(hex, 16))) - .replaceAll(/&#(\d+);/g, (_, code: string) => String.fromCharCode(parseInt(code, 10))) + .replaceAll(/&#x([0-9A-F]+);/gi, (_, hex: string) => String.fromCodePoint(Number.parseInt(hex, 16))) + .replaceAll(/&#(\d+);/g, (_, code: string) => String.fromCodePoint(parseInt(code, 10))) .replaceAll('&', '&') .replaceAll('>', '>') .replaceAll('<', '<') From 53e39eb5670c9ebec5b46dce7e04140e53afe4d6 Mon Sep 17 00:00:00 2001 From: ruwinirathnamalala Date: Fri, 20 Mar 2026 20:39:16 +0530 Subject: [PATCH 3/4] PR sonar issues fixed --- GUI/src/components/chat/chat.module.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GUI/src/components/chat/chat.module.scss b/GUI/src/components/chat/chat.module.scss index ba7fff03f..ae998a29e 100644 --- a/GUI/src/components/chat/chat.module.scss +++ b/GUI/src/components/chat/chat.module.scss @@ -283,12 +283,12 @@ } &.error { - color: rgb(153, 0, 0); + color: rgb(80, 0, 0); background: #f002; } &.info { - color: rgb(0, 0, 153); + color: rgb(0, 0, 80); background: #00f2; } From 4f72df388cd093834239d87df5ed7e37117495d8 Mon Sep 17 00:00:00 2001 From: ruwinirathnamalala Date: Fri, 20 Mar 2026 20:52:49 +0530 Subject: [PATCH 4/4] PR sonar issues fixed --- GUI/src/utils/string-util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GUI/src/utils/string-util.ts b/GUI/src/utils/string-util.ts index 007fc034a..92093dc7f 100644 --- a/GUI/src/utils/string-util.ts +++ b/GUI/src/utils/string-util.ts @@ -163,7 +163,7 @@ export function decodeHtmlEntities(value: string): string { return value .replaceAll(/&#x([0-9A-F]+);/gi, (_, hex: string) => String.fromCodePoint(Number.parseInt(hex, 16))) - .replaceAll(/&#(\d+);/g, (_, code: string) => String.fromCodePoint(parseInt(code, 10))) + .replaceAll(/&#(\d+);/g, (_, code: string) => String.fromCodePoint(Number.parseInt(code, 10))) .replaceAll('&', '&') .replaceAll('>', '>') .replaceAll('<', '<')