@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
@@ -2,6 +2,17 @@ import { LogLevel, TelemetryEvent } from "../../../common/telemetry/TelemetryCon
2
2
  import { TelemetryHelper } from "../../../common/telemetry/TelemetryHelper";
3
3
  import { WidgetLoadCustomErrorString } from "../../../common/Constants";
4
4
  import { isNullOrEmptyString } from "../../../common/utils";
5
+
6
+ /**
7
+ * Check if mid-auth is enabled based on chatConfig.
8
+ * Mid-auth flag lives under LiveWSAndLiveChatEngJoin.msdyn_authenticatedsigninoptional.
9
+ */
10
+ const isMidAuthEnabled = chatConfig => {
11
+ var _chatConfig$LiveWSAnd, _value$toString, _value$toString$call$, _value$toString$call;
12
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
+ const value = chatConfig === null || chatConfig === void 0 ? void 0 : (_chatConfig$LiveWSAnd = chatConfig.LiveWSAndLiveChatEngJoin) === null || _chatConfig$LiveWSAnd === void 0 ? void 0 : _chatConfig$LiveWSAnd.msdyn_authenticatedsigninoptional;
14
+ return (value === null || value === void 0 ? void 0 : (_value$toString = value.toString) === null || _value$toString === void 0 ? void 0 : (_value$toString$call$ = (_value$toString$call = _value$toString.call(value)).toLowerCase) === null || _value$toString$call$ === void 0 ? void 0 : _value$toString$call$.call(_value$toString$call)) === "true";
15
+ };
5
16
  const getAuthClientFunction = chatConfig => {
6
17
  let authClientFunction = undefined;
7
18
  if (chatConfig !== null && chatConfig !== void 0 && chatConfig.LiveChatConfigAuthSettings) {
@@ -12,12 +23,17 @@ const getAuthClientFunction = chatConfig => {
12
23
  };
13
24
  const handleAuthentication = async (chatSDK, chatConfig, getAuthToken) => {
14
25
  var _chatSDK$chatSDKConfi;
26
+ const midAuthEnabled = isMidAuthEnabled(chatConfig);
15
27
  const authClientFunction = getAuthClientFunction(chatConfig);
16
28
  if (getAuthToken && authClientFunction) {
17
29
  TelemetryHelper.logActionEvent(LogLevel.INFO, {
18
30
  Event: TelemetryEvent.GetAuthTokenCalled
19
31
  });
20
- const token = await getAuthToken(authClientFunction);
32
+
33
+ // Only pass isMidAuthEnabled option when mid-auth is enabled.
34
+ const token = midAuthEnabled ? await getAuthToken(authClientFunction, {
35
+ isMidAuthEnabled: midAuthEnabled
36
+ }) : await getAuthToken(authClientFunction);
21
37
  if (!isNullOrEmptyString(token)) {
22
38
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
39
  chatSDK.setAuthTokenProvider(async () => {
@@ -28,8 +44,21 @@ const handleAuthentication = async (chatSDK, chatConfig, getAuthToken) => {
28
44
  "token": token
29
45
  };
30
46
  } else {
31
- // instead of returning false, it's more appropiate to thrown an error to force error handling on the caller side
32
- // this will help to avoid the error to be ignored and the chat to be started
47
+ // For mid-auth scenarios, empty token means "user not signed in" - this is expected behavior.
48
+ // Return result: true with empty token so caller can decide to proceed unauthenticated.
49
+ if (midAuthEnabled) {
50
+ // Expected behavior for mid-auth: user not signed in
51
+ TelemetryHelper.logActionEvent(LogLevel.INFO, {
52
+ Event: TelemetryEvent.GetAuthTokenCalled,
53
+ Description: "Mid-auth: token provider returned empty; user not signed in"
54
+ });
55
+ return {
56
+ "result": true,
57
+ "token": null
58
+ };
59
+ }
60
+
61
+ // For non-mid-auth scenarios, empty token is an error
33
62
  TelemetryHelper.logActionEvent(LogLevel.ERROR, {
34
63
  Event: TelemetryEvent.ReceivedNullOrEmptyToken
35
64
  });
@@ -39,6 +68,17 @@ const handleAuthentication = async (chatSDK, chatConfig, getAuthToken) => {
39
68
  var _chatSDK$chatSDKConfi2;
40
69
  const token = await ((_chatSDK$chatSDKConfi2 = chatSDK.chatSDKConfig) === null || _chatSDK$chatSDKConfi2 === void 0 ? void 0 : _chatSDK$chatSDKConfi2.getAuthToken());
41
70
  if (isNullOrEmptyString(token)) {
71
+ // For mid-auth scenarios, empty token from SDK's getAuthToken is also expected
72
+ if (midAuthEnabled) {
73
+ TelemetryHelper.logActionEvent(LogLevel.INFO, {
74
+ Event: TelemetryEvent.GetAuthTokenCalled,
75
+ Description: "Mid-auth: SDK getAuthToken returned empty; user not signed in"
76
+ });
77
+ return {
78
+ "result": true,
79
+ "token": null
80
+ };
81
+ }
42
82
  TelemetryHelper.logActionEvent(LogLevel.ERROR, {
43
83
  Event: TelemetryEvent.ReceivedNullOrEmptyToken,
44
84
  Description: "getAuthToken in chat SDK returns empty string"
@@ -66,4 +106,4 @@ const removeAuthTokenProvider = chatSDK => {
66
106
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
67
107
  chatSDK.authenticatedUserToken = null;
68
108
  };
69
- export { getAuthClientFunction, handleAuthentication, removeAuthTokenProvider };
109
+ export { getAuthClientFunction, handleAuthentication, removeAuthTokenProvider, isMidAuthEnabled };
@@ -39,10 +39,11 @@ export const createAdapter = async (facadeChatSDK, props) => {
39
39
  //so far, there is no need to convert to the shim adapter when using visual tests
40
40
  const isMocked = facadeChatSDK.getChatSDK() instanceof MockChatSDK;
41
41
  if (isMocked !== true) {
42
- var _props$webChatContain, _props$webChatContain2, _props$webChatContain3, _props$webChatContain4;
42
+ var _props$webChatContain, _props$webChatContain2, _props$webChatContain3, _props$webChatContain4, _props$webChatContain5, _props$webChatContain6;
43
43
  const botAuthActivitySubscriberOptionalParams = {
44
44
  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,
45
- 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
45
+ 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,
46
+ 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
46
47
  };
47
48
  adapter = new ChatAdapterShim(adapter);
48
49
  adapter.addSubscriber(new AddActivitySubscriber());
@@ -1,11 +1,13 @@
1
+ import { isNullOrUndefined, parseBooleanFromConfig } from "../../../common/utils";
1
2
  import { ConversationMode } from "../../../common/Constants";
2
- import { isNullOrUndefined } from "../../../common/utils";
3
3
  export const isPostChatSurveyEnabled = async facadeChatSDK => {
4
4
  var _chatConfig$LiveWSAnd;
5
5
  const chatConfig = await facadeChatSDK.getLiveChatConfig();
6
6
  const postChatEnabled = (_chatConfig$LiveWSAnd = chatConfig.LiveWSAndLiveChatEngJoin) === null || _chatConfig$LiveWSAnd === void 0 ? void 0 : _chatConfig$LiveWSAnd.msdyn_postconversationsurveyenable.toString().toLowerCase();
7
7
  return postChatEnabled === "true";
8
8
  };
9
+
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9
11
  export const getPostChatSurveyConfig = async facadeChatSDK => {
10
12
  var _chatConfig$LiveWSAnd2, _chatConfig$LiveWSAnd3, _chatConfig$LiveWSAnd4, _chatConfig$LiveWSAnd5, _chatConfig$LiveWSAnd6, _chatConfig$LiveWSAnd7, _chatConfig$LiveWSAnd8, _chatConfig$LiveWSAnd9, _chatConfig$LiveWSAnd10;
11
13
  const chatConfig = await facadeChatSDK.getLiveChatConfig();
@@ -26,5 +28,34 @@ export const isPersistentChatEnabled = conversationMode => {
26
28
  if (isNullOrUndefined(conversationMode)) {
27
29
  return false;
28
30
  }
29
- return (conversationMode === null || conversationMode === void 0 ? void 0 : conversationMode.toString().toLowerCase()) === ConversationMode.Persistent;
31
+ return (conversationMode === null || conversationMode === void 0 ? void 0 : conversationMode.toString()) === ConversationMode.Persistent;
32
+ };
33
+
34
+ /**
35
+ * Determines if persistent chat history should be loaded based on all required conditions.
36
+ *
37
+ * @param extendedChatConfig - The extended chat configuration object
38
+ * @returns true if ALL conditions are met:
39
+ * 1. Conversation mode must be Persistent ("192350001")
40
+ * 2. History is enabled in admin config (msdyn_enablepersistentchatpreviousconversations)
41
+ * 3. History is enabled via feature flag (lcwPersistentChatHistoryEnabled)
42
+ */
43
+ export const shouldLoadPersistentChatHistory = extendedChatConfig => {
44
+ var _extendedChatConfig$L, _extendedChatConfig$L2, _extendedChatConfig$L3;
45
+ // CRITICAL: First check if conversation mode is persistent
46
+ // Only persistent mode ("192350001") should allow history loading
47
+ const isPersistentChatEnabledForWidget = isPersistentChatEnabled(extendedChatConfig === null || extendedChatConfig === void 0 ? void 0 : (_extendedChatConfig$L = extendedChatConfig.LiveWSAndLiveChatEngJoin) === null || _extendedChatConfig$L === void 0 ? void 0 : _extendedChatConfig$L.msdyn_conversationmode);
48
+ if (!isPersistentChatEnabledForWidget) {
49
+ return false;
50
+ }
51
+
52
+ // Check if history is enabled in admin config (handles both boolean and string "true"/"false")
53
+ const isHistoryEnabledInConfig = parseBooleanFromConfig(extendedChatConfig === null || extendedChatConfig === void 0 ? void 0 : (_extendedChatConfig$L2 = extendedChatConfig.LiveWSAndLiveChatEngJoin) === null || _extendedChatConfig$L2 === void 0 ? void 0 : _extendedChatConfig$L2.msdyn_enablepersistentchatpreviousconversations);
54
+ if (!isHistoryEnabledInConfig) {
55
+ return false;
56
+ }
57
+
58
+ // Check if history is enabled via feature flag (handles both boolean and string "true"/"false")
59
+ const isHistoryEnabledViaFCB = parseBooleanFromConfig(extendedChatConfig === null || extendedChatConfig === void 0 ? void 0 : (_extendedChatConfig$L3 = extendedChatConfig.LcwFcbConfiguration) === null || _extendedChatConfig$L3 === void 0 ? void 0 : _extendedChatConfig$L3.lcwPersistentChatHistoryEnabled);
60
+ return isHistoryEnabledViaFCB;
30
61
  };
@@ -14,6 +14,7 @@ import { createAdapter } from "./createAdapter";
14
14
  import { createOnNewAdapterActivityHandler } from "../../../plugins/newMessageEventHandler";
15
15
  import { createTrackingForFirstMessage } from "../../../firstresponselatency/FirstMessageTrackerFromBot";
16
16
  import { isPersistentChatEnabled } from "./liveChatConfigUtils";
17
+ import { isMidAuthEnabled } from "./authHelper";
17
18
  import { setPostChatContextAndLoadSurvey } from "./setPostChatContextAndLoadSurvey";
18
19
  import { shouldSetPreChatIfPersistentChat } from "./persistentChatHelper";
19
20
  import { updateTelemetryData } from "./updateSessionDataForTelemetry";
@@ -59,7 +60,7 @@ const prepareStartChat = async (props, facadeChatSDK, state, dispatch, setAdapte
59
60
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
60
61
  const setPreChatAndInitiateChat = async (facadeChatSDK, dispatch, setAdapter, isProactiveChat, proactiveChatEnablePrechatState, state, props) => {
61
62
  var _props$preChatSurveyP, _props$preChatSurveyP2, _props$controlProps, _state$domainStates, _state$domainStates$l, _state$domainStates$l2, _state$appStates2;
62
- // This reset needs to be done before to load prechat, because the conversation state changes from close to prechat
63
+ // Reset before loading prechat to avoid starting chat with previous requestId
63
64
  if ((state === null || state === void 0 ? void 0 : state.appStates.conversationState) === ConversationState.Closed) {
64
65
  // Preventive reset to avoid starting chat with previous requestId which could potentially cause problems
65
66
  chatSDKStateCleanUp(facadeChatSDK.getChatSDK());
@@ -192,7 +193,7 @@ const initStartChat = async (facadeChatSDK, dispatch, setAdapter, state, props,
192
193
  });
193
194
  }
194
195
  try {
195
- var _window$Microsoft, _window$Microsoft$Dyn, _window$Microsoft$Dyn2, _window$Microsoft$Dyn3;
196
+ var _window$Microsoft, _window$Microsoft$Dyn, _window$Microsoft$Dyn2, _window$Microsoft$Dyn3, _state$domainStates8;
196
197
  // Set custom context params
197
198
  await setCustomContextParams(state, props);
198
199
  const defaultOptionalParams = {
@@ -201,9 +202,25 @@ const initStartChat = async (facadeChatSDK, dispatch, setAdapter, state, props,
201
202
  portalContactId: (_window$Microsoft = window.Microsoft) === null || _window$Microsoft === void 0 ? void 0 : (_window$Microsoft$Dyn = _window$Microsoft.Dynamic365) === null || _window$Microsoft$Dyn === void 0 ? void 0 : (_window$Microsoft$Dyn2 = _window$Microsoft$Dyn.Portal) === null || _window$Microsoft$Dyn2 === void 0 ? void 0 : (_window$Microsoft$Dyn3 = _window$Microsoft$Dyn2.User) === null || _window$Microsoft$Dyn3 === void 0 ? void 0 : _window$Microsoft$Dyn3.contactId
202
203
  };
203
204
  const startChatOptionalParams = Object.assign({}, params, optionalParams, defaultOptionalParams);
205
+
206
+ // MID-AUTH: Add wasAuthenticated flag for reconnect scenarios
207
+ // Tells FacadeChatSDK whether the previous session was authenticated
208
+ // Used to detect auth transitions (Auth->Unauth) and decide whether to call authenticateChat
209
+ const midAuthEnabled = isMidAuthEnabled(state === null || state === void 0 ? void 0 : (_state$domainStates8 = state.domainStates) === null || _state$domainStates8 === void 0 ? void 0 : _state$domainStates8.liveChatConfig);
210
+ if (midAuthEnabled) {
211
+ var _state$appStates4, _persistedState$appSt;
212
+ const isUserAuthenticated = (state === null || state === void 0 ? void 0 : (_state$appStates4 = state.appStates) === null || _state$appStates4 === void 0 ? void 0 : _state$appStates4.isUserAuthenticated) === true || (persistedState === null || persistedState === void 0 ? void 0 : (_persistedState$appSt = persistedState.appStates) === null || _persistedState$appSt === void 0 ? void 0 : _persistedState$appSt.isUserAuthenticated) === true;
213
+ startChatOptionalParams.wasAuthenticated = isUserAuthenticated;
214
+ }
215
+
204
216
  // startTime is used to determine if a message is history or new, better to be set before creating the adapter to get bandwidth
205
217
  const startTime = new Date().getTime();
206
218
  createTrackingForFirstMessage();
219
+
220
+ // FacadeChatSDK.startChat() handles:
221
+ // 1. tokenRing() - checks authentication, calls handleAuthentication() if needed
222
+ // 2. Mid-auth: If no token, sets deferInitialAuth=true for unauthenticated flow
223
+ // 3. Mid-auth: On reconnect with valid token, calls authenticateChat to upgrade conversation
207
224
  await facadeChatSDK.startChat(startChatOptionalParams);
208
225
  logStartChatComplete();
209
226
  isStartChatSuccessful = true;
@@ -290,15 +307,15 @@ const createAdapterAndSubscribe = async (facadeChatSDK, dispatch, setAdapter, st
290
307
  };
291
308
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
292
309
  const canConnectToExistingChat = async (props, facadeChatSDK, state, dispatch, setAdapter) => {
293
- var _state$appStates4, _persistedState$domai6, _persistedState$appSt;
310
+ var _state$appStates5, _persistedState$domai6, _persistedState$appSt2;
294
311
  // By pass this function in case of popout chat
295
- if ((state === null || state === void 0 ? void 0 : (_state$appStates4 = state.appStates) === null || _state$appStates4 === void 0 ? void 0 : _state$appStates4.hideStartChatButton) === true) {
312
+ if ((state === null || state === void 0 ? void 0 : (_state$appStates5 = state.appStates) === null || _state$appStates5 === void 0 ? void 0 : _state$appStates5.hideStartChatButton) === true) {
296
313
  return false;
297
314
  }
298
315
  const persistedState = getStateFromCache(getWidgetCacheIdfromProps(props));
299
316
 
300
317
  //Connect to only active chat session
301
- if (persistedState && !isUndefinedOrEmpty(persistedState === null || persistedState === void 0 ? void 0 : (_persistedState$domai6 = persistedState.domainStates) === null || _persistedState$domai6 === void 0 ? void 0 : _persistedState$domai6.liveChatContext) && (persistedState === null || persistedState === void 0 ? void 0 : (_persistedState$appSt = persistedState.appStates) === null || _persistedState$appSt === void 0 ? void 0 : _persistedState$appSt.conversationState) === ConversationState.Active) {
318
+ if (persistedState && !isUndefinedOrEmpty(persistedState === null || persistedState === void 0 ? void 0 : (_persistedState$domai6 = persistedState.domainStates) === null || _persistedState$domai6 === void 0 ? void 0 : _persistedState$domai6.liveChatContext) && (persistedState === null || persistedState === void 0 ? void 0 : (_persistedState$appSt2 = persistedState.appStates) === null || _persistedState$appSt2 === void 0 ? void 0 : _persistedState$appSt2.conversationState) === ConversationState.Active) {
302
319
  var _persistedState$domai7;
303
320
  dispatch({
304
321
  type: LiveChatWidgetActionType.SET_CONVERSATION_STATE,
@@ -315,11 +332,11 @@ const canConnectToExistingChat = async (props, facadeChatSDK, state, dispatch, s
315
332
 
316
333
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
317
334
  const setCustomContextParams = async (state, props) => {
318
- var _state$domainStates8, _persistedState$domai8;
319
- if (state !== null && state !== void 0 && (_state$domainStates8 = state.domainStates) !== null && _state$domainStates8 !== void 0 && _state$domainStates8.customContext) {
320
- var _state$domainStates9;
335
+ var _state$domainStates9, _persistedState$domai8;
336
+ if (state !== null && state !== void 0 && (_state$domainStates9 = state.domainStates) !== null && _state$domainStates9 !== void 0 && _state$domainStates9.customContext) {
337
+ var _state$domainStates10;
321
338
  optionalParams = Object.assign({}, optionalParams, {
322
- customContext: JSON.parse(JSON.stringify(state === null || state === void 0 ? void 0 : (_state$domainStates9 = state.domainStates) === null || _state$domainStates9 === void 0 ? void 0 : _state$domainStates9.customContext))
339
+ customContext: JSON.parse(JSON.stringify(state === null || state === void 0 ? void 0 : (_state$domainStates10 = state.domainStates) === null || _state$domainStates10 === void 0 ? void 0 : _state$domainStates10.customContext))
323
340
  });
324
341
  return;
325
342
  }
@@ -352,9 +369,9 @@ const canStartPopoutChat = async props => {
352
369
  }
353
370
  popoutWidgetInstanceId = getWidgetCacheIdfromProps(props, true);
354
371
  if (!isNullOrEmptyString(popoutWidgetInstanceId)) {
355
- var _persistedState$domai9, _persistedState$appSt2;
372
+ var _persistedState$domai9, _persistedState$appSt3;
356
373
  const persistedState = getStateFromCache(popoutWidgetInstanceId);
357
- if (persistedState && !isUndefinedOrEmpty(persistedState === null || persistedState === void 0 ? void 0 : (_persistedState$domai9 = persistedState.domainStates) === null || _persistedState$domai9 === void 0 ? void 0 : _persistedState$domai9.liveChatContext) && (persistedState === null || persistedState === void 0 ? void 0 : (_persistedState$appSt2 = persistedState.appStates) === null || _persistedState$appSt2 === void 0 ? void 0 : _persistedState$appSt2.conversationState) === ConversationState.Active) {
374
+ if (persistedState && !isUndefinedOrEmpty(persistedState === null || persistedState === void 0 ? void 0 : (_persistedState$domai9 = persistedState.domainStates) === null || _persistedState$domai9 === void 0 ? void 0 : _persistedState$domai9.liveChatContext) && (persistedState === null || persistedState === void 0 ? void 0 : (_persistedState$appSt3 = persistedState.appStates) === null || _persistedState$appSt3 === void 0 ? void 0 : _persistedState$appSt3.conversationState) === ConversationState.Active) {
358
375
  // Initiate popout chat
359
376
  BroadcastService.postMessage({
360
377
  eventName: BroadcastEvent.InitiateStartChatInPopoutMode
@@ -367,9 +384,9 @@ const canStartPopoutChat = async props => {
367
384
 
368
385
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
369
386
  const checkIfConversationStillValid = async (facadeChatSDK, dispatch, state) => {
370
- var _state$domainStates10, _state$domainStates11, _state$domainStates12;
371
- const requestIdFromCache = (_state$domainStates10 = state.domainStates) === null || _state$domainStates10 === void 0 ? void 0 : (_state$domainStates11 = _state$domainStates10.liveChatContext) === null || _state$domainStates11 === void 0 ? void 0 : _state$domainStates11.requestId;
372
- const liveChatContext = state === null || state === void 0 ? void 0 : (_state$domainStates12 = state.domainStates) === null || _state$domainStates12 === void 0 ? void 0 : _state$domainStates12.liveChatContext;
387
+ var _state$domainStates11, _state$domainStates12, _state$domainStates13;
388
+ const requestIdFromCache = (_state$domainStates11 = state.domainStates) === null || _state$domainStates11 === void 0 ? void 0 : (_state$domainStates12 = _state$domainStates11.liveChatContext) === null || _state$domainStates12 === void 0 ? void 0 : _state$domainStates12.requestId;
389
+ const liveChatContext = state === null || state === void 0 ? void 0 : (_state$domainStates13 = state.domainStates) === null || _state$domainStates13 === void 0 ? void 0 : _state$domainStates13.liveChatContext;
373
390
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
374
391
  let conversationDetails = undefined;
375
392
  // Preserve current requestId
@@ -50,6 +50,7 @@ import { initConfirmationPropsComposer } from "../common/initConfirmationPropsCo
50
50
  import { initWebChatComposer } from "../common/initWebChatComposer";
51
51
  import { registerBroadcastServiceForStorage } from "../../../common/storage/default/defaultCacheManager";
52
52
  import { setPostChatContextAndLoadSurvey } from "../common/setPostChatContextAndLoadSurvey";
53
+ import { shouldLoadPersistentChatHistory } from "../common/liveChatConfigUtils";
53
54
  import { startProactiveChat } from "../common/startProactiveChat";
54
55
  import useChatAdapterStore from "../../../hooks/useChatAdapterStore";
55
56
  import useChatContextStore from "../../../hooks/useChatContextStore";
@@ -728,7 +729,15 @@ export const LiveChatWidgetStateful = props => {
728
729
  if (state.appStates.isMinimized) {
729
730
  ActivityStreamHandler.cork();
730
731
  } else {
731
- setTimeout(() => ActivityStreamHandler.uncork(), 500);
732
+ var _state$domainStates4;
733
+ const extendedChatConfig = state === null || state === void 0 ? void 0 : (_state$domainStates4 = state.domainStates) === null || _state$domainStates4 === void 0 ? void 0 : _state$domainStates4.liveChatConfig;
734
+ if (shouldLoadPersistentChatHistory(extendedChatConfig)) {
735
+ requestAnimationFrame(() => {
736
+ setTimeout(() => ActivityStreamHandler.uncork(), 500);
737
+ });
738
+ } else {
739
+ setTimeout(() => ActivityStreamHandler.uncork(), 500);
740
+ }
732
741
  }
733
742
  }, [state.appStates.isMinimized]);
734
743
 
@@ -806,7 +815,7 @@ export const LiveChatWidgetStateful = props => {
806
815
 
807
816
  // In conversational survey, we need to check post chat survey logics before we set ConversationState to InActive
808
817
  // Hence setting ConversationState to InActive will be done later in the post chat flows
809
- if (!isConversationalSurveyEnabled && (inMemoryState === null || inMemoryState === void 0 ? void 0 : (_inMemoryState$appSta12 = inMemoryState.appStates) === null || _inMemoryState$appSta12 === void 0 ? void 0 : _inMemoryState$appSta12.conversationEndedBy) === ConversationEndEntity.Agent || (inMemoryState === null || inMemoryState === void 0 ? void 0 : (_inMemoryState$appSta13 = inMemoryState.appStates) === null || _inMemoryState$appSta13 === void 0 ? void 0 : _inMemoryState$appSta13.conversationEndedBy) === ConversationEndEntity.Bot) {
818
+ if (!isConversationalSurveyEnabled && ((inMemoryState === null || inMemoryState === void 0 ? void 0 : (_inMemoryState$appSta12 = inMemoryState.appStates) === null || _inMemoryState$appSta12 === void 0 ? void 0 : _inMemoryState$appSta12.conversationEndedBy) === ConversationEndEntity.Agent || (inMemoryState === null || inMemoryState === void 0 ? void 0 : (_inMemoryState$appSta13 = inMemoryState.appStates) === null || _inMemoryState$appSta13 === void 0 ? void 0 : _inMemoryState$appSta13.conversationEndedBy) === ConversationEndEntity.Bot)) {
810
819
  dispatch({
811
820
  type: LiveChatWidgetActionType.SET_CONVERSATION_STATE,
812
821
  payload: ConversationState.InActive
@@ -867,6 +876,37 @@ export const LiveChatWidgetStateful = props => {
867
876
  }
868
877
  }, [state.appStates.chatDisconnectEventReceived]);
869
878
 
879
+ // Auth state change listeners (broadcast by FacadeChatSDK.startChat())
880
+ useEffect(() => {
881
+ const authSucceededSub = BroadcastService.getMessageByEventName(BroadcastEvent.MidConversationAuthSucceeded).subscribe(msg => {
882
+ var _msg$payload15;
883
+ if (msg !== null && msg !== void 0 && (_msg$payload15 = msg.payload) !== null && _msg$payload15 !== void 0 && _msg$payload15.isAuthenticated) {
884
+ // Only store boolean flag, NOT the token - token is managed by FacadeChatSDK
885
+ dispatch({
886
+ type: LiveChatWidgetActionType.SET_USER_AUTHENTICATED,
887
+ payload: true
888
+ });
889
+ }
890
+ });
891
+
892
+ // Auth reset: only update the boolean flag here.
893
+ // We do NOT clear widget state/cache/adapter because this fires DURING startChat(),
894
+ // which will handle state transitions. FacadeChatSDK has already cleared SDK internals.
895
+ const authResetSub = BroadcastService.getMessageByEventName(BroadcastEvent.MidConversationAuthReset).subscribe(msg => {
896
+ var _msg$payload16;
897
+ if ((msg === null || msg === void 0 ? void 0 : (_msg$payload16 = msg.payload) === null || _msg$payload16 === void 0 ? void 0 : _msg$payload16.isAuthenticated) === false) {
898
+ dispatch({
899
+ type: LiveChatWidgetActionType.SET_USER_AUTHENTICATED,
900
+ payload: false
901
+ });
902
+ }
903
+ });
904
+ return () => {
905
+ authSucceededSub.unsubscribe();
906
+ authResetSub.unsubscribe();
907
+ };
908
+ }, [dispatch]);
909
+
870
910
  // if props state gets updates we need to update the renderingMiddlewareProps in the state
871
911
  useEffect(() => {
872
912
  var _props$webChatContain16;
@@ -885,6 +925,25 @@ export const LiveChatWidgetStateful = props => {
885
925
  }
886
926
  });
887
927
  }, []);
928
+
929
+ // Reliable browser close detection via visibilitychange + sendBeacon
930
+ // visibilitychange fires while the page is still alive (unlike beforeunload),
931
+ // so telemetry calls complete reliably. False positives (tab switch, minimize)
932
+ // are filtered in Kusto by checking if this is the last event in the session.
933
+ useEffect(() => {
934
+ const handleVisibilityChange = () => {
935
+ if (document.visibilityState === "hidden" && state.appStates.conversationState === ConversationState.Active) {
936
+ TelemetryHelper.logActionEvent(LogLevel.INFO, {
937
+ Event: TelemetryEvent.BrowserTabHidden,
938
+ Description: "Browser tab hidden during active conversation"
939
+ });
940
+ }
941
+ };
942
+ document.addEventListener("visibilitychange", handleVisibilityChange);
943
+ return () => {
944
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
945
+ };
946
+ }, [state.appStates.conversationState]);
888
947
  const initiateEndChatOnBrowserUnload = () => {
889
948
  var _DataStoreManager$cli;
890
949
  TelemetryHelper.logActionEvent(LogLevel.INFO, {
@@ -955,6 +1014,7 @@ export const LiveChatWidgetStateful = props => {
955
1014
  // React to dynamic bot avatar initials updates from context
956
1015
  useEffect(() => {
957
1016
  if (state.domainStates.botAvatarInitials && state.domainStates.botAvatarInitials !== webChatStyles.botAvatarInitials) {
1017
+ /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */
958
1018
  setWebChatStyles(styles => ({
959
1019
  ...styles,
960
1020
  botAvatarInitials: state.domainStates.botAvatarInitials
@@ -22,7 +22,7 @@ const generateSurveyInviteLink = function (surveyInviteLink, isEmbed, locale, co
22
22
  return `${surveyInviteLink}&${surveyLinkParams.toString()}`;
23
23
  };
24
24
  export const PostChatSurveyPaneStateful = props => {
25
- var _props$styleProps, _state$appStates, _props$controlProps;
25
+ var _props$styleProps, _state$appStates, _state$domainStates$p, _props$controlProps;
26
26
  useEffect(() => {
27
27
  uiTimer = createTimer();
28
28
  TelemetryHelper.logLoadingEventToAllTelemetry(LogLevel.INFO, {
@@ -39,13 +39,15 @@ export const PostChatSurveyPaneStateful = props => {
39
39
  });
40
40
  let surveyInviteLink = "";
41
41
  const surveyMode = (state === null || state === void 0 ? void 0 : (_state$appStates = state.appStates) === null || _state$appStates === void 0 ? void 0 : _state$appStates.selectedSurveyMode) === PostChatSurveyMode.Embed;
42
- if (state.domainStates.postChatContext.botSurveyInviteLink &&
42
+ if ((_state$domainStates$p = state.domainStates.postChatContext) !== null && _state$domainStates$p !== void 0 && _state$domainStates$p.botSurveyInviteLink &&
43
43
  // Bot survey enabled
44
44
  state.appStates.postChatParticipantType === ParticipantType.Bot) {
45
+ var _state$domainStates$p2, _state$domainStates$p3;
45
46
  // Only Bot has engaged
46
- surveyInviteLink = generateSurveyInviteLink(state.domainStates.postChatContext.botSurveyInviteLink, surveyMode, state.domainStates.postChatContext.botFormsProLocale, props.isCustomerVoiceSurveyCompact ?? true, props.customerVoiceSurveyCorrelationId || "");
47
+ surveyInviteLink = generateSurveyInviteLink((_state$domainStates$p2 = state.domainStates.postChatContext) === null || _state$domainStates$p2 === void 0 ? void 0 : _state$domainStates$p2.botSurveyInviteLink, surveyMode, (_state$domainStates$p3 = state.domainStates.postChatContext) === null || _state$domainStates$p3 === void 0 ? void 0 : _state$domainStates$p3.botFormsProLocale, props.isCustomerVoiceSurveyCompact ?? true, props.customerVoiceSurveyCorrelationId || "");
47
48
  } else {
48
- surveyInviteLink = generateSurveyInviteLink(state.domainStates.postChatContext.surveyInviteLink, surveyMode, state.domainStates.postChatContext.formsProLocale, props.isCustomerVoiceSurveyCompact ?? true, props.customerVoiceSurveyCorrelationId || "");
49
+ var _state$domainStates$p4, _state$domainStates$p5;
50
+ surveyInviteLink = generateSurveyInviteLink((_state$domainStates$p4 = state.domainStates.postChatContext) === null || _state$domainStates$p4 === void 0 ? void 0 : _state$domainStates$p4.surveyInviteLink, surveyMode, (_state$domainStates$p5 = state.domainStates.postChatContext) === null || _state$domainStates$p5 === void 0 ? void 0 : _state$domainStates$p5.formsProLocale, props.isCustomerVoiceSurveyCompact ?? true, props.customerVoiceSurveyCorrelationId || "");
49
51
  }
50
52
  if (props.copilotSurveyContext) {
51
53
  surveyInviteLink = `${surveyInviteLink}&mcs_additionalcontext=${JSON.stringify(props.copilotSurveyContext)}`;
@@ -1,6 +1,6 @@
1
1
  import { BroadcastEvent, LogLevel, TelemetryEvent } from "../../common/telemetry/TelemetryConstants";
2
2
  import React, { useEffect, useState } from "react";
3
- import { createTimer, setFocusOnElement } from "../../common/utils";
3
+ import { createTimer } from "../../common/utils";
4
4
  import { BroadcastService } from "@microsoft/omnichannel-chat-components";
5
5
  import { Constants } from "../../common/Constants";
6
6
  import { ConversationState } from "../../contexts/common/ConversationState";
@@ -114,7 +114,6 @@ export const ProactiveChatPaneStateful = props => {
114
114
  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
115
115
  };
116
116
  useEffect(() => {
117
- setFocusOnElement(document.getElementById(controlProps.id + "-startbutton"));
118
117
  TelemetryTimers.ProactiveChatScreenTimer = createTimer();
119
118
  const timeoutEvent = setTimeout(() => {
120
119
  handleProactiveChatInviteTimeout();