andy-widget-react 2.3.1 → 2.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "andy-widget-react",
3
- "version": "2.3.1",
3
+ "version": "2.3.2",
4
4
  "description": "Official React Widget for embeddable Andy agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -67,6 +67,6 @@
67
67
  "react-dom": "^18.0.0 || ^19.0.0"
68
68
  },
69
69
  "dependencies": {
70
- "andy-widget-core": "2.3.1"
70
+ "andy-widget-core": "2.3.2"
71
71
  }
72
72
  }
package/src/AndyChat.tsx CHANGED
@@ -12,6 +12,7 @@ import {
12
12
  getHostClasses,
13
13
  createBrowserHandoff,
14
14
  parseMarkdown,
15
+ escapeHtml,
15
16
  CHAT_BUBBLE_DEFAULT_ICON,
16
17
  CHAT_BUBBLE_DEFAULT_ICON_URL,
17
18
  SVG_ICONS,
@@ -713,7 +714,7 @@ const SalesAndyChat: React.FC<SalesAndyChatProps> = ({
713
714
  bubble.setAttribute('aria-label', strings.openChat)
714
715
 
715
716
  const tooltipHTML = tooltip
716
- ? `<span class="andy-chat-bubble__tooltip" role="tooltip">${tooltip}</span>`
717
+ ? `<span class="andy-chat-bubble__tooltip" role="tooltip">${escapeHtml(tooltip)}</span>`
717
718
  : ''
718
719
  bubble.innerHTML = `${CHAT_BUBBLE_DEFAULT_ICON}${tooltipHTML}`
719
720
  shadowRef.current.appendChild(bubble)
@@ -744,7 +745,7 @@ const SalesAndyChat: React.FC<SalesAndyChatProps> = ({
744
745
 
745
746
  const tooltipHTML =
746
747
  !isOpen && tooltip && !isPillStyle
747
- ? `<span class="andy-chat-bubble__tooltip" role="tooltip">${tooltip}</span>`
748
+ ? `<span class="andy-chat-bubble__tooltip" role="tooltip">${escapeHtml(tooltip)}</span>`
748
749
  : ''
749
750
 
750
751
  const configUseBrandColor = config?.useBrandColor ?? true
@@ -762,12 +763,12 @@ const SalesAndyChat: React.FC<SalesAndyChatProps> = ({
762
763
  bubble.innerHTML = chevronWithStyle
763
764
  } else if (isPillStyle && displayLabel) {
764
765
  bubble.innerHTML = `
765
- <span class="andy-chat-bubble__label">${displayLabel}</span>
766
- <img src="${bubbleLogoUrl}" alt="Chat" class="andy-chat-bubble__icon--pill" style="${darkModeIconStyle}" />
766
+ <span class="andy-chat-bubble__label">${escapeHtml(displayLabel)}</span>
767
+ <img src="${escapeHtml(bubbleLogoUrl)}" alt="Chat" class="andy-chat-bubble__icon--pill" style="${darkModeIconStyle}" />
767
768
  `
768
769
  } else {
769
770
  if (config?.logoUrl) {
770
- bubble.innerHTML = `<img src="${config.logoUrl}" alt="Chatbot Logo" class="andy-chat-bubble__logo" style="${darkModeIconStyle}" />${tooltipHTML}`
771
+ bubble.innerHTML = `<img src="${escapeHtml(config.logoUrl)}" alt="Chatbot Logo" class="andy-chat-bubble__logo" style="${darkModeIconStyle}" />${tooltipHTML}`
771
772
  } else {
772
773
  const defaultIconWithStyle = CHAT_BUBBLE_DEFAULT_ICON.replace(
773
774
  'class="andy-chat-bubble__icon"',
@@ -1,6 +1,6 @@
1
1
  import React, { useEffect, useRef, useState } from 'react'
2
2
  import { createPortal } from 'react-dom'
3
- import { generateDesignTokenVariablesString, getContrastTextColor, getSupportStylesheet, type SupportTicket } from 'andy-widget-core'
3
+ import { generateDesignTokenVariablesString, getContrastTextColor, getSupportStylesheet, sanitizeUrl, type SupportTicket } from 'andy-widget-core'
4
4
  import type { SupportAndyChatProps } from './types'
5
5
  import { useSupportChat } from './hooks/useSupportChat'
6
6
 
@@ -96,7 +96,7 @@ const SupportPanel: React.FC<SupportAndyChatProps> = ({ support, lang, orientati
96
96
  </div>
97
97
  {state.view === 'conversation' && <form className='andy-support__composer' onSubmit={event => { event.preventDefault(); send() }}><input aria-label={strings.placeholder} placeholder={strings.placeholder} value={draft} maxLength={10000} disabled={!state.config} onChange={event => setDraft(event.target.value)} /><button type='submit' aria-label={strings.send} disabled={state.busy || !state.config || !draft.trim()}>↑</button></form>}
98
98
  {state.view === 'request' && state.ticket && (state.ticket.status !== 'resolved' || reopening) && <form className='andy-support__followup' onSubmit={event => { event.preventDefault(); void followup() }}><label>{reopening ? strings.reopening : strings.reply}<textarea rows={3} maxLength={20000} required value={state.followup?.body ?? replyDraft} readOnly={!!state.followup} disabled={state.busy} onChange={event => setReplyDraft(event.target.value)} /></label><button type='submit' className='andy-support__primary' disabled={state.busy || !replyDraft.trim()}>{state.busy ? strings.loading : reopening ? strings.reopen : strings.send}</button></form>}
99
- <footer className='andy-support__footer'><span>{strings.powered}</span>{state.config?.branding.footer.map(link => <a key={link.url} href={link.url} target='_blank' rel='noopener noreferrer'>{link.label}</a>)}</footer>
99
+ <footer className='andy-support__footer'><span>{strings.powered}</span>{state.config?.branding.footer.map(link => <a key={link.url} href={sanitizeUrl(link.url)} target='_blank' rel='noopener noreferrer'>{link.label}</a>)}</footer>
100
100
  </section>}
101
101
  </div>, portal)}
102
102
  </div>