diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 8ecf27b..5b34b97 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -7,7 +7,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
- node: ['16.x', '20.x', '23.x']
+ node: ['20.x', '23.x']
os: ['ubuntu-latest']
steps:
diff --git a/package.json b/package.json
index 38b0df4..310b99f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@chatwoot/utils",
- "version": "0.0.50",
+ "version": "0.0.51",
"description": "Chatwoot utils",
"private": false,
"license": "MIT",
diff --git a/src/index.ts b/src/index.ts
index 08294f5..ca14ac0 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -16,7 +16,7 @@ import { toURL, isSameHost, isValidDomain } from './url';
import { getRecipients } from './email';
-import { parseBoolean } from './string';
+import { parseBoolean, sanitizeTextForRender } from './string';
import {
sortAsc,
quantile,
@@ -62,6 +62,7 @@ export {
parseBoolean,
quantile,
replaceVariablesInMessage,
+ sanitizeTextForRender,
sortAsc,
splitName,
toURL,
diff --git a/src/string.ts b/src/string.ts
index 1971547..2799528 100644
--- a/src/string.ts
+++ b/src/string.ts
@@ -3,7 +3,6 @@
* @param {string | number} candidate - The string boolean value to be parsed
* @return {boolean} - The parsed boolean value
*/
-
export function parseBoolean(candidate: string | number) {
try {
// lowercase the string, so TRUE becomes true
@@ -16,3 +15,65 @@ export function parseBoolean(candidate: string | number) {
return false;
}
}
+
+/**
+ * Sanitizes text for safe HTML rendering by escaping potentially dangerous characters
+ * while preserving valid HTML tags.
+ *
+ * This function performs the following transformations:
+ * - Converts newline characters (\n) to HTML line breaks (
)
+ * - Escapes stray '<' characters that are not part of valid HTML tags (e.g., "x < 5" → "x < 5")
+ * - Escapes stray '>' characters that are not part of valid HTML tags (e.g., "x > 5" → "x > 5")
+ * - Preserves valid HTML tags and their attributes (e.g.,
')).toBe(
+ '
'
+ );
+ });
+
+ it('should preserve nested HTML tags', () => {
+ expect(sanitizeTextForRender('x > 5
and y < 10')).toBe( + 'x > 5
and y < 10' + ); + }); + + it('should handle edge cases with malformed HTML-like content', () => { + expect(sanitizeTextForRender('<Best regards,
\nSales Team`; + const expected = `Hello,Best regards,
Previous reply`; + const expected = `Original message:
Previous reply`; + expect(sanitizeTextForRender(quoted)).toBe(expected); + }); + + it('should handle self-closing tags correctly', () => { + expect(sanitizeTextForRender('
')).toBe(
+ '
'
+ );
+ expect(sanitizeTextForRender('')).toBe(
+ ''
+ );
+ expect(sanitizeTextForRender('
'
+ )
+ ).toBe(
+ '
'
+ );
+ expect(
+ sanitizeTextForRender(
+ 'Profile'
+ )
+ ).toBe(
+ 'Profile'
+ );
+ expect(
+ sanitizeTextForRender(
+ ''
+ )
+ ).toBe(
+ ''
+ );
+ });
+});