@microsoft/omnichannel-chat-widget 1.8.4-main.cbab5fc → 1.8.4-main.cd79f08

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 (81) hide show
  1. package/README.md +12 -3
  2. package/lib/cjs/common/Constants.js +2 -0
  3. package/lib/cjs/common/facades/FacadeChatSDK.js +235 -9
  4. package/lib/cjs/common/telemetry/TelemetryConstants.js +13 -0
  5. package/lib/cjs/common/telemetry/loggers/appInsightsLogger.js +7 -7
  6. package/lib/cjs/common/utils/xssUtils.js +23 -51
  7. package/lib/cjs/common/utils.js +15 -2
  8. package/lib/cjs/components/errorboundary/ErrorBoundary.js +2 -1
  9. package/lib/cjs/components/livechatwidget/LiveChatWidget.js +9 -1
  10. package/lib/cjs/components/livechatwidget/common/ActivitySubscriber/BotAuthActivitySubscriber.js +23 -6
  11. package/lib/cjs/components/livechatwidget/common/ChatWidgetEvents.js +1 -0
  12. package/lib/cjs/components/livechatwidget/common/PersistentConversationHandler.js +9 -0
  13. package/lib/cjs/components/livechatwidget/common/authHelper.js +44 -4
  14. package/lib/cjs/components/livechatwidget/common/createAdapter.js +3 -2
  15. package/lib/cjs/components/livechatwidget/common/liveChatConfigUtils.js +36 -4
  16. package/lib/cjs/components/livechatwidget/common/startChat.js +31 -14
  17. package/lib/cjs/components/livechatwidget/livechatwidgetstateful/LiveChatWidgetStateful.js +62 -2
  18. package/lib/cjs/components/postchatsurveypanestateful/PostChatSurveyPaneStateful.js +6 -4
  19. package/lib/cjs/components/proactivechatpanestateful/ProactiveChatPaneStateful.js +0 -1
  20. package/lib/cjs/components/webchatcontainerstateful/WebChatContainerStateful.js +42 -27
  21. package/lib/cjs/components/webchatcontainerstateful/common/activityConverters/convertPersistentChatHistoryMessageToActivity.js +117 -16
  22. package/lib/cjs/components/webchatcontainerstateful/common/defaultStyles/defaultAdaptiveCardStyles.js +3 -1
  23. package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/WebChatEventSubscribers.js +13 -2
  24. package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/activities/LazyLoadActivity.js +160 -167
  25. package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/activityMiddleware.js +1 -1
  26. package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/timestamps/NotDeliveredTimestamp.js +2 -0
  27. package/lib/cjs/contexts/common/LiveChatWidgetActionType.js +1 -0
  28. package/lib/cjs/contexts/common/LiveChatWidgetContextInitialState.js +6 -1
  29. package/lib/cjs/contexts/createReducer.js +15 -0
  30. package/lib/cjs/controller/componentController.js +13 -1
  31. package/lib/cjs/plugins/newMessageEventHandler.js +20 -3
  32. package/lib/esm/common/Constants.js +2 -0
  33. package/lib/esm/common/facades/FacadeChatSDK.js +236 -10
  34. package/lib/esm/common/telemetry/TelemetryConstants.js +13 -0
  35. package/lib/esm/common/telemetry/loggers/appInsightsLogger.js +7 -7
  36. package/lib/esm/common/utils/xssUtils.js +23 -51
  37. package/lib/esm/common/utils.js +12 -1
  38. package/lib/esm/components/errorboundary/ErrorBoundary.js +4 -2
  39. package/lib/esm/components/livechatwidget/LiveChatWidget.js +9 -1
  40. package/lib/esm/components/livechatwidget/common/ActivitySubscriber/BotAuthActivitySubscriber.js +23 -6
  41. package/lib/esm/components/livechatwidget/common/ChatWidgetEvents.js +1 -0
  42. package/lib/esm/components/livechatwidget/common/PersistentConversationHandler.js +9 -0
  43. package/lib/esm/components/livechatwidget/common/authHelper.js +44 -4
  44. package/lib/esm/components/livechatwidget/common/createAdapter.js +3 -2
  45. package/lib/esm/components/livechatwidget/common/liveChatConfigUtils.js +33 -2
  46. package/lib/esm/components/livechatwidget/common/startChat.js +31 -14
  47. package/lib/esm/components/livechatwidget/livechatwidgetstateful/LiveChatWidgetStateful.js +62 -2
  48. package/lib/esm/components/postchatsurveypanestateful/PostChatSurveyPaneStateful.js +6 -4
  49. package/lib/esm/components/proactivechatpanestateful/ProactiveChatPaneStateful.js +1 -2
  50. package/lib/esm/components/webchatcontainerstateful/WebChatContainerStateful.js +43 -28
  51. package/lib/esm/components/webchatcontainerstateful/common/activityConverters/convertPersistentChatHistoryMessageToActivity.js +117 -16
  52. package/lib/esm/components/webchatcontainerstateful/common/defaultStyles/defaultAdaptiveCardStyles.js +3 -1
  53. package/lib/esm/components/webchatcontainerstateful/webchatcontroller/WebChatEventSubscribers.js +13 -2
  54. package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/activities/LazyLoadActivity.js +160 -171
  55. package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/activityMiddleware.js +1 -1
  56. package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/timestamps/NotDeliveredTimestamp.js +2 -0
  57. package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/channelDataMiddleware.js +1 -0
  58. package/lib/esm/contexts/common/LiveChatWidgetActionType.js +1 -0
  59. package/lib/esm/contexts/common/LiveChatWidgetContextInitialState.js +6 -1
  60. package/lib/esm/contexts/createReducer.js +15 -0
  61. package/lib/esm/controller/componentController.js +13 -1
  62. package/lib/esm/plugins/newMessageEventHandler.js +20 -3
  63. package/lib/types/common/Constants.d.ts +2 -0
  64. package/lib/types/common/facades/FacadeChatSDK.d.ts +29 -0
  65. package/lib/types/common/facades/types/IFacadeChatSDKInput.d.ts +3 -1
  66. package/lib/types/common/telemetry/TelemetryConstants.d.ts +13 -2
  67. package/lib/types/common/utils/xssUtils.d.ts +5 -21
  68. package/lib/types/common/utils.d.ts +9 -1
  69. package/lib/types/components/errorboundary/ErrorBoundary.d.ts +1 -1
  70. package/lib/types/components/livechatwidget/common/ActivitySubscriber/BotAuthActivitySubscriber.d.ts +1 -0
  71. package/lib/types/components/livechatwidget/common/ChatWidgetEvents.d.ts +2 -1
  72. package/lib/types/components/livechatwidget/common/authHelper.d.ts +9 -2
  73. package/lib/types/components/livechatwidget/common/liveChatConfigUtils.d.ts +11 -0
  74. package/lib/types/components/livechatwidget/interfaces/IBotAuthActivitySubscriberOptionalParams.d.ts +1 -0
  75. package/lib/types/components/webchatcontainerstateful/interfaces/IAdaptiveCardStyles.d.ts +2 -0
  76. package/lib/types/components/webchatcontainerstateful/interfaces/IBotAuthConfig.d.ts +7 -0
  77. package/lib/types/components/webchatcontainerstateful/interfaces/IExtendedChatConffig.d.ts +1 -1
  78. package/lib/types/components/webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/activities/LazyLoadActivity.d.ts +14 -38
  79. package/lib/types/contexts/common/ILiveChatWidgetContext.d.ts +1 -0
  80. package/lib/types/contexts/common/LiveChatWidgetActionType.d.ts +2 -1
  81. package/package.json +15 -6
@@ -43,10 +43,6 @@ import { createTimer } from "../../../../../../common/utils";
43
43
  import dispatchCustomEvent from "../../../../../../common/utils/dispatchCustomEvent";
44
44
  import SecureEventBus from "../../../../../../common/utils/SecureEventBus";
45
45
 
46
- /*
47
- * Interface defining the state of a scroll operation
48
- * Used to track and verify scroll actions for reliability
49
- */
50
46
  /**
51
47
  * LazyLoadHandler - Static class managing all lazy loading functionality
52
48
  *
@@ -83,8 +79,10 @@ let LazyLoadHandler = /*#__PURE__*/function () {
83
79
  // Temporary disable flag
84
80
  // The intersection observer instance
85
81
 
86
- // Scroll operation state
87
- // Current scroll operation tracking
82
+ // Scroll anchoring state (height-delta approach)
83
+ // scrollHeight before content loads
84
+ // scrollTop before content loads
85
+ // Tracks if the first batch has loaded
88
86
  // Prevents concurrent scroll operations (public for event handlers)
89
87
 
90
88
  // Timeout and queue management
@@ -128,6 +126,7 @@ let LazyLoadHandler = /*#__PURE__*/function () {
128
126
  key: "directReset",
129
127
  value: function directReset() {
130
128
  LazyLoadHandler.resetPending = true;
129
+ LazyLoadHandler.initialLoadComplete = false; // New session — next batch is an initial load
131
130
  LazyLoadHandler.setHasMoreHistoryAvailable(true);
132
131
  LazyLoadHandler.unmount();
133
132
  }
@@ -431,16 +430,15 @@ let LazyLoadHandler = /*#__PURE__*/function () {
431
430
 
432
431
  /**
433
432
  * Main lazy load trigger handler
434
- *
433
+ *
435
434
  * This is the core method that executes when the trigger element becomes visible.
436
435
  * It coordinates the entire lazy loading process:
437
- *
436
+ *
438
437
  * 1. Sets flags to prevent concurrent operations
439
- * 2. Dispatches event to fetch more chat history
440
- * 3. Waits for content to load
441
- * 4. Executes scroll adjustment to maintain user position
442
- *
443
- * Timing: Uses 300ms delay to allow content loading before scroll adjustment
438
+ * 2. Captures scroll state BEFORE content loads (for height-delta anchoring)
439
+ * 3. Dispatches event to fetch more chat history
440
+ *
441
+ * Scroll anchoring is triggered by HISTORY_BATCH_LOADED event (not a blind timeout).
444
442
  */
445
443
  }, {
446
444
  key: "handleLazyLoadTrigger",
@@ -454,138 +452,60 @@ let LazyLoadHandler = /*#__PURE__*/function () {
454
452
  LazyLoadHandler.pendingScrollAction = true; // Block new scroll actions
455
453
  LazyLoadHandler.paused = true; // Pause intersection observer
456
454
 
455
+ // Capture scroll geometry BEFORE new content loads — used for height-delta anchoring
456
+ const {
457
+ container,
458
+ isScrollable
459
+ } = LazyLoadHandler.findScrollContainer();
460
+ if (container && isScrollable) {
461
+ LazyLoadHandler.preLoadScrollHeight = container.scrollHeight;
462
+ LazyLoadHandler.preLoadScrollTop = container.scrollTop;
463
+ }
464
+ LazyLoadHandler.logLifecycleEvent(TelemetryEvent.LCWLazyLoadTriggerFired, "Lazy load trigger fired — fetching history");
465
+
457
466
  // Dispatch custom event to trigger chat history fetching
458
- // This event is handled by other parts of the chat system
459
467
  dispatchCustomEvent(ChatWidgetEvents.FETCH_PERSISTENT_CHAT_HISTORY);
460
-
461
- // Wait for content to load before performing scroll adjustment
462
- // 200ms provides good balance between responsiveness and content loading
463
- const timeoutId = window.setTimeout(() => {
464
- LazyLoadHandler.retryTimeouts.delete(timeoutId);
465
- LazyLoadHandler.executeReliableScroll();
466
- }, 200); // Reduced from 300ms to 200ms for faster response
467
- LazyLoadHandler.retryTimeouts.add(timeoutId);
468
468
  }
469
469
 
470
470
  /**
471
- * Executes reliable scroll with validation and retry logic
472
- *
473
- * This method implements a robust scroll system that:
474
- * 1. Finds and validates the scroll container
475
- * 2. Calculates target scroll position (current + 35px)
476
- * 3. Sets up scroll state for tracking and retries
477
- * 4. Initiates the scroll attempt process
478
- *
479
- * If no suitable container is found, schedules a retry.
471
+ * Applies height-delta scroll anchoring after new history content is prepended.
472
+ *
473
+ * When content is added above the viewport, the browser keeps scrollTop constant
474
+ * but the content shifts down — making the viewport drift upward.
475
+ * Fix: scrollTop = savedScrollTop + (newScrollHeight - savedScrollHeight)
476
+ * This keeps the user looking at the same content they had before.
480
477
  */
481
478
  }, {
482
- key: "executeReliableScroll",
483
- value: function executeReliableScroll() {
484
- // Guard: Don't execute scroll if no more history is available
485
- if (!LazyLoadHandler.hasMoreHistoryAvailable) {
486
- LazyLoadHandler.finishScrollAction();
487
- return;
488
- }
489
-
490
- // Find container using multiple fallback strategies
479
+ key: "applyScrollAnchor",
480
+ value: function applyScrollAnchor() {
491
481
  const {
492
482
  container,
493
483
  isScrollable
494
484
  } = LazyLoadHandler.findScrollContainer();
495
485
  if (!container || !isScrollable) {
496
- LazyLoadHandler.scheduleScrollRetry();
497
- return;
498
- }
499
-
500
- // Calculate scroll positions
501
- const initialScrollTop = container.scrollTop;
502
- const targetScrollTop = initialScrollTop + 35; // 35px down to maintain position
503
-
504
- // Set up scroll state for tracking and retries
505
- LazyLoadHandler.scrollState = {
506
- container,
507
- initialScrollTop,
508
- targetScrollTop,
509
- attemptCount: 0,
510
- maxAttempts: 5 // Allow up to 5 retry attempts
511
- };
512
-
513
- // Begin the scroll attempt process
514
- LazyLoadHandler.attemptScroll();
515
- }
516
-
517
- /**
518
- * Attempts to perform scroll with verification and retry logic
519
- *
520
- * This method implements a sophisticated scroll system:
521
- *
522
- * 1. Uses requestAnimationFrame for smooth execution
523
- * 2. Performs the scroll operation
524
- * 3. Verifies scroll actually occurred (5px tolerance)
525
- * 4. Retries with exponential backoff if failed
526
- * 5. Continues operation after max attempts
527
- *
528
- * The two-frame approach ensures scroll is applied and then verified
529
- * after the browser has had time to process the scroll change.
530
- */
531
- }, {
532
- key: "attemptScroll",
533
- value: function attemptScroll() {
534
- // Guard: Don't attempt scroll if no more history is available
535
- if (!LazyLoadHandler.hasMoreHistoryAvailable) {
536
486
  LazyLoadHandler.finishScrollAction();
537
487
  return;
538
488
  }
539
- if (!LazyLoadHandler.scrollState) {
540
- LazyLoadHandler.finishScrollAction();
541
- return;
489
+ const newScrollHeight = container.scrollHeight;
490
+ const heightDelta = newScrollHeight - LazyLoadHandler.preLoadScrollHeight;
491
+ if (heightDelta > 0) {
492
+ container.scrollTop = LazyLoadHandler.preLoadScrollTop + heightDelta;
542
493
  }
543
-
544
- // Extract current scroll state
545
- const {
546
- container,
547
- targetScrollTop,
548
- attemptCount,
549
- maxAttempts
550
- } = LazyLoadHandler.scrollState;
551
- LazyLoadHandler.scrollState.attemptCount++;
552
-
553
- // Perform scroll using requestAnimationFrame for smooth execution
554
- // Frame 1: Apply the scroll
555
- requestAnimationFrame(() => {
556
- // Double-check history availability before applying scroll
557
- if (!LazyLoadHandler.hasMoreHistoryAvailable) {
558
- LazyLoadHandler.finishScrollAction();
559
- return;
560
- }
561
- if (container) {
562
- container.scrollTop = targetScrollTop;
563
- }
564
-
565
- // Frame 2: Verify scroll occurred after browser has processed the change
566
- requestAnimationFrame(() => {
567
- // Triple-check history availability before verification
568
- if (!LazyLoadHandler.hasMoreHistoryAvailable) {
569
- LazyLoadHandler.finishScrollAction();
570
- return;
571
- }
572
- const actualScrollTop = container ? container.scrollTop : 0;
573
- const scrollSucceeded = Math.abs(actualScrollTop - targetScrollTop) < 5; // 5px tolerance for success
574
-
575
- if (scrollSucceeded) {
576
- LazyLoadHandler.finishScrollAction();
577
- } else if (attemptCount < maxAttempts) {
578
- // Retry with exponential backoff (100ms * attempt number)
579
- const timeoutId = window.setTimeout(() => {
580
- LazyLoadHandler.retryTimeouts.delete(timeoutId);
581
- LazyLoadHandler.attemptScroll();
582
- }, 100 * attemptCount); // Exponential backoff
583
- LazyLoadHandler.retryTimeouts.add(timeoutId);
584
- } else {
585
- LazyLoadHandler.finishScrollAction();
494
+ try {
495
+ TelemetryHelper.logActionEvent(LogLevel.INFO, {
496
+ Event: TelemetryEvent.LCWLazyLoadScrollAnchorApplied,
497
+ Description: "Scroll anchor applied after history batch",
498
+ CustomProperties: {
499
+ heightDelta,
500
+ preLoadScrollHeight: LazyLoadHandler.preLoadScrollHeight,
501
+ newScrollHeight,
502
+ newScrollTop: container.scrollTop
586
503
  }
587
504
  });
588
- });
505
+ } catch {
506
+ // Silent fail — don't break scroll anchoring for telemetry issues
507
+ }
508
+ LazyLoadHandler.finishScrollAction();
589
509
  }
590
510
 
591
511
  /**
@@ -599,7 +519,6 @@ let LazyLoadHandler = /*#__PURE__*/function () {
599
519
  key: "finishScrollAction",
600
520
  value: function finishScrollAction() {
601
521
  // Clean up scroll tracking state
602
- LazyLoadHandler.scrollState = null;
603
522
  LazyLoadHandler.pendingScrollAction = false;
604
523
 
605
524
  // Schedule unpause and reset with delay for content stabilization
@@ -611,33 +530,9 @@ let LazyLoadHandler = /*#__PURE__*/function () {
611
530
  LazyLoadHandler.retryTimeouts.add(timeoutId);
612
531
  }
613
532
 
614
- /**
615
- * Schedules retry for failed scroll operations
616
- *
617
- * Used when scroll container is not available or scrollable.
618
- * Provides a longer delay to allow container to become ready.
619
- */
620
- }, {
621
- key: "scheduleScrollRetry",
622
- value: function scheduleScrollRetry() {
623
- // Don't schedule retry if no more history is available
624
- if (!LazyLoadHandler.hasMoreHistoryAvailable) {
625
- LazyLoadHandler.finishScrollAction();
626
- return;
627
- }
628
- const timeoutId = window.setTimeout(() => {
629
- LazyLoadHandler.retryTimeouts.delete(timeoutId);
630
- // Only retry if we're still in a pending scroll action state and have more history
631
- if (LazyLoadHandler.pendingScrollAction && LazyLoadHandler.hasMoreHistoryAvailable) {
632
- LazyLoadHandler.executeReliableScroll();
633
- }
634
- }, 1000); // 1 second delay for container readiness
635
- LazyLoadHandler.retryTimeouts.add(timeoutId);
636
- }
637
-
638
533
  /**
639
534
  * Schedules observer reset for next lazy load cycle
640
- *
535
+ *
641
536
  * After a lazy load operation completes, the observer needs to be reset
642
537
  * to detect the next time the user scrolls to the top.
643
538
  */
@@ -755,7 +650,7 @@ let LazyLoadHandler = /*#__PURE__*/function () {
755
650
  }, {
756
651
  key: "moveScrollDown",
757
652
  value: function moveScrollDown() {
758
- LazyLoadHandler.executeReliableScroll();
653
+ LazyLoadHandler.applyScrollAnchor();
759
654
  }
760
655
 
761
656
  /**
@@ -795,6 +690,9 @@ let LazyLoadHandler = /*#__PURE__*/function () {
795
690
  }, {
796
691
  key: "handleNoMoreHistoryAvailable",
797
692
  value: function handleNoMoreHistoryAvailable() {
693
+ if (!LazyLoadHandler.initialLoadComplete) {
694
+ LazyLoadHandler.initialLoadComplete = true;
695
+ }
798
696
  LazyLoadHandler.setHasMoreHistoryAvailable(false);
799
697
  LazyLoadHandler.paused = true;
800
698
  LazyLoadHandler.pendingScrollAction = false; // Reset this to prevent stuck states
@@ -812,9 +710,6 @@ let LazyLoadHandler = /*#__PURE__*/function () {
812
710
  LazyLoadHandler.observer.disconnect();
813
711
  LazyLoadHandler.observer = null;
814
712
  }
815
-
816
- // Clear scroll state
817
- LazyLoadHandler.scrollState = null;
818
713
  }
819
714
 
820
715
  /**
@@ -836,6 +731,8 @@ let LazyLoadHandler = /*#__PURE__*/function () {
836
731
  LazyLoadHandler.setHasMoreHistoryAvailable(true); // Reset history availability flag
837
732
  LazyLoadHandler.initializationQueue = []; // Clear action queue
838
733
  LazyLoadHandler.resetPending = false; // Clear pending reset flag
734
+ // Note: initialLoadComplete is NOT reset here — it persists across observer cycles.
735
+ // It's only reset on new chat sessions (directReset / PersistentConversationReset).
839
736
 
840
737
  // Reinitialize with faster timing for better responsiveness
841
738
  const timeoutId = window.setTimeout(() => {
@@ -875,8 +772,9 @@ let LazyLoadHandler = /*#__PURE__*/function () {
875
772
  LazyLoadHandler.initialized = false;
876
773
  LazyLoadHandler.paused = false;
877
774
  LazyLoadHandler.pendingScrollAction = false;
878
- LazyLoadHandler.scrollState = null;
879
775
  LazyLoadHandler.isReady = false;
776
+ LazyLoadHandler.preLoadScrollHeight = 0;
777
+ LazyLoadHandler.preLoadScrollTop = 0;
880
778
  LazyLoadHandler.initializationQueue = [];
881
779
  // Note: Don't reset resetPending here as it needs to persist across unmount/mount cycles
882
780
  }
@@ -925,7 +823,9 @@ _defineProperty(LazyLoadHandler, "targetId", "lazy-load-trigger-element");
925
823
  _defineProperty(LazyLoadHandler, "initialized", false);
926
824
  _defineProperty(LazyLoadHandler, "paused", false);
927
825
  _defineProperty(LazyLoadHandler, "observer", null);
928
- _defineProperty(LazyLoadHandler, "scrollState", null);
826
+ _defineProperty(LazyLoadHandler, "preLoadScrollHeight", 0);
827
+ _defineProperty(LazyLoadHandler, "preLoadScrollTop", 0);
828
+ _defineProperty(LazyLoadHandler, "initialLoadComplete", false);
929
829
  _defineProperty(LazyLoadHandler, "pendingScrollAction", false);
930
830
  _defineProperty(LazyLoadHandler, "retryTimeouts", new Set());
931
831
  _defineProperty(LazyLoadHandler, "resetPending", false);
@@ -933,6 +833,7 @@ _defineProperty(LazyLoadHandler, "initTimer", createTimer());
933
833
  _defineProperty(LazyLoadHandler, "resetEventListener", BroadcastService.getMessageByEventName(BroadcastEvent.PersistentConversationReset).subscribe(() => {
934
834
  LazyLoadHandler.logLifecycleEvent(TelemetryEvent.LCWLazyLoadReset, "LazyLoad reset triggered");
935
835
  LazyLoadHandler.resetPending = true;
836
+ LazyLoadHandler.initialLoadComplete = false; // New session — next batch is an initial load
936
837
  LazyLoadHandler.setHasMoreHistoryAvailable(true); // Reset this immediately so activityMiddleware doesn't block rendering
937
838
  LazyLoadHandler.unmount(); // Clean up current state immediately
938
839
  }));
@@ -956,19 +857,11 @@ const LazyLoadActivity = props => {
956
857
  setHasMoreHistory(false);
957
858
  };
958
859
 
959
- // Event listener for HISTORY_LOAD_ERROR - hides banner temporarily without disabling future loads
860
+ // Event listener for HISTORY_LOAD_ERROR dismiss banner on any error
960
861
  const handleHistoryLoadError = () => {
961
- // Temporarily hide the banner by pausing, but keep hasMoreHistory true to allow retry
962
- LazyLoadHandler.paused = true;
963
- LazyLoadHandler.pendingScrollAction = false;
964
-
965
- // Re-enable after a delay to allow retry on next scroll
966
- // Note: This timeout is intentionally not tracked as it's scoped to the component lifecycle
967
- window.setTimeout(() => {
968
- LazyLoadHandler.paused = false;
969
- }, 2000); // 2 second delay before allowing retry
970
-
971
- LazyLoadHandler.logLifecycleEvent(TelemetryEvent.LCWLazyLoadHistoryError, "History load error - will retry on next scroll");
862
+ LazyLoadHandler.logLifecycleEvent(TelemetryEvent.LCWLazyLoadHistoryError, `History load error - dismissing banner. initialLoadComplete: ${LazyLoadHandler.initialLoadComplete}`);
863
+ LazyLoadHandler.handleNoMoreHistoryAvailable();
864
+ setHasMoreHistory(false);
972
865
  };
973
866
 
974
867
  // Event listener for PersistentConversationReset to sync React state
@@ -979,6 +872,97 @@ const LazyLoadActivity = props => {
979
872
  setHasMoreHistory(true);
980
873
  };
981
874
 
875
+ // Event listener for HISTORY_BATCH_LOADED — applies scroll anchoring after batch is processed
876
+ const handleBatchLoaded = () => {
877
+ LazyLoadHandler.logLifecycleEvent(TelemetryEvent.LCWLazyLoadBatchReceived, `Batch received — initialLoadComplete: ${LazyLoadHandler.initialLoadComplete}`);
878
+ if (!LazyLoadHandler.initialLoadComplete) {
879
+ // Initial load: use the same height-delta scroll anchoring as subsequent loads.
880
+ // This keeps the user at their current position while history is prepended above.
881
+ LazyLoadHandler.initialLoadComplete = true;
882
+ LazyLoadHandler.pendingScrollAction = false;
883
+ try {
884
+ const {
885
+ container
886
+ } = LazyLoadHandler.findScrollContainer();
887
+ if (container) {
888
+ const savedScrollTop = container.scrollTop;
889
+ const savedScrollHeight = container.scrollHeight;
890
+
891
+ // Freeze viewport while React renders new content above
892
+ container.style.overflow = "hidden";
893
+ let framesRemaining = 6;
894
+ const anchorScroll = () => {
895
+ const newScrollHeight = container.scrollHeight;
896
+ const heightDelta = newScrollHeight - savedScrollHeight;
897
+ if (heightDelta > 0) {
898
+ container.scrollTop = savedScrollTop + heightDelta;
899
+ }
900
+ framesRemaining--;
901
+ if (framesRemaining > 0) {
902
+ requestAnimationFrame(anchorScroll);
903
+ } else {
904
+ container.style.overflow = "";
905
+ LazyLoadHandler.paused = false;
906
+ LazyLoadHandler.scheduleReset();
907
+ }
908
+ };
909
+ requestAnimationFrame(anchorScroll);
910
+ } else {
911
+ LazyLoadHandler.paused = false;
912
+ LazyLoadHandler.scheduleReset();
913
+ }
914
+ } catch {
915
+ LazyLoadHandler.paused = false;
916
+ LazyLoadHandler.scheduleReset();
917
+ }
918
+ LazyLoadHandler.logLifecycleEvent(TelemetryEvent.LCWLazyLoadInitialLoadComplete, "Initial history load complete — scroll anchored to current position");
919
+ return;
920
+ }
921
+ // Pagination: apply height-delta scroll anchoring repeatedly across frames.
922
+ //
923
+ // react-scroll-to-bottom uses useEffect + rAF to auto-scroll to bottom when new content
924
+ // arrives. This fires at unpredictable timing relative to our callbacks, so a single
925
+ // scrollTop assignment gets overridden intermittently.
926
+ //
927
+ // Fix: re-apply the correct scrollTop across several animation frames. After a few frames,
928
+ // react-scroll-to-bottom detects the user is NOT at the bottom (sticky=false) and stops
929
+ // auto-scrolling. We also temporarily freeze overflow to prevent visible flicker.
930
+ try {
931
+ const {
932
+ container
933
+ } = LazyLoadHandler.findScrollContainer();
934
+ if (container) {
935
+ // Re-capture scrollTop — user may have scrolled during the API fetch
936
+ const savedScrollTop = container.scrollTop;
937
+ const savedScrollHeight = LazyLoadHandler.preLoadScrollHeight;
938
+
939
+ // Freeze viewport to prevent visible flicker between competing scroll positions
940
+ container.style.overflow = "hidden";
941
+ let framesRemaining = 6; // ~100ms at 60fps — enough for react-scroll-to-bottom to settle
942
+ const anchorScroll = () => {
943
+ const newScrollHeight = container.scrollHeight;
944
+ const heightDelta = newScrollHeight - savedScrollHeight;
945
+ if (heightDelta > 0) {
946
+ container.scrollTop = savedScrollTop + heightDelta;
947
+ }
948
+ framesRemaining--;
949
+ if (framesRemaining > 0) {
950
+ requestAnimationFrame(anchorScroll);
951
+ } else {
952
+ // All frames applied — restore overflow and finish
953
+ container.style.overflow = "";
954
+ LazyLoadHandler.finishScrollAction();
955
+ }
956
+ };
957
+ requestAnimationFrame(anchorScroll);
958
+ } else {
959
+ LazyLoadHandler.finishScrollAction();
960
+ }
961
+ } catch {
962
+ LazyLoadHandler.finishScrollAction();
963
+ }
964
+ };
965
+
982
966
  // Add secure event listener for no more history signal
983
967
  const eventBus = SecureEventBus.getInstance();
984
968
  const unsubscribeNoMoreHistory = eventBus.subscribe(ChatWidgetEvents.NO_MORE_HISTORY_AVAILABLE, handleNoMoreHistory);
@@ -986,6 +970,9 @@ const LazyLoadActivity = props => {
986
970
  // Add event listener for history load errors
987
971
  const unsubscribeHistoryError = eventBus.subscribe(ChatWidgetEvents.HISTORY_LOAD_ERROR, handleHistoryLoadError);
988
972
 
973
+ // Add event listener for history batch loaded
974
+ const unsubscribeBatchLoaded = eventBus.subscribe(ChatWidgetEvents.HISTORY_BATCH_LOADED, handleBatchLoaded);
975
+
989
976
  // Add event listener for persistent conversation reset
990
977
  const resetSubscription = BroadcastService.getMessageByEventName(BroadcastEvent.PersistentConversationReset).subscribe(handlePersistentConversationReset);
991
978
 
@@ -1003,6 +990,7 @@ const LazyLoadActivity = props => {
1003
990
  return () => {
1004
991
  unsubscribeNoMoreHistory();
1005
992
  unsubscribeHistoryError();
993
+ unsubscribeBatchLoaded();
1006
994
  resetSubscription.unsubscribe();
1007
995
  };
1008
996
  }
@@ -1056,6 +1044,7 @@ const LazyLoadActivity = props => {
1056
1044
  // Remove event listeners
1057
1045
  unsubscribeNoMoreHistory();
1058
1046
  unsubscribeHistoryError();
1047
+ unsubscribeBatchLoaded();
1059
1048
  resetSubscription.unsubscribe();
1060
1049
  if (container) {
1061
1050
  container.removeEventListener("scroll", handleScroll);
@@ -124,7 +124,7 @@ export const createActivityMiddleware = (renderMarkdown, systemMessageStyleProps
124
124
  if (isTagIncluded(card, Constants.conversationDividerTag)) {
125
125
  const conversationDividerLabel = (localizedTexts === null || localizedTexts === void 0 ? void 0 : localizedTexts.CONVERSATION_DIVIDER_ARIA_LABEL) || defaultMiddlewareLocalizedTexts.CONVERSATION_DIVIDER_ARIA_LABEL;
126
126
  // Pass the computed localized text to the divider component
127
- return /*#__PURE__*/React.createElement(ConversationDividerActivity, {
127
+ return () => /*#__PURE__*/React.createElement(ConversationDividerActivity, {
128
128
  dividerActivityAriaLabel: conversationDividerLabel
129
129
  });
130
130
  }
@@ -1,3 +1,5 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+
1
3
  import React, { useCallback, useEffect, useRef } from "react";
2
4
  import { KeyCodes } from "../../../../../../common/KeyCodes";
3
5
  import { Stack } from "@fluentui/react";
@@ -7,6 +7,7 @@
7
7
  import { Constants } from "../../../../../common/Constants";
8
8
  import { DeliveryMode } from "@microsoft/omnichannel-chat-sdk";
9
9
  import { WebChatActionType } from "../../enums/WebChatActionType";
10
+
10
11
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
11
12
  const channelDataMiddleware = addConversationalSurveyTagsCallback => _ref => {
12
13
  let {
@@ -50,4 +50,5 @@ export let LiveChatWidgetActionType;
50
50
  LiveChatWidgetActionType[LiveChatWidgetActionType["SET_CONVERSATIONAL_SURVEY_DISPLAY"] = 47] = "SET_CONVERSATIONAL_SURVEY_DISPLAY";
51
51
  LiveChatWidgetActionType[LiveChatWidgetActionType["GET_IN_MEMORY_STATE"] = 48] = "GET_IN_MEMORY_STATE";
52
52
  LiveChatWidgetActionType[LiveChatWidgetActionType["SET_BOT_AVATAR_INITIALS"] = 49] = "SET_BOT_AVATAR_INITIALS";
53
+ LiveChatWidgetActionType[LiveChatWidgetActionType["SET_USER_AUTHENTICATED"] = 50] = "SET_USER_AUTHENTICATED";
53
54
  })(LiveChatWidgetActionType || (LiveChatWidgetActionType = {}));
@@ -38,6 +38,10 @@ export const getLiveChatWidgetContextInitialState = props => {
38
38
  if (!initialStateFromCache.domainStates.botAvatarInitials) {
39
39
  initialStateFromCache.domainStates.botAvatarInitials = defaultWebChatStyles.botAvatarInitials;
40
40
  }
41
+ // Default isUserAuthenticated to false if not present in cache
42
+ if (initialStateFromCache.appStates.isUserAuthenticated === undefined) {
43
+ initialStateFromCache.appStates.isUserAuthenticated = false;
44
+ }
41
45
  return initialStateFromCache;
42
46
  }
43
47
  const LiveChatWidgetContextInitialState = {
@@ -87,7 +91,8 @@ export const getLiveChatWidgetContextInitialState = props => {
87
91
  selectedSurveyMode: null,
88
92
  postChatParticipantType: undefined,
89
93
  isConversationalSurvey: false,
90
- isConversationalSurveyEnabled: false
94
+ isConversationalSurveyEnabled: false,
95
+ isUserAuthenticated: false
91
96
  },
92
97
  uiStates: {
93
98
  showConfirmationPane: false,
@@ -708,6 +708,21 @@ const reducer = (state, action) => {
708
708
  isConversationalSurvey: action.payload
709
709
  }
710
710
  };
711
+ case LiveChatWidgetActionType.SET_USER_AUTHENTICATED:
712
+ inMemory = {
713
+ ...inMemory,
714
+ appStates: {
715
+ ...inMemory.appStates,
716
+ isUserAuthenticated: action.payload
717
+ }
718
+ };
719
+ return {
720
+ ...state,
721
+ appStates: {
722
+ ...state.appStates,
723
+ isUserAuthenticated: action.payload
724
+ }
725
+ };
711
726
  case LiveChatWidgetActionType.GET_IN_MEMORY_STATE:
712
727
  return inMemory;
713
728
  default:
@@ -1,4 +1,5 @@
1
1
  import { ConversationState } from "../contexts/common/ConversationState";
2
+ import { shouldLoadPersistentChatHistory } from "../components/livechatwidget/common/liveChatConfigUtils";
2
3
  export const shouldShowChatButton = state => {
3
4
  var _state$appStates;
4
5
  return (state.appStates.isMinimized || state.appStates.conversationState === ConversationState.Closed) && (state === null || state === void 0 ? void 0 : (_state$appStates = state.appStates) === null || _state$appStates === void 0 ? void 0 : _state$appStates.hideStartChatButton) === false; // Do not show chat button in case of popout
@@ -17,7 +18,18 @@ export const shouldShowEmailTranscriptPane = state => {
17
18
  return state.uiStates.showEmailTranscriptPane;
18
19
  };
19
20
  export const shouldShowWebChatContainer = state => {
20
- return !state.appStates.isMinimized && state.appStates.conversationState === ConversationState.Active || state.appStates.conversationState === ConversationState.InActive || state.appStates.conversationState === ConversationState.Postchat && state.appStates.isConversationalSurveyEnabled && state.appStates.isConversationalSurvey;
21
+ var _state$domainStates;
22
+ const extendedChatConfig = state === null || state === void 0 ? void 0 : (_state$domainStates = state.domainStates) === null || _state$domainStates === void 0 ? void 0 : _state$domainStates.liveChatConfig;
23
+ const persistentHistoryEnabled = shouldLoadPersistentChatHistory(extendedChatConfig);
24
+ const isActiveOrInactive = state.appStates.conversationState === ConversationState.Active || state.appStates.conversationState === ConversationState.InActive;
25
+ const isPostchatSurvey = state.appStates.conversationState === ConversationState.Postchat && state.appStates.isConversationalSurveyEnabled && state.appStates.isConversationalSurvey;
26
+ if (persistentHistoryEnabled) {
27
+ // Keep WebChat in DOM even when minimized (CSS hides it via visibility)
28
+ // This preserves scroll position across minimize/maximize cycles
29
+ return isActiveOrInactive || isPostchatSurvey;
30
+ }
31
+ // Original behavior - hide WebChat when minimized
32
+ return !state.appStates.isMinimized && (isActiveOrInactive || isPostchatSurvey);
21
33
  };
22
34
  export const shouldShowLoadingPane = state => {
23
35
  return !state.appStates.isMinimized && state.appStates.conversationState === ConversationState.Loading;
@@ -14,6 +14,8 @@ export const createOnNewAdapterActivityHandler = (chatId, userId, startTime) =>
14
14
  // this is a workaround for the fact that we dont have a way to identify if a message is history or new, and it will provide consistency across different scenarios
15
15
 
16
16
  let isHistoryMessageReceivedEventRaised = false;
17
+ let isInQueue = false;
18
+ let agentJoinLogged = false;
17
19
  const onNewAdapterActivityHandler = activity => {
18
20
  raiseMessageEvent(activity);
19
21
  };
@@ -35,9 +37,14 @@ export const createOnNewAdapterActivityHandler = (chatId, userId, startTime) =>
35
37
  });
36
38
  };
37
39
  const systemMessageStrategy = activity => {
38
- var _TelemetryManager$Int3;
40
+ var _activity$channelData, _activity$channelData2, _TelemetryManager$Int3;
39
41
  const payload = buildMessagePayload(activity, userId);
40
42
  payload.messageType = Constants.systemMessageTag;
43
+
44
+ // Track queue entry for agent-join detection
45
+ if (activity !== null && activity !== void 0 && (_activity$channelData = activity.channelData) !== null && _activity$channelData !== void 0 && (_activity$channelData2 = _activity$channelData.tags) !== null && _activity$channelData2 !== void 0 && _activity$channelData2.includes(Constants.queuePositionMessageTag)) {
46
+ isInQueue = true;
47
+ }
41
48
  if (isHistoryMessage(activity, startTime)) {
42
49
  var _TelemetryManager$Int2;
43
50
  historyMessageStrategy(polyfillMessagePayloadForEvent(activity, payload, (_TelemetryManager$Int2 = TelemetryManager.InternalTelemetryData) === null || _TelemetryManager$Int2 === void 0 ? void 0 : _TelemetryManager$Int2.conversationId));
@@ -70,11 +77,11 @@ export const createOnNewAdapterActivityHandler = (chatId, userId, startTime) =>
70
77
  }
71
78
  };
72
79
  const isValidMessage = activity => {
73
- var _activity$channelData, _activity$channelData2, _activity$channelData3;
80
+ var _activity$channelData3, _activity$channelData4, _activity$channelData5;
74
81
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
75
82
  const messageHasNoText = !(activity !== null && activity !== void 0 && activity.text);
76
83
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
77
- const messageHasNoTags = !(activity !== null && activity !== void 0 && activity.channelData) || !(activity !== null && activity !== void 0 && (_activity$channelData = activity.channelData) !== null && _activity$channelData !== void 0 && _activity$channelData.tags) || (activity === null || activity === void 0 ? void 0 : (_activity$channelData2 = activity.channelData) === null || _activity$channelData2 === void 0 ? void 0 : (_activity$channelData3 = _activity$channelData2.tags) === null || _activity$channelData3 === void 0 ? void 0 : _activity$channelData3.length) === 0;
84
+ const messageHasNoTags = !(activity !== null && activity !== void 0 && activity.channelData) || !(activity !== null && activity !== void 0 && (_activity$channelData3 = activity.channelData) !== null && _activity$channelData3 !== void 0 && _activity$channelData3.tags) || (activity === null || activity === void 0 ? void 0 : (_activity$channelData4 = activity.channelData) === null || _activity$channelData4 === void 0 ? void 0 : (_activity$channelData5 = _activity$channelData4.tags) === null || _activity$channelData5 === void 0 ? void 0 : _activity$channelData5.length) === 0;
78
85
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
79
86
  const messageHasNoAttachments = !(activity !== null && activity !== void 0 && activity.attachments) || (activity === null || activity === void 0 ? void 0 : activity.attachments.length) === 0;
80
87
  if (messageHasNoTags && messageHasNoText && messageHasNoAttachments) {
@@ -96,6 +103,16 @@ export const createOnNewAdapterActivityHandler = (chatId, userId, startTime) =>
96
103
  historyMessageStrategy(polyfillMessagePayloadForEvent(activity, payload, (_TelemetryManager$Int4 = TelemetryManager.InternalTelemetryData) === null || _TelemetryManager$Int4 === void 0 ? void 0 : _TelemetryManager$Int4.conversationId));
97
104
  return;
98
105
  }
106
+
107
+ // Log agent joining when first non-history message is received after queue
108
+ if (isInQueue && !agentJoinLogged) {
109
+ agentJoinLogged = true;
110
+ isInQueue = false;
111
+ TelemetryHelper.logActionEvent(LogLevel.INFO, {
112
+ Event: TelemetryEvent.AgentJoinedConversation,
113
+ Description: "First message received from agent after queue"
114
+ });
115
+ }
99
116
  firstResponseLatencyTracker.stopClock(payload);
100
117
  const newMessageReceivedEvent = {
101
118
  eventName: BroadcastEvent.NewMessageReceived,
@@ -58,6 +58,8 @@ export declare class Constants {
58
58
  static readonly videoMediaRegex: RegExp;
59
59
  static readonly chromeSupportedInlineMediaRegex: RegExp;
60
60
  static readonly firefoxSupportedInlineMediaRegex: RegExp;
61
+ static readonly AdaptiveCardType = "adaptivecard";
62
+ static readonly SuggestedActionsType = "suggestedactions";
61
63
  static readonly CallAdded = "callAdded";
62
64
  static readonly LocalVideoStreamAdded = "localVideoStreamAdded";
63
65
  static readonly LocalVideoStreamRemoved = "localVideoStreamRemoved";