@microsoft/omnichannel-chat-widget 1.8.4-main.3eccac9 → 1.8.4-main.40cbfab

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.
Files changed (44) hide show
  1. package/README.md +12 -3
  2. package/lib/cjs/common/Constants.js +2 -0
  3. package/lib/cjs/common/telemetry/TelemetryConstants.js +8 -0
  4. package/lib/cjs/common/utils/xssUtils.js +23 -51
  5. package/lib/cjs/common/utils.js +1 -1
  6. package/lib/cjs/components/livechatwidget/common/ActivitySubscriber/BotAuthActivitySubscriber.js +23 -6
  7. package/lib/cjs/components/livechatwidget/common/ChatWidgetEvents.js +1 -0
  8. package/lib/cjs/components/livechatwidget/common/PersistentConversationHandler.js +9 -0
  9. package/lib/cjs/components/livechatwidget/common/createAdapter.js +3 -2
  10. package/lib/cjs/components/livechatwidget/livechatwidgetstateful/LiveChatWidgetStateful.js +29 -1
  11. package/lib/cjs/components/proactivechatpanestateful/ProactiveChatPaneStateful.js +0 -1
  12. package/lib/cjs/components/webchatcontainerstateful/WebChatContainerStateful.js +11 -3
  13. package/lib/cjs/components/webchatcontainerstateful/common/activityConverters/convertPersistentChatHistoryMessageToActivity.js +109 -2
  14. package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/WebChatEventSubscribers.js +13 -2
  15. package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/activities/LazyLoadActivity.js +160 -167
  16. package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/activityMiddleware.js +1 -1
  17. package/lib/cjs/controller/componentController.js +13 -1
  18. package/lib/cjs/plugins/newMessageEventHandler.js +20 -3
  19. package/lib/esm/common/Constants.js +2 -0
  20. package/lib/esm/common/telemetry/TelemetryConstants.js +8 -0
  21. package/lib/esm/common/utils/xssUtils.js +23 -51
  22. package/lib/esm/common/utils.js +1 -1
  23. package/lib/esm/components/livechatwidget/common/ActivitySubscriber/BotAuthActivitySubscriber.js +23 -6
  24. package/lib/esm/components/livechatwidget/common/ChatWidgetEvents.js +1 -0
  25. package/lib/esm/components/livechatwidget/common/PersistentConversationHandler.js +9 -0
  26. package/lib/esm/components/livechatwidget/common/createAdapter.js +3 -2
  27. package/lib/esm/components/livechatwidget/livechatwidgetstateful/LiveChatWidgetStateful.js +29 -1
  28. package/lib/esm/components/proactivechatpanestateful/ProactiveChatPaneStateful.js +1 -2
  29. package/lib/esm/components/webchatcontainerstateful/WebChatContainerStateful.js +11 -3
  30. package/lib/esm/components/webchatcontainerstateful/common/activityConverters/convertPersistentChatHistoryMessageToActivity.js +109 -2
  31. package/lib/esm/components/webchatcontainerstateful/webchatcontroller/WebChatEventSubscribers.js +13 -2
  32. package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/activities/LazyLoadActivity.js +160 -171
  33. package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/activityMiddleware.js +1 -1
  34. package/lib/esm/controller/componentController.js +13 -1
  35. package/lib/esm/plugins/newMessageEventHandler.js +20 -3
  36. package/lib/types/common/Constants.d.ts +2 -0
  37. package/lib/types/common/telemetry/TelemetryConstants.d.ts +7 -1
  38. package/lib/types/common/utils/xssUtils.d.ts +5 -21
  39. package/lib/types/components/livechatwidget/common/ActivitySubscriber/BotAuthActivitySubscriber.d.ts +1 -0
  40. package/lib/types/components/livechatwidget/common/ChatWidgetEvents.d.ts +2 -1
  41. package/lib/types/components/livechatwidget/interfaces/IBotAuthActivitySubscriberOptionalParams.d.ts +1 -0
  42. package/lib/types/components/webchatcontainerstateful/interfaces/IBotAuthConfig.d.ts +7 -0
  43. package/lib/types/components/webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/activities/LazyLoadActivity.d.ts +14 -38
  44. package/package.json +14 -5
package/README.md CHANGED
@@ -177,7 +177,7 @@ Header's and Footer's child components consist of three parts:
177
177
  1. "middleGroup" - adding child components in the middle of the Header/Footer
178
178
  1. "rightGroup" - adding child components at the right of the Header/Footer
179
179
 
180
- By default Header has the header icon and title on the left and minimize and close buttons on the right, and Footer has Download Transcript and Email Transcript buttons on the left and audio notification button on the right. These components can be overriden with [ComponentOverrides](#ComponentOverrides). In addition, other custom child components can be added to both Header and Footer by creating custom react nodes and adding them to attributes "leftGroup", "middleGroup" or "rightGroup" of "controlProps".
180
+ By default Header has the header icon and title on the left and minimize and close buttons on the right, and Footer has Download Transcript and Email Transcript buttons on the left and audio notification button on the right. These components can be overriden with [ComponentOverrides](#componentoverrides). In addition, other custom child components can be added to both Header and Footer by creating custom react nodes and adding them to attributes "leftGroup", "middleGroup" or "rightGroup" of "controlProps".
181
181
 
182
182
  ```js
183
183
  const buttonStyleProps: IButtonStyles = {
@@ -224,8 +224,10 @@ const customizedFooterProp: IFooterProps = {
224
224
  > :pushpin: Note that [WebChat hooks](https://github.com/microsoft/BotFramework-WebChat/blob/main/docs/HOOKS.md) can also be used in any custom components.
225
225
 
226
226
  #### Bidirectional Custom Events
227
+
227
228
  - Sending events from a hosting web page to bots/agents
228
- - Register a function to post event
229
+ - Register a function to post event
230
+
229
231
  ```js
230
232
  //define sendCustomEvent function
231
233
  const sendCustomEvent = (payload) => {
@@ -253,7 +255,9 @@ const customizedFooterProp: IFooterProps = {
253
255
  }
254
256
  })
255
257
  ```
256
- - Receiving events from bots/agents
258
+
259
+ - Receiving events from bots/agents
260
+
257
261
  ```js
258
262
  //define setOnCustomEvent function
259
263
  const setOnCustomEvent = (callback) => {
@@ -269,8 +273,10 @@ const customizedFooterProp: IFooterProps = {
269
273
  ```
270
274
 
271
275
  #### Trigger initiateEndChat event
276
+
272
277
  Customer can trigger the initiateEndChat event via BroadcastService to end a chat session.
273
278
  When needed, the payload below could be triggered:
279
+
274
280
  ```js
275
281
  const endChatEvent = {
276
282
  eventName: "InitiateEndChat",
@@ -283,18 +289,21 @@ BroadcastService.postMessage(endChatEvent);
283
289
 
284
290
  The payload of the event is optional, only needed when force closing of a persistent chat session is not required.
285
291
  When chat widget receives the event without any payload, it will:
292
+
286
293
  1. set the widget to closed state, the widget panel will be minimized. Post chat survey will not be displayed.
287
294
  2. trigger a sessionclose service network request to OmniChannel services.
288
295
 
289
296
  If skipSessionCloseForPersistentChat is set to true. The session close network request will not be triggered, instead, if postChat survey is available, post chat survey will be displayed.
290
297
 
291
298
  After successfully processed initiateEndChat event. The CloseChat event is broadcasted.
299
+
292
300
  ```js
293
301
  BroadcastService.getMessageByEventName("CloseChat").subscribe(async (msg) => {
294
302
  console.log("close chat received: ", msg);
295
303
  //more actions to unmount component and resources
296
304
  })
297
305
  ```
306
+
298
307
  ## See Also
299
308
 
300
309
  [Customizations Dev Guide](https://github.com/microsoft/omnichannel-chat-widget/blob/main/docs/customizations/getstarted.md)\
@@ -82,6 +82,8 @@ _defineProperty(Constants, "audioMediaRegex", /(\.)(aac|aiff|alac|amr|flac|mp2|m
82
82
  _defineProperty(Constants, "videoMediaRegex", /(\.)(avchd|avi|flv|mpe|mpeg|mpg|mpv|mp4|m4p|m4v|mov|qt|swf|webm|wmv)$/i);
83
83
  _defineProperty(Constants, "chromeSupportedInlineMediaRegex", /(\.)(aac|mp3|wav|mp4)$/i);
84
84
  _defineProperty(Constants, "firefoxSupportedInlineMediaRegex", /(\.)(aac|flac|mp3|wav|mp4|mov)$/i);
85
+ _defineProperty(Constants, "AdaptiveCardType", "adaptivecard");
86
+ _defineProperty(Constants, "SuggestedActionsType", "suggestedactions");
85
87
  // calling container event names
86
88
  _defineProperty(Constants, "CallAdded", "callAdded");
87
89
  _defineProperty(Constants, "LocalVideoStreamAdded", "localVideoStreamAdded");
@@ -318,10 +318,16 @@ exports.TelemetryEvent = TelemetryEvent;
318
318
  TelemetryEvent["LCWLazyLoadNoMoreHistory"] = "LCWLazyLoadNoMoreHistory";
319
319
  TelemetryEvent["LCWLazyLoadHistoryError"] = "LCWLazyLoadHistoryError";
320
320
  TelemetryEvent["LCWLazyLoadDestroyed"] = "LCWLazyLoadDestroyed";
321
+ TelemetryEvent["LCWLazyLoadTriggerFired"] = "LCWLazyLoadTriggerFired";
322
+ TelemetryEvent["LCWLazyLoadBatchReceived"] = "LCWLazyLoadBatchReceived";
323
+ TelemetryEvent["LCWLazyLoadInitialLoadComplete"] = "LCWLazyLoadInitialLoadComplete";
324
+ TelemetryEvent["LCWLazyLoadScrollAnchorApplied"] = "LCWLazyLoadScrollAnchorApplied";
321
325
  TelemetryEvent["SecureEventBusUnauthorizedDispatch"] = "SecureEventBusUnauthorizedDispatch";
322
326
  TelemetryEvent["SecureEventBusListenerError"] = "SecureEventBusListenerError";
323
327
  TelemetryEvent["SecureEventBusDispatchError"] = "SecureEventBusDispatchError";
324
328
  TelemetryEvent["StartChatComplete"] = "StartChatComplete";
329
+ TelemetryEvent["AgentJoinedConversation"] = "AgentJoinedConversation";
330
+ TelemetryEvent["BrowserTabHidden"] = "BrowserTabHidden";
325
331
  })(TelemetryEvent || (exports.TelemetryEvent = TelemetryEvent = {}));
326
332
  let TelemetryConstants = /*#__PURE__*/function () {
327
333
  function TelemetryConstants() {
@@ -392,6 +398,8 @@ let TelemetryConstants = /*#__PURE__*/function () {
392
398
  case TelemetryEvent.SecureEventBusUnauthorizedDispatch:
393
399
  case TelemetryEvent.SecureEventBusListenerError:
394
400
  case TelemetryEvent.SecureEventBusDispatchError:
401
+ case TelemetryEvent.AgentJoinedConversation:
402
+ case TelemetryEvent.BrowserTabHidden:
395
403
  return ScenarioType.ACTIONS;
396
404
  case TelemetryEvent.StartChatSDKCall:
397
405
  case TelemetryEvent.StartChatEventReceived:
@@ -7,70 +7,42 @@ exports.detectAndCleanXSS = void 0;
7
7
  var _dompurify = _interopRequireDefault(require("dompurify"));
8
8
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
9
9
  /**
10
- * Detects potential Cross-Site Scripting (XSS) attacks in text input and sanitizes the content.
10
+ * Sanitizes text input and detects XSS attack patterns.
11
11
  *
12
- * This function performs comprehensive XSS detection using pattern matching for common attack vectors
13
- * and then sanitizes the input using DOMPurify with strict configuration. It's designed to protect
14
- * against various XSS techniques including script injection, event handler injection, style-based
15
- * attacks, and encoded payloads.
12
+ * Sanitizes first with DOMPurify, then checks for malicious patterns in both
13
+ * the original and sanitized text to catch mutation XSS attacks.
16
14
  *
17
- * Security patterns detected:
18
- * - JavaScript protocol URLs (javascript:)
19
- * - HTML event handlers (onmouseover, onclick, etc.)
20
- * - Script tags (<script>)
21
- * - CSS expression() functions
22
- * - CSS url() functions
23
- * - Position-based CSS attacks (position: fixed/absolute)
24
- * - VBScript protocol URLs
25
- * - Data URLs with HTML content
26
- * - Fragment identifiers with escaped quotes
27
- * - HTML entity-encoded angle brackets
28
- *
29
- * @param text - The input text to be analyzed and sanitized
30
- * @returns An object containing:
31
- * - cleanText: The sanitized version of the input text with all HTML tags and attributes removed
32
- * - isXSSDetected: Boolean flag indicating whether potential XSS patterns were found in the original text
15
+ * @param text - Input text to sanitize
16
+ * @returns Object with cleanText (sanitized) and isXSSDetected flag
33
17
  */
34
18
  const detectAndCleanXSS = text => {
35
- // Comprehensive array of regular expressions to detect common XSS attack patterns
36
- const xssPatterns = [/javascript\s*:/gi,
37
- // JavaScript protocol URLs (with optional spaces)
38
- /vbscript\s*:/gi,
39
- // VBScript protocol URLs (with optional spaces)
40
- /on\w+\s*=/gi,
41
- // HTML event handlers (onmouseover, onclick, onload, etc.)
42
- /<\s*script/gi,
43
- // Script tag opening (with optional spaces)
44
- /expression\s*\(/gi,
45
- // CSS expression() function (IE-specific)
46
- /url\s*\(/gi,
47
- // CSS url() function
48
- /style\s*=.*position\s*:\s*fixed/gi,
49
- // CSS position fixed attacks
50
- /style\s*=.*position\s*:\s*absolute/gi,
51
- // CSS position absolute attacks
52
- /data\s*:\s*text\s*\/\s*html/gi,
53
- // Data URLs containing HTML
54
- /#.*\\"/gi,
55
- // Fragment identifiers with escaped quotes
56
- /&gt;.*&lt;/gi // HTML entity-encoded angle brackets indicating tag structure
57
- ];
58
-
59
- // Check if any XSS patterns are detected in the input text
60
- const isXSSDetected = xssPatterns.some(pattern => pattern.test(text));
61
-
62
- // Clean the text using DOMPurify with strict config
19
+ // Sanitize first to prevent mutation XSS (e.g., "s<iframe></iframe>tyle" "style")
63
20
  const cleanText = _dompurify.default.sanitize(text, {
64
21
  ALLOWED_TAGS: [],
65
- // No HTML tags allowed in title
66
22
  ALLOWED_ATTR: [],
67
23
  KEEP_CONTENT: true,
68
- // Keep text content
69
24
  ALLOW_DATA_ATTR: false,
70
25
  ALLOW_UNKNOWN_PROTOCOLS: false,
71
26
  SANITIZE_DOM: true,
72
27
  FORCE_BODY: false
73
28
  });
29
+ const contentChanged = text !== cleanText;
30
+
31
+ // Non-global regex patterns to avoid stateful .test() issues
32
+ const xssPatterns = [/javascript\s*:/i, /vbscript\s*:/i, /on\w+\s*=/i,
33
+ // Event handlers
34
+ /<\s*script/i, /<\s*iframe/i, /<\s*object/i, /<\s*embed/i, /<\s*svg/i, /expression\s*\(/i,
35
+ // IE CSS expressions
36
+ /style\s*=.*position\s*:\s*(fixed|absolute)/i, /data\s*:\s*text\s*\/\s*html/i, /#.*\\"/i, /&(lt|gt|#x3c|#60|#x3e|#62);/i,
37
+ // HTML entities
38
+ /&#x?[0-9a-f]+;.*</i, /\u003c.*\u003e/i,
39
+ // Unicode escapes
40
+ /src\s*=\s*["']?\s*javascript:/i, /href\s*=\s*["']?\s*javascript:/i];
41
+ const hasXSSPattern = xssPatterns.some(pattern => {
42
+ return pattern.test(text) || pattern.test(cleanText);
43
+ });
44
+ const hasHTMLStructure = /<[^>]+>/.test(text) && !/<[^>]+>/.test(cleanText);
45
+ const isXSSDetected = contentChanged || hasXSSPattern || hasHTMLStructure;
74
46
  return {
75
47
  cleanText,
76
48
  isXSSDetected
@@ -480,7 +480,7 @@ const setOcUserAgent = chatSDK => {
480
480
  // eslint-disable-line @typescript-eslint/no-explicit-any
481
481
  if ((_chatSDK$OCClient = chatSDK.OCClient) !== null && _chatSDK$OCClient !== void 0 && _chatSDK$OCClient.ocUserAgent && !((_chatSDK$OCClient2 = chatSDK.OCClient) !== null && _chatSDK$OCClient2 !== void 0 && _chatSDK$OCClient2.ocUserAgent.join(" ").includes("omnichannel-chat-widget/"))) {
482
482
  try {
483
- const version = require("../../../package.json").version; // eslint-disable-line @typescript-eslint/no-var-requires
483
+ const version = require("../../package.json").version; // eslint-disable-line @typescript-eslint/no-var-requires
484
484
  const userAgent = `omnichannel-chat-widget/${version}`;
485
485
  chatSDK.OCClient.ocUserAgent = [userAgent, ...chatSDK.OCClient.ocUserAgent];
486
486
  } catch (error) {
@@ -44,7 +44,7 @@ const extractSasUrl = async attachment => {
44
44
  }
45
45
  return sasUrl;
46
46
  };
47
- const fetchBotAuthConfig = async (retries, interval) => {
47
+ const fetchBotAuthConfig = async (retries, interval, fallback) => {
48
48
  _TelemetryHelper.TelemetryHelper.logLoadingEvent(_TelemetryConstants.LogLevel.INFO, {
49
49
  Event: _TelemetryConstants.TelemetryEvent.SetBotAuthProviderFetchConfig
50
50
  });
@@ -60,13 +60,23 @@ const fetchBotAuthConfig = async (retries, interval) => {
60
60
  });
61
61
  if (retries === 1) {
62
62
  // Base Case
63
- throw new Error();
63
+ if (response !== undefined) {
64
+ return response;
65
+ }
66
+ if (fallback !== undefined) {
67
+ _TelemetryHelper.TelemetryHelper.logLoadingEvent(_TelemetryConstants.LogLevel.WARN, {
68
+ Event: _TelemetryConstants.TelemetryEvent.SetBotAuthProviderNotFound,
69
+ Description: `Failed to fetch bot auth config after maximum retries. Using fallback value: ${fallback}`
70
+ });
71
+ return fallback;
72
+ }
73
+ throw new Error("Failed to fetch bot auth config after maximum retries");
64
74
  }
65
75
  await delay(interval);
66
76
  if (response !== undefined) {
67
77
  return response;
68
78
  }
69
- return await fetchBotAuthConfig(--retries, interval);
79
+ return await fetchBotAuthConfig(--retries, interval, fallback);
70
80
  };
71
81
  let BotAuthActivitySubscriber = /*#__PURE__*/function () {
72
82
  function BotAuthActivitySubscriber() {
@@ -76,6 +86,7 @@ let BotAuthActivitySubscriber = /*#__PURE__*/function () {
76
86
  _defineProperty(this, "signInCardSeen", void 0);
77
87
  _defineProperty(this, "fetchBotAuthConfigRetries", void 0);
78
88
  _defineProperty(this, "fetchBotAuthConfigRetryInterval", void 0);
89
+ _defineProperty(this, "fallbackShowSignInCard", void 0);
79
90
  this.signInCardSeen = new Set();
80
91
  this.fetchBotAuthConfigRetries = 3;
81
92
  this.fetchBotAuthConfigRetryInterval = 1000;
@@ -85,6 +96,11 @@ let BotAuthActivitySubscriber = /*#__PURE__*/function () {
85
96
  if (optionalParams.fetchBotAuthConfigRetryInterval) {
86
97
  this.fetchBotAuthConfigRetryInterval = optionalParams.fetchBotAuthConfigRetryInterval;
87
98
  }
99
+ this.fallbackShowSignInCard = optionalParams.fallbackShowSignInCard;
100
+ _TelemetryHelper.TelemetryHelper.logLoadingEvent(_TelemetryConstants.LogLevel.INFO, {
101
+ Event: _TelemetryConstants.TelemetryEvent.SetBotAuthProviderFetchConfig,
102
+ Description: `BotAuthActivitySubscriber initialized with fallbackShowSignInCard: ${optionalParams.fallbackShowSignInCard}`
103
+ });
88
104
  }
89
105
  _createClass(BotAuthActivitySubscriber, [{
90
106
  key: "applicable",
@@ -129,7 +145,7 @@ let BotAuthActivitySubscriber = /*#__PURE__*/function () {
129
145
  _omnichannelChatComponents.BroadcastService.postMessage(event);
130
146
  }
131
147
  try {
132
- const response = await fetchBotAuthConfig(this.fetchBotAuthConfigRetries, this.fetchBotAuthConfigRetryInterval);
148
+ const response = await fetchBotAuthConfig(this.fetchBotAuthConfigRetries, this.fetchBotAuthConfigRetryInterval, this.fallbackShowSignInCard);
133
149
  if (response === false) {
134
150
  _TelemetryHelper.TelemetryHelper.logLoadingEvent(_TelemetryConstants.LogLevel.INFO, {
135
151
  Event: _TelemetryConstants.TelemetryEvent.SetBotAuthProviderHideCard
@@ -140,9 +156,10 @@ let BotAuthActivitySubscriber = /*#__PURE__*/function () {
140
156
  });
141
157
  return activity;
142
158
  }
143
- } catch {
159
+ } catch (e) {
144
160
  _TelemetryHelper.TelemetryHelper.logLoadingEvent(_TelemetryConstants.LogLevel.INFO, {
145
- Event: _TelemetryConstants.TelemetryEvent.SetBotAuthProviderNotFound
161
+ Event: _TelemetryConstants.TelemetryEvent.SetBotAuthProviderNotFound,
162
+ ExceptionDetails: e instanceof Error ? e.message : JSON.stringify(e)
146
163
  });
147
164
  //this is to ensure listener continues waiting for response
148
165
  if (this.signInCardSeen.has(signInId)) {
@@ -10,6 +10,7 @@ var ChatWidgetEvents;
10
10
  ChatWidgetEvents["FETCH_PERSISTENT_CHAT_HISTORY"] = "CHAT_WIDGET/FETCH_PERSISTENT_CHAT_HISTORY";
11
11
  ChatWidgetEvents["NO_MORE_HISTORY_AVAILABLE"] = "CHAT_WIDGET/NO_MORE_HISTORY_AVAILABLE";
12
12
  ChatWidgetEvents["HISTORY_LOAD_ERROR"] = "CHAT_WIDGET/HISTORY_LOAD_ERROR";
13
+ ChatWidgetEvents["HISTORY_BATCH_LOADED"] = "CHAT_WIDGET/HISTORY_BATCH_LOADED";
13
14
  })(ChatWidgetEvents || (ChatWidgetEvents = {}));
14
15
  var _default = ChatWidgetEvents;
15
16
  exports.default = _default;
@@ -133,6 +133,12 @@ let PersistentConversationHandler = /*#__PURE__*/function () {
133
133
  }
134
134
  const messagesDescOrder = (_ref = [...messages]) === null || _ref === void 0 ? void 0 : _ref.reverse();
135
135
  this.processHistoryMessages(messagesDescOrder);
136
+
137
+ // Signal that a batch of history messages has been added to the store.
138
+ // LazyLoadActivity subscribes to this to apply scroll anchoring after render.
139
+ (0, _dispatchCustomEvent.default)(_ChatWidgetEvents.default.HISTORY_BATCH_LOADED, {
140
+ messageCount: messages.length
141
+ });
136
142
  _TelemetryHelper.TelemetryHelper.logActionEvent(_TelemetryConstants.LogLevel.INFO, {
137
143
  Event: _TelemetryConstants.TelemetryEvent.LCWPersistentHistoryPullCompleted,
138
144
  Description: "History pull completed successfully",
@@ -241,6 +247,9 @@ let PersistentConversationHandler = /*#__PURE__*/function () {
241
247
  value: function processMessageToActivity(message) {
242
248
  try {
243
249
  const activity = (0, _convertPersistentChatHistoryMessageToActivity.default)(message);
250
+ if (!activity) {
251
+ return null;
252
+ }
244
253
  activity.id = activity.id || `activity-${this.count}`;
245
254
  activity.channelData = {
246
255
  ...activity.channelData,
@@ -45,10 +45,11 @@ const createAdapter = async (facadeChatSDK, props) => {
45
45
  //so far, there is no need to convert to the shim adapter when using visual tests
46
46
  const isMocked = facadeChatSDK.getChatSDK() instanceof _mockchatsdk.MockChatSDK;
47
47
  if (isMocked !== true) {
48
- var _props$webChatContain, _props$webChatContain2, _props$webChatContain3, _props$webChatContain4;
48
+ var _props$webChatContain, _props$webChatContain2, _props$webChatContain3, _props$webChatContain4, _props$webChatContain5, _props$webChatContain6;
49
49
  const botAuthActivitySubscriberOptionalParams = {
50
50
  fetchBotAuthConfigRetries: (props === null || props === void 0 ? void 0 : (_props$webChatContain = props.webChatContainerProps) === null || _props$webChatContain === void 0 ? void 0 : (_props$webChatContain2 = _props$webChatContain.botAuthConfig) === null || _props$webChatContain2 === void 0 ? void 0 : _props$webChatContain2.fetchBotAuthConfigRetries) || defaultBotAuthConfig.fetchBotAuthConfigRetries,
51
- fetchBotAuthConfigRetryInterval: (props === null || props === void 0 ? void 0 : (_props$webChatContain3 = props.webChatContainerProps) === null || _props$webChatContain3 === void 0 ? void 0 : (_props$webChatContain4 = _props$webChatContain3.botAuthConfig) === null || _props$webChatContain4 === void 0 ? void 0 : _props$webChatContain4.fetchBotAuthConfigRetryInterval) || defaultBotAuthConfig.fetchBotAuthConfigRetryInterval
51
+ fetchBotAuthConfigRetryInterval: (props === null || props === void 0 ? void 0 : (_props$webChatContain3 = props.webChatContainerProps) === null || _props$webChatContain3 === void 0 ? void 0 : (_props$webChatContain4 = _props$webChatContain3.botAuthConfig) === null || _props$webChatContain4 === void 0 ? void 0 : _props$webChatContain4.fetchBotAuthConfigRetryInterval) || defaultBotAuthConfig.fetchBotAuthConfigRetryInterval,
52
+ fallbackShowSignInCard: props === null || props === void 0 ? void 0 : (_props$webChatContain5 = props.webChatContainerProps) === null || _props$webChatContain5 === void 0 ? void 0 : (_props$webChatContain6 = _props$webChatContain5.botAuthConfig) === null || _props$webChatContain6 === void 0 ? void 0 : _props$webChatContain6.fallbackShowSignInCard
52
53
  };
53
54
  adapter = new _ChatAdapterShim.ChatAdapterShim(adapter);
54
55
  adapter.addSubscriber(new _AddActivitySubscriber.AddActivitySubscriber());
@@ -54,6 +54,7 @@ var _initConfirmationPropsComposer = require("../common/initConfirmationPropsCom
54
54
  var _initWebChatComposer = require("../common/initWebChatComposer");
55
55
  var _defaultCacheManager = require("../../../common/storage/default/defaultCacheManager");
56
56
  var _setPostChatContextAndLoadSurvey = require("../common/setPostChatContextAndLoadSurvey");
57
+ var _liveChatConfigUtils = require("../common/liveChatConfigUtils");
57
58
  var _startProactiveChat = require("../common/startProactiveChat");
58
59
  var _useChatAdapterStore = _interopRequireDefault(require("../../../hooks/useChatAdapterStore"));
59
60
  var _useChatContextStore = _interopRequireDefault(require("../../../hooks/useChatContextStore"));
@@ -736,7 +737,15 @@ const LiveChatWidgetStateful = props => {
736
737
  if (state.appStates.isMinimized) {
737
738
  _ActivityStreamHandler.ActivityStreamHandler.cork();
738
739
  } else {
739
- setTimeout(() => _ActivityStreamHandler.ActivityStreamHandler.uncork(), 500);
740
+ var _state$domainStates4;
741
+ const extendedChatConfig = state === null || state === void 0 ? void 0 : (_state$domainStates4 = state.domainStates) === null || _state$domainStates4 === void 0 ? void 0 : _state$domainStates4.liveChatConfig;
742
+ if ((0, _liveChatConfigUtils.shouldLoadPersistentChatHistory)(extendedChatConfig)) {
743
+ requestAnimationFrame(() => {
744
+ setTimeout(() => _ActivityStreamHandler.ActivityStreamHandler.uncork(), 500);
745
+ });
746
+ } else {
747
+ setTimeout(() => _ActivityStreamHandler.ActivityStreamHandler.uncork(), 500);
748
+ }
740
749
  }
741
750
  }, [state.appStates.isMinimized]);
742
751
 
@@ -893,6 +902,25 @@ const LiveChatWidgetStateful = props => {
893
902
  }
894
903
  });
895
904
  }, []);
905
+
906
+ // Reliable browser close detection via visibilitychange + sendBeacon
907
+ // visibilitychange fires while the page is still alive (unlike beforeunload),
908
+ // so telemetry calls complete reliably. False positives (tab switch, minimize)
909
+ // are filtered in Kusto by checking if this is the last event in the session.
910
+ (0, _react2.useEffect)(() => {
911
+ const handleVisibilityChange = () => {
912
+ if (document.visibilityState === "hidden" && state.appStates.conversationState === _ConversationState.ConversationState.Active) {
913
+ _TelemetryHelper.TelemetryHelper.logActionEvent(_TelemetryConstants.LogLevel.INFO, {
914
+ Event: _TelemetryConstants.TelemetryEvent.BrowserTabHidden,
915
+ Description: "Browser tab hidden during active conversation"
916
+ });
917
+ }
918
+ };
919
+ document.addEventListener("visibilitychange", handleVisibilityChange);
920
+ return () => {
921
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
922
+ };
923
+ }, [state.appStates.conversationState]);
896
924
  const initiateEndChatOnBrowserUnload = () => {
897
925
  var _DataStoreManager$cli;
898
926
  _TelemetryHelper.TelemetryHelper.logActionEvent(_TelemetryConstants.LogLevel.INFO, {
@@ -122,7 +122,6 @@ const ProactiveChatPaneStateful = props => {
122
122
  bodyTitleText: state.appStates.proactiveChatStates.proactiveChatBodyTitle ? state.appStates.proactiveChatStates.proactiveChatBodyTitle : proactiveChatProps === null || proactiveChatProps === void 0 ? void 0 : (_proactiveChatProps$c = proactiveChatProps.controlProps) === null || _proactiveChatProps$c === void 0 ? void 0 : _proactiveChatProps$c.bodyTitleText
123
123
  };
124
124
  (0, _react.useEffect)(() => {
125
- (0, _utils.setFocusOnElement)(document.getElementById(controlProps.id + "-startbutton"));
126
125
  _TelemetryManager.TelemetryTimers.ProactiveChatScreenTimer = (0, _utils.createTimer)();
127
126
  const timeoutEvent = setTimeout(() => {
128
127
  handleProactiveChatInviteTimeout();
@@ -151,10 +151,18 @@ const WebChatContainerStateful = props => {
151
151
  document.addEventListener("click", clickHandler);
152
152
  return () => document.removeEventListener("click", clickHandler);
153
153
  }, [state]);
154
+ const minimizedStyles = state.appStates.isMinimized ? shouldLoadPersistentHistoryMessages ? {
155
+ visibility: "hidden",
156
+ position: "absolute",
157
+ width: 0,
158
+ height: 0,
159
+ overflow: "hidden",
160
+ pointerEvents: "none"
161
+ } : {
162
+ display: "none"
163
+ } : {};
154
164
  const containerStyles = {
155
- root: Object.assign({}, _defaultWebChatContainerStatefulProps.defaultWebChatContainerStatefulProps.containerStyles, webChatContainerProps === null || webChatContainerProps === void 0 ? void 0 : webChatContainerProps.containerStyles, {
156
- display: state.appStates.isMinimized ? "none" : ""
157
- }) // Use this instead of removing WebChat from the picture so that the activity observer inside the adapter is not invoked
165
+ root: Object.assign({}, _defaultWebChatContainerStatefulProps.defaultWebChatContainerStatefulProps.containerStyles, webChatContainerProps === null || webChatContainerProps === void 0 ? void 0 : webChatContainerProps.containerStyles, minimizedStyles) // Use visibility-based hiding instead of display:none to preserve scroll position across minimize/maximize
158
166
  };
159
167
 
160
168
  const localizedTexts = {
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.default = void 0;
7
7
  var _Constants = require("../../../../common/Constants");
8
+ var _SupportedAdaptiveCards = require("@microsoft/omnichannel-chat-sdk/lib/utils/printers/interfaces/SupportedAdaptiveCards");
8
9
  var _botActivity = _interopRequireDefault(require("../activities/botActivity"));
9
10
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
10
11
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -23,15 +24,24 @@ const convertStringValueToInt = value => {
23
24
 
24
25
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
25
26
  const convertPersistentChatHistoryMessageToActivity = message => {
26
- var _from$user, _from$application;
27
+ var _from$application, _from$user, _from$application2;
27
28
  const {
28
29
  additionalData,
29
30
  attachments,
31
+ botContentType,
30
32
  content,
31
33
  created,
32
34
  from,
33
35
  transcriptOriginalMessageId
34
36
  } = message;
37
+ const isFromCustomer = (from === null || from === void 0 ? void 0 : (_from$application = from.application) === null || _from$application === void 0 ? void 0 : _from$application.displayName) === "Customer";
38
+
39
+ // Filter out customer responses to adaptive cards (e.g., form submissions like {"value":{"goPaperless":"yes"}}).
40
+ // These are the customer's postBack/messageBack replies to bot adaptive cards — not displayable content.
41
+ // In live chat, webchat hides these natively; in history we must filter them explicitly.
42
+ if (isFromCustomer && botContentType === "azurebotservice.adaptivecard") {
43
+ return null;
44
+ }
35
45
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
46
  const activity = {
37
47
  ..._botActivity.default,
@@ -68,13 +78,110 @@ const convertPersistentChatHistoryMessageToActivity = message => {
68
78
  name: from.user.displayName
69
79
  };
70
80
  }
71
- if ((from === null || from === void 0 ? void 0 : (_from$application = from.application) === null || _from$application === void 0 ? void 0 : _from$application.displayName) === "Customer") {
81
+ if ((from === null || from === void 0 ? void 0 : (_from$application2 = from.application) === null || _from$application2 === void 0 ? void 0 : _from$application2.displayName) === "Customer") {
72
82
  activity.from = {
73
83
  role: "user",
74
84
  name: from.application.displayName
75
85
  };
76
86
  }
77
87
  if (content) {
88
+ var _parsedContent, _parsedContent$value;
89
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
90
+ let parsedContent;
91
+ try {
92
+ parsedContent = JSON.parse(content);
93
+ } catch (error) {
94
+ parsedContent = null; // fall back to normal text handling
95
+ }
96
+
97
+ // Check if this is a customer form submission response (e.g., RichObjectMessage_Form)
98
+ // These should be ignored as they are form submission data, not displayable content
99
+ if (isFromCustomer && ((_parsedContent = parsedContent) === null || _parsedContent === void 0 ? void 0 : (_parsedContent$value = _parsedContent.value) === null || _parsedContent$value === void 0 ? void 0 : _parsedContent$value.type) === "RichObjectMessage_Form") {
100
+ return null;
101
+ }
102
+ if (parsedContent && typeof parsedContent === "object") {
103
+ var _parsedContent$sugges;
104
+ // Structural detection: check the parsed object's properties directly
105
+ const hasAttachments = Array.isArray(parsedContent.attachments) && parsedContent.attachments.length > 0;
106
+ const hasSuggestedActions = Array.isArray((_parsedContent$sugges = parsedContent.suggestedActions) === null || _parsedContent$sugges === void 0 ? void 0 : _parsedContent$sugges.actions) && parsedContent.suggestedActions.actions.length > 0;
107
+ const isRawAdaptiveCardBody = parsedContent.type === "AdaptiveCard";
108
+
109
+ // Filter out suggested-action-only messages from history.
110
+ // WebChat only renders suggestedActions for the most recent activity, so in history
111
+ // these render as empty "Suggested reply" text bubbles with no actionable buttons.
112
+ if (hasSuggestedActions && !hasAttachments) {
113
+ return null;
114
+ }
115
+
116
+ // Substring detection: check the raw content string for known card type patterns
117
+ const contentLower = content.toLowerCase();
118
+ const isAdaptiveCard = contentLower.includes(_Constants.Constants.AdaptiveCardType);
119
+ const isSuggestedActions = contentLower.includes(_Constants.Constants.SuggestedActionsType);
120
+ const containsSupportedCard = Object.values(_SupportedAdaptiveCards.SupportedAdaptiveCards).some(type => contentLower.includes(type.toLowerCase()));
121
+
122
+ // If the content is a raw adaptive card body (type: "AdaptiveCard"), wrap it as an attachment
123
+ // so webchat can render it properly instead of treating it as an unknown activity type
124
+ if (isRawAdaptiveCardBody) {
125
+ return {
126
+ ...activity,
127
+ text: "",
128
+ attachments: [{
129
+ contentType: _SupportedAdaptiveCards.SupportedAdaptiveCards.Adaptive,
130
+ content: parsedContent
131
+ }],
132
+ timestamp,
133
+ channelData: {
134
+ ...activity.channelData,
135
+ "webchat:sequence-id": webchatSequenceId
136
+ }
137
+ };
138
+ }
139
+
140
+ // Detect rich content using both structural checks and substring matching
141
+ if (hasAttachments || hasSuggestedActions || isAdaptiveCard || isSuggestedActions || containsSupportedCard) {
142
+ var _activity$from;
143
+ // Preserve from.role from the base activity — parsedContent.from may lack the role property
144
+ // which webchat needs to determine how to render the message (bot vs user)
145
+ const preservedFrom = {
146
+ ...activity.from,
147
+ ...(parsedContent.from || {}),
148
+ role: ((_activity$from = activity.from) === null || _activity$from === void 0 ? void 0 : _activity$from.role) || "bot"
149
+ };
150
+ return {
151
+ ...activity,
152
+ ...parsedContent,
153
+ from: preservedFrom,
154
+ timestamp,
155
+ channelData: {
156
+ ...activity.channelData,
157
+ "webchat:sequence-id": webchatSequenceId
158
+ }
159
+ };
160
+ }
161
+
162
+ // If parsedContent is a webchat activity (type: "message") but didn't match any specific card check,
163
+ // still treat it as a rich activity to avoid displaying raw JSON as text
164
+ if (parsedContent.type === "message" && (parsedContent.attachments || parsedContent.suggestedActions || parsedContent.value)) {
165
+ var _activity$from2;
166
+ const preservedFrom = {
167
+ ...activity.from,
168
+ ...(parsedContent.from || {}),
169
+ role: ((_activity$from2 = activity.from) === null || _activity$from2 === void 0 ? void 0 : _activity$from2.role) || "bot"
170
+ };
171
+ return {
172
+ ...activity,
173
+ ...parsedContent,
174
+ from: preservedFrom,
175
+ timestamp,
176
+ channelData: {
177
+ ...activity.channelData,
178
+ "webchat:sequence-id": webchatSequenceId
179
+ }
180
+ };
181
+ }
182
+ }
183
+
184
+ // Plain text message
78
185
  return {
79
186
  ...activity,
80
187
  text: content,
@@ -76,15 +76,26 @@ const WebChatEventSubscribers = () => {
76
76
  // Dispatch events when connection is established
77
77
  setTimeout(() => {
78
78
  (0, _dispatchCustomEvent.default)(_ChatWidgetEvents.default.FETCH_PERSISTENT_CHAT_HISTORY);
79
+ // The trigger activity renders the "Loading previous messages..." banner
80
+ // via LazyLoadActivity. We set webchat:sequence-id to 1 and timestamp
81
+ // to epoch+1ms so WebChat sorts this activity BEFORE all history messages
82
+ // (which have sequence-ids based on transcriptOriginalMessageId timestamps).
83
+ // Without these, WebChat places activities without a sequence-id at the
84
+ // end of the transcript, causing the banner to appear at the bottom and
85
+ // breaking the IntersectionObserver pagination trigger (the observer only
86
+ // fires on visibility transitions — if the element starts visible at the
87
+ // bottom, the initial fire is blocked by the paused state and never
88
+ // re-fires since there is no transition).
79
89
  (0, _dispatchCustomEvent.default)(_ChatWidgetEvents.default.ADD_ACTIVITY, {
80
90
  activity: {
81
91
  from: {
82
92
  role: "bot"
83
93
  },
84
- timestamp: 0,
94
+ timestamp: new Date(1).toISOString(),
85
95
  type: "message",
86
96
  channelData: {
87
- tags: [_Constants.Constants.persistentChatHistoryMessagePullTriggerTag]
97
+ tags: [_Constants.Constants.persistentChatHistoryMessagePullTriggerTag],
98
+ "webchat:sequence-id": 1
88
99
  }
89
100
  }
90
101
  });