@microsoft/omnichannel-chat-widget 1.8.2-main.e3c1d40 → 1.8.2-main.f638bed

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 (71) hide show
  1. package/README.md +48 -1
  2. package/lib/cjs/common/Constants.js +16 -3
  3. package/lib/cjs/common/telemetry/TelemetryConstants.js +2 -0
  4. package/lib/cjs/common/utils.js +27 -2
  5. package/lib/cjs/components/chatbuttonstateful/ChatButtonStateful.js +4 -4
  6. package/lib/cjs/components/draggable/DraggableChatWidget.js +16 -1
  7. package/lib/cjs/components/livechatwidget/common/createInternetConnectionChangeHandler.js +22 -9
  8. package/lib/cjs/components/livechatwidget/common/customEventHandler.js +53 -0
  9. package/lib/cjs/components/livechatwidget/common/endChat.js +18 -7
  10. package/lib/cjs/components/livechatwidget/common/initWebChatComposer.js +32 -1
  11. package/lib/cjs/components/livechatwidget/common/liveChatConfigUtils.js +18 -1
  12. package/lib/cjs/components/livechatwidget/common/renderSurveyHelpers.js +31 -7
  13. package/lib/cjs/components/livechatwidget/common/setPostChatContextAndLoadSurvey.js +15 -2
  14. package/lib/cjs/components/livechatwidget/common/startChat.js +6 -4
  15. package/lib/cjs/components/livechatwidget/livechatwidgetstateful/LiveChatWidgetStateful.js +27 -12
  16. package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/channelDataMiddleware.js +4 -3
  17. package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/conversationEndMiddleware.js +12 -6
  18. package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/customEventMiddleware.js +41 -0
  19. package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/queueOverflowHandlerMiddleware.js +45 -0
  20. package/lib/cjs/contexts/common/CustomEventType.js +1 -0
  21. package/lib/cjs/contexts/common/LiveChatWidgetActionType.js +3 -1
  22. package/lib/cjs/contexts/common/LiveChatWidgetContextInitialState.js +3 -1
  23. package/lib/cjs/contexts/createReducer.js +30 -0
  24. package/lib/cjs/controller/componentController.js +2 -2
  25. package/lib/cjs/firstresponselatency/FirstMessageTrackerFromBot.js +101 -36
  26. package/lib/cjs/firstresponselatency/FirstResponseLatencyTracker.js +39 -21
  27. package/lib/cjs/firstresponselatency/util.js +60 -31
  28. package/lib/cjs/plugins/newMessageEventHandler.js +12 -6
  29. package/lib/esm/common/Constants.js +14 -2
  30. package/lib/esm/common/telemetry/TelemetryConstants.js +2 -0
  31. package/lib/esm/common/utils.js +21 -0
  32. package/lib/esm/components/chatbuttonstateful/ChatButtonStateful.js +4 -4
  33. package/lib/esm/components/draggable/DraggableChatWidget.js +16 -1
  34. package/lib/esm/components/livechatwidget/common/createInternetConnectionChangeHandler.js +22 -9
  35. package/lib/esm/components/livechatwidget/common/customEventHandler.js +45 -0
  36. package/lib/esm/components/livechatwidget/common/endChat.js +18 -7
  37. package/lib/esm/components/livechatwidget/common/initWebChatComposer.js +32 -1
  38. package/lib/esm/components/livechatwidget/common/liveChatConfigUtils.js +16 -0
  39. package/lib/esm/components/livechatwidget/common/renderSurveyHelpers.js +33 -9
  40. package/lib/esm/components/livechatwidget/common/setPostChatContextAndLoadSurvey.js +16 -3
  41. package/lib/esm/components/livechatwidget/common/startChat.js +6 -4
  42. package/lib/esm/components/livechatwidget/livechatwidgetstateful/LiveChatWidgetStateful.js +27 -12
  43. package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/channelDataMiddleware.js +4 -4
  44. package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/conversationEndMiddleware.js +12 -6
  45. package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/customEventMiddleware.js +33 -0
  46. package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/queueOverflowHandlerMiddleware.js +38 -0
  47. package/lib/esm/contexts/common/CustomEventType.js +1 -0
  48. package/lib/esm/contexts/common/LiveChatWidgetActionType.js +3 -1
  49. package/lib/esm/contexts/common/LiveChatWidgetContextInitialState.js +3 -1
  50. package/lib/esm/contexts/createReducer.js +30 -0
  51. package/lib/esm/controller/componentController.js +2 -2
  52. package/lib/esm/firstresponselatency/FirstMessageTrackerFromBot.js +101 -36
  53. package/lib/esm/firstresponselatency/FirstResponseLatencyTracker.js +39 -21
  54. package/lib/esm/firstresponselatency/util.js +57 -29
  55. package/lib/esm/plugins/newMessageEventHandler.js +12 -6
  56. package/lib/types/common/Constants.d.ts +13 -2
  57. package/lib/types/common/telemetry/TelemetryConstants.d.ts +2 -0
  58. package/lib/types/common/utils.d.ts +8 -0
  59. package/lib/types/components/livechatwidget/common/customEventHandler.d.ts +4 -0
  60. package/lib/types/components/livechatwidget/common/liveChatConfigUtils.d.ts +1 -0
  61. package/lib/types/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/channelDataMiddleware.d.ts +1 -1
  62. package/lib/types/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/conversationEndMiddleware.d.ts +1 -1
  63. package/lib/types/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/customEventMiddleware.d.ts +22 -0
  64. package/lib/types/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/queueOverflowHandlerMiddleware.d.ts +5 -0
  65. package/lib/types/contexts/common/CustomEventType.d.ts +6 -0
  66. package/lib/types/contexts/common/ILiveChatWidgetContext.d.ts +2 -0
  67. package/lib/types/contexts/common/LiveChatWidgetActionType.d.ts +3 -1
  68. package/lib/types/firstresponselatency/FirstResponseLatencyTracker.d.ts +2 -2
  69. package/lib/types/firstresponselatency/util.d.ts +17 -0
  70. package/lib/types/plugins/newMessageEventHandler.d.ts +1 -1
  71. package/package.json +4 -4
@@ -11,27 +11,33 @@ import { MessageTypes } from "../../enums/MessageType";
11
11
  import { WebChatActionType } from "../../enums/WebChatActionType";
12
12
 
13
13
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
14
- const createConversationEndMiddleware = conversationEndCallback => _ref => {
14
+ const createConversationEndMiddleware = (conversationEndCallback, startConversationalSurveyCallback, endConversationalSurveyCallback) => _ref => {
15
15
  let {
16
16
  dispatch
17
17
  } = _ref;
18
18
  return next => action => {
19
19
  var _action$payload;
20
20
  if ((action === null || action === void 0 ? void 0 : action.type) == WebChatActionType.DIRECT_LINE_INCOMING_ACTIVITY && (_action$payload = action.payload) !== null && _action$payload !== void 0 && _action$payload.activity) {
21
- var _activity$from2, _activity$channelData7, _activity$channelData8;
21
+ var _activity$from2, _activity$channelData17, _activity$channelData18;
22
22
  const activity = action.payload.activity;
23
23
  if (activity.channelId === "ACS_CHANNEL") {
24
24
  var _activity$from;
25
25
  if (((_activity$from = activity.from) === null || _activity$from === void 0 ? void 0 : _activity$from.role) === DirectLineSenderRole.Bot) {
26
- var _activity$channelData, _activity$channelData2, _activity$channelData3, _activity$channelData4, _activity$channelData5, _activity$channelData6;
26
+ var _activity$channelData, _activity$channelData2, _activity$channelData3, _activity$channelData4, _activity$channelData5, _activity$channelData6, _activity$channelData7, _activity$channelData8, _activity$channelData9, _activity$channelData10, _activity$channelData11, _activity$channelData12, _activity$channelData13, _activity$channelData14, _activity$channelData15, _activity$channelData16;
27
27
  if ((_activity$channelData = activity.channelData) !== null && _activity$channelData !== void 0 && (_activity$channelData2 = _activity$channelData.tags) !== null && _activity$channelData2 !== void 0 && _activity$channelData2.includes(Constants.systemMessageTag) && ((_activity$channelData3 = activity.channelData) !== null && _activity$channelData3 !== void 0 && (_activity$channelData4 = _activity$channelData3.tags) !== null && _activity$channelData4 !== void 0 && _activity$channelData4.includes(Constants.agentEndConversationMessageTag) || (_activity$channelData5 = activity.channelData) !== null && _activity$channelData5 !== void 0 && (_activity$channelData6 = _activity$channelData5.tags) !== null && _activity$channelData6 !== void 0 && _activity$channelData6.includes(Constants.supervisorForceCloseMessageTag))) {
28
28
  conversationEndCallback();
29
29
  }
30
+ if ((_activity$channelData7 = activity.channelData) !== null && _activity$channelData7 !== void 0 && (_activity$channelData8 = _activity$channelData7.tags) !== null && _activity$channelData8 !== void 0 && _activity$channelData8.includes(Constants.systemMessageTag) && ((_activity$channelData9 = activity.channelData) !== null && _activity$channelData9 !== void 0 && (_activity$channelData10 = _activity$channelData9.tags) !== null && _activity$channelData10 !== void 0 && _activity$channelData10.includes(Constants.startConversationalSurveyMessageTag) || (_activity$channelData11 = activity.channelData) !== null && _activity$channelData11 !== void 0 && (_activity$channelData12 = _activity$channelData11.tags) !== null && _activity$channelData12 !== void 0 && _activity$channelData12.includes(Constants.startConversationalSurveyMessageTag))) {
31
+ startConversationalSurveyCallback();
32
+ }
33
+ if ((_activity$channelData13 = activity.channelData) !== null && _activity$channelData13 !== void 0 && (_activity$channelData14 = _activity$channelData13.tags) !== null && _activity$channelData14 !== void 0 && _activity$channelData14.includes(Constants.systemMessageTag) && (_activity$channelData15 = activity.channelData) !== null && _activity$channelData15 !== void 0 && (_activity$channelData16 = _activity$channelData15.tags) !== null && _activity$channelData16 !== void 0 && _activity$channelData16.includes(Constants.endConversationalSurveyMessageTag)) {
34
+ endConversationalSurveyCallback();
35
+ }
30
36
  }
31
- } else if (((_activity$from2 = activity.from) === null || _activity$from2 === void 0 ? void 0 : _activity$from2.role) === DirectLineSenderRole.Channel && ((_activity$channelData7 = activity.channelData) === null || _activity$channelData7 === void 0 ? void 0 : _activity$channelData7.type) === MessageTypes.Thread && (_activity$channelData8 = activity.channelData) !== null && _activity$channelData8 !== void 0 && _activity$channelData8.properties) {
32
- var _activity$channelData9, _activity$channelData10, _activity$channelData11, _activity$channelData12;
37
+ } else if (((_activity$from2 = activity.from) === null || _activity$from2 === void 0 ? void 0 : _activity$from2.role) === DirectLineSenderRole.Channel && ((_activity$channelData17 = activity.channelData) === null || _activity$channelData17 === void 0 ? void 0 : _activity$channelData17.type) === MessageTypes.Thread && (_activity$channelData18 = activity.channelData) !== null && _activity$channelData18 !== void 0 && _activity$channelData18.properties) {
38
+ var _activity$channelData19, _activity$channelData20, _activity$channelData21, _activity$channelData22;
33
39
  // IC3
34
- if (((_activity$channelData9 = activity.channelData) === null || _activity$channelData9 === void 0 ? void 0 : (_activity$channelData10 = _activity$channelData9.properties) === null || _activity$channelData10 === void 0 ? void 0 : _activity$channelData10.isdeleted) === Constants.truePascal || !((_activity$channelData11 = activity.channelData) !== null && _activity$channelData11 !== void 0 && (_activity$channelData12 = _activity$channelData11.properties) !== null && _activity$channelData12 !== void 0 && _activity$channelData12.containsExternalEntitiesListeningAll)) {
40
+ if (((_activity$channelData19 = activity.channelData) === null || _activity$channelData19 === void 0 ? void 0 : (_activity$channelData20 = _activity$channelData19.properties) === null || _activity$channelData20 === void 0 ? void 0 : _activity$channelData20.isdeleted) === Constants.truePascal || !((_activity$channelData21 = activity.channelData) !== null && _activity$channelData21 !== void 0 && (_activity$channelData22 = _activity$channelData21.properties) !== null && _activity$channelData22 !== void 0 && _activity$channelData22.containsExternalEntitiesListeningAll)) {
35
41
  conversationEndCallback();
36
42
  }
37
43
  }
@@ -0,0 +1,33 @@
1
+ /******
2
+ * CustomEventMiddleware
3
+ *
4
+ * This middleware is invoked when a custom event is received.
5
+ * The callback is then invoked to handle the custom event.
6
+ ******/
7
+
8
+ import { Constants } from "../../../../../common/Constants";
9
+ import { WebChatActionType } from "../../enums/WebChatActionType";
10
+ export const isValidCustomEvent = activity => {
11
+ var _activity$channelData, _activity$channelData2, _activity$channelData3, _activity$channelData4, _activity$channelData5, _activity$channelData6, _activity$channelData7, _activity$from, _activity$channelData8, _activity$channelData9, _activity$channelData10, _activity$channelData11;
12
+ return !!(activity !== null && activity !== void 0 && (_activity$channelData = activity.channelData) !== null && _activity$channelData !== void 0 && (_activity$channelData2 = _activity$channelData.metadata) !== null && _activity$channelData2 !== void 0 && _activity$channelData2.customEvent && typeof (activity === null || activity === void 0 ? void 0 : (_activity$channelData3 = activity.channelData) === null || _activity$channelData3 === void 0 ? void 0 : (_activity$channelData4 = _activity$channelData3.metadata) === null || _activity$channelData4 === void 0 ? void 0 : _activity$channelData4.customEvent) === Constants.String && (activity === null || activity === void 0 ? void 0 : (_activity$channelData5 = activity.channelData) === null || _activity$channelData5 === void 0 ? void 0 : (_activity$channelData6 = _activity$channelData5.metadata) === null || _activity$channelData6 === void 0 ? void 0 : (_activity$channelData7 = _activity$channelData6.customEvent) === null || _activity$channelData7 === void 0 ? void 0 : _activity$channelData7.toLowerCase()) === Constants.true && (activity === null || activity === void 0 ? void 0 : (_activity$from = activity.from) === null || _activity$from === void 0 ? void 0 : _activity$from.role) !== Constants.userMessageTag && typeof (activity === null || activity === void 0 ? void 0 : (_activity$channelData8 = activity.channelData) === null || _activity$channelData8 === void 0 ? void 0 : (_activity$channelData9 = _activity$channelData8.metadata) === null || _activity$channelData9 === void 0 ? void 0 : _activity$channelData9.customEventName) === Constants.String && activity !== null && activity !== void 0 && (_activity$channelData10 = activity.channelData) !== null && _activity$channelData10 !== void 0 && (_activity$channelData11 = _activity$channelData10.metadata) !== null && _activity$channelData11 !== void 0 && _activity$channelData11.customEventValue);
13
+ };
14
+ const createCustomEventMiddleware = broadcastservice => () => next => action => {
15
+ var _action$payload;
16
+ if ((action === null || action === void 0 ? void 0 : action.type) == WebChatActionType.DIRECT_LINE_INCOMING_ACTIVITY && (_action$payload = action.payload) !== null && _action$payload !== void 0 && _action$payload.activity) {
17
+ const activity = action.payload.activity;
18
+ if (isValidCustomEvent(activity)) {
19
+ const customEvent = {
20
+ eventName: Constants.onCustomEvent,
21
+ payload: {
22
+ messageId: activity.messageid ?? activity.id,
23
+ customEventName: activity.channelData.metadata.customEventName,
24
+ customEventValue: activity.channelData.metadata.customEventValue
25
+ }
26
+ };
27
+ broadcastservice.postMessage(customEvent);
28
+ return;
29
+ }
30
+ }
31
+ return next(action);
32
+ };
33
+ export default createCustomEventMiddleware;
@@ -0,0 +1,38 @@
1
+ import { WebChatActionType } from "../../enums/WebChatActionType";
2
+ import { LogLevel, TelemetryEvent } from "../../../../../common/telemetry/TelemetryConstants";
3
+ import { TelemetryHelper } from "../../../../../common/telemetry/TelemetryHelper";
4
+ import { LiveChatWidgetActionType } from "../../../../../contexts/common/LiveChatWidgetActionType";
5
+ import { executeReducer } from "../../../../../contexts/createReducer";
6
+ import { isEndConversationDueToOverflowActivity } from "../../../../../common/utils";
7
+ const queueOverflowHandlingHelper = async (state, dispatch) => {
8
+ const {
9
+ appStates
10
+ } = executeReducer(state, {
11
+ type: LiveChatWidgetActionType.GET_IN_MEMORY_STATE,
12
+ payload: undefined
13
+ });
14
+ if (!appStates.chatDisconnectEventReceived) {
15
+ dispatch({
16
+ type: LiveChatWidgetActionType.SET_CHAT_DISCONNECT_EVENT_RECEIVED,
17
+ payload: true
18
+ });
19
+ TelemetryHelper.logActionEvent(LogLevel.INFO, {
20
+ Event: TelemetryEvent.QueueOverflowEvent,
21
+ Description: "Set chat disconnect event received."
22
+ });
23
+ }
24
+ };
25
+ export const createQueueOverflowMiddleware = (state, dispatch) => () => next => action => {
26
+ var _action$payload;
27
+ if ((action === null || action === void 0 ? void 0 : action.type) == WebChatActionType.DIRECT_LINE_INCOMING_ACTIVITY && (_action$payload = action.payload) !== null && _action$payload !== void 0 && _action$payload.activity) {
28
+ const activity = action.payload.activity;
29
+ if (isEndConversationDueToOverflowActivity(activity)) {
30
+ TelemetryHelper.logActionEvent(LogLevel.INFO, {
31
+ Event: TelemetryEvent.QueueOverflowEvent,
32
+ Description: "Queue overflow event received."
33
+ });
34
+ queueOverflowHandlingHelper(state, dispatch);
35
+ }
36
+ }
37
+ return next(action);
38
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -45,5 +45,7 @@ export let LiveChatWidgetActionType;
45
45
  LiveChatWidgetActionType[LiveChatWidgetActionType["SET_SURVEY_MODE"] = 42] = "SET_SURVEY_MODE";
46
46
  LiveChatWidgetActionType[LiveChatWidgetActionType["SET_CONFIRMATION_STATE"] = 43] = "SET_CONFIRMATION_STATE";
47
47
  LiveChatWidgetActionType[LiveChatWidgetActionType["SET_POST_CHAT_PARTICIPANT_TYPE"] = 44] = "SET_POST_CHAT_PARTICIPANT_TYPE";
48
- LiveChatWidgetActionType[LiveChatWidgetActionType["GET_IN_MEMORY_STATE"] = 45] = "GET_IN_MEMORY_STATE";
48
+ LiveChatWidgetActionType[LiveChatWidgetActionType["SET_CONVERSATIONAL_SURVEY_ENABLED"] = 45] = "SET_CONVERSATIONAL_SURVEY_ENABLED";
49
+ LiveChatWidgetActionType[LiveChatWidgetActionType["SET_CONVERSATIONAL_SURVEY_DISPLAY"] = 46] = "SET_CONVERSATIONAL_SURVEY_DISPLAY";
50
+ LiveChatWidgetActionType[LiveChatWidgetActionType["GET_IN_MEMORY_STATE"] = 47] = "GET_IN_MEMORY_STATE";
49
51
  })(LiveChatWidgetActionType || (LiveChatWidgetActionType = {}));
@@ -77,7 +77,9 @@ export const getLiveChatWidgetContextInitialState = props => {
77
77
  conversationEndedBy: ConversationEndEntity.NotSet,
78
78
  chatDisconnectEventReceived: false,
79
79
  selectedSurveyMode: null,
80
- postChatParticipantType: undefined
80
+ postChatParticipantType: undefined,
81
+ isConversationalSurvey: false,
82
+ isConversationalSurveyEnabled: false
81
83
  },
82
84
  uiStates: {
83
85
  showConfirmationPane: false,
@@ -648,6 +648,36 @@ const reducer = (state, action) => {
648
648
  postChatParticipantType: action.payload
649
649
  }
650
650
  };
651
+ case LiveChatWidgetActionType.SET_CONVERSATIONAL_SURVEY_ENABLED:
652
+ inMemory = {
653
+ ...inMemory,
654
+ appStates: {
655
+ ...inMemory.appStates,
656
+ isConversationalSurveyEnabled: action.payload
657
+ }
658
+ };
659
+ return {
660
+ ...state,
661
+ appStates: {
662
+ ...state.appStates,
663
+ isConversationalSurveyEnabled: action.payload
664
+ }
665
+ };
666
+ case LiveChatWidgetActionType.SET_CONVERSATIONAL_SURVEY_DISPLAY:
667
+ inMemory = {
668
+ ...inMemory,
669
+ appStates: {
670
+ ...inMemory.appStates,
671
+ isConversationalSurvey: action.payload
672
+ }
673
+ };
674
+ return {
675
+ ...state,
676
+ appStates: {
677
+ ...state.appStates,
678
+ isConversationalSurvey: action.payload
679
+ }
680
+ };
651
681
  case LiveChatWidgetActionType.GET_IN_MEMORY_STATE:
652
682
  return inMemory;
653
683
  default:
@@ -17,7 +17,7 @@ export const shouldShowEmailTranscriptPane = state => {
17
17
  return state.uiStates.showEmailTranscriptPane;
18
18
  };
19
19
  export const shouldShowWebChatContainer = state => {
20
- return !state.appStates.isMinimized && state.appStates.conversationState === ConversationState.Active || state.appStates.conversationState === ConversationState.InActive;
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
21
  };
22
22
  export const shouldShowLoadingPane = state => {
23
23
  return !state.appStates.isMinimized && state.appStates.conversationState === ConversationState.Loading;
@@ -42,7 +42,7 @@ export const shouldShowConfirmationPane = state => {
42
42
  return state.uiStates.showConfirmationPane;
43
43
  };
44
44
  export const shouldShowPostChatSurveyPane = state => {
45
- return state.appStates.conversationState === ConversationState.Postchat;
45
+ return state.appStates.conversationState === ConversationState.Postchat && !state.appStates.isConversationalSurvey;
46
46
  };
47
47
  export const shouldShowCallingContainer = state => {
48
48
  return state.appStates.conversationState === ConversationState.Active && state.appStates.e2vvEnabled;
@@ -7,54 +7,96 @@ import { createTrackingMessage } from "./util";
7
7
  // with different timeline, therefore this is a functional approach to track the events, instead of a class based approach
8
8
  export const createTrackingForFirstMessage = () => {
9
9
  // Reset the tracking variables
10
- let startTracking = false;
11
- let stopTracking = false;
10
+ let isTracking = false;
12
11
  let startTime = 0;
13
12
  let stopTime = 0;
14
- let stopTrackingMessage;
13
+ let stopTrackingMessage = null;
15
14
  let flag = false;
15
+ let trackingTimeoutId;
16
+
17
+ /**
18
+ * Checks if the message payload is from a valid sender (not an agent).
19
+ * Returns false if the message is from an agent (tag 'public'), true otherwise.
20
+ */
16
21
  const isMessageFromValidSender = payload => {
17
22
  var _payload$tags;
18
- // agent scenario
19
23
  if (payload !== null && payload !== void 0 && (_payload$tags = payload.tags) !== null && _payload$tags !== void 0 && _payload$tags.includes("public")) {
20
24
  return false;
21
25
  }
22
26
  return true;
23
27
  };
28
+
29
+ /**
30
+ * Listener for widget load completion event.
31
+ * Starts tracking the time for the first bot message after widget loads.
32
+ * Sets a 5-second timeout to auto-reset if no bot message is received.
33
+ */
24
34
  const widgetLoadListener = BroadcastService.getMessageByEventName(TelemetryEvent.WidgetLoadComplete).subscribe(() => {
25
- if (startTracking) return;
26
- startTracking = true;
35
+ if (isTracking) return;
36
+ isTracking = true;
27
37
  startTime = new Date().getTime();
38
+ // Start a 5-second timeout to auto-stop tracking if not stopped
39
+ if (trackingTimeoutId) {
40
+ clearTimeout(trackingTimeoutId);
41
+ }
42
+ trackingTimeoutId = setTimeout(() => {
43
+ if (isTracking) {
44
+ // Reset state and disengage, no telemetry or FMLTrackingCompleted
45
+ isTracking = false;
46
+ startTime = 0;
47
+ stopTime = 0;
48
+ stopTrackingMessage = null;
49
+ trackingTimeoutId = undefined;
50
+ disconnectListener();
51
+ }
52
+ }, 10000); //adding more time since it meassures from widget load complete till message received
28
53
  });
54
+
55
+ /**
56
+ * Listener for new bot message event.
57
+ * If a valid bot message is received, stops tracking and logs telemetry.
58
+ * If the message is invalid, resets and disengages listeners.
59
+ */
29
60
  const newMessageListener = BroadcastService.getMessageByEventName(BroadcastEvent.NewMessageReceived).subscribe(message => {
30
61
  const payload = message.payload;
31
-
32
- // we only care for bot, so we need to check if the message is from the bot
33
- // pending to add typing message indicator signal detection
34
-
35
- if (isMessageFromValidSender(payload)) {
36
- if (startTracking && !stopTracking) {
37
- stopTime = new Date().getTime();
38
- const elapsedTime = stopTime - startTime;
39
- stopTracking = true;
40
- stopTrackingMessage = createTrackingMessage(payload, "botMessage");
41
- notifyFMLTrackingCompleted();
42
- TelemetryHelper.logActionEvent(LogLevel.INFO, {
43
- Event: TelemetryEvent.BotFirstMessageLapTrack,
44
- Description: "First Message from Bot latency tracking",
45
- CustomProperties: {
46
- elapsedTime,
47
- widgetLoadedAt: startTime,
48
- botMessage: stopTrackingMessage
49
- }
50
- });
62
+ if (!isMessageFromValidSender(payload)) {
63
+ // If not valid, stop everything and clean up
64
+ isTracking = false;
65
+ if (trackingTimeoutId) {
66
+ clearTimeout(trackingTimeoutId);
67
+ trackingTimeoutId = undefined;
51
68
  }
69
+ disconnectListener();
70
+ return;
71
+ }
72
+ if (isTracking) {
73
+ isTracking = false;
74
+ // Clear the timeout if it exists
75
+ if (trackingTimeoutId) {
76
+ clearTimeout(trackingTimeoutId);
77
+ trackingTimeoutId = undefined;
78
+ }
79
+ stopTime = new Date().getTime();
80
+ const elapsedTime = stopTime - startTime;
81
+ stopTrackingMessage = createTrackingMessage(payload, "botMessage");
82
+ notifyFMLTrackingCompleted();
83
+ TelemetryHelper.logActionEvent(LogLevel.INFO, {
84
+ Event: TelemetryEvent.BotFirstMessageLapTrack,
85
+ Description: "First Message from Bot latency tracking",
86
+ CustomProperties: {
87
+ elapsedTime,
88
+ widgetLoadedAt: startTime,
89
+ botMessage: stopTrackingMessage
90
+ }
91
+ });
92
+ disconnectListener();
52
93
  }
53
-
54
- // this track only first message, if coming from the bot or not
55
- // the only difference is that it logs only those from bot
56
- disconnectListener();
57
94
  });
95
+
96
+ /**
97
+ * Notifies that FML (First Message Latency) tracking is completed.
98
+ * Retries sending the completion event until acknowledged.
99
+ */
58
100
  const notifyFMLTrackingCompleted = () => {
59
101
  ackListener();
60
102
  // Retry sending until flag is true, but do not block the main thread
@@ -69,6 +111,11 @@ export const createTrackingForFirstMessage = () => {
69
111
  }
70
112
  }, 100);
71
113
  };
114
+
115
+ /**
116
+ * Listener for FMLTrackingCompletedAck event.
117
+ * Sets the flag to true when acknowledgment is received.
118
+ */
72
119
  const ackListener = () => {
73
120
  const listen = BroadcastService.getMessageByEventName(BroadcastEvent.FMLTrackingCompletedAck).subscribe(() => {
74
121
  flag = true;
@@ -78,22 +125,32 @@ export const createTrackingForFirstMessage = () => {
78
125
 
79
126
  // Rehydrate message is received when the widget is reloaded, this is to ensure that we are not tracking messages that are not part of the current conversation
80
127
  // No need to keep listerning for tracking, enforcing disconnection for the listners
128
+ /**
129
+ * Listener for widget rehydration event.
130
+ * Resets tracking and disconnects listeners when widget is reloaded.
131
+ */
81
132
  const rehydrateListener = BroadcastService.getMessageByEventName(TelemetryEvent.RehydrateMessageReceived).subscribe(() => {
82
- startTracking = false;
83
- stopTracking = false;
133
+ isTracking = false;
84
134
  disconnectListener();
85
135
  });
86
136
 
87
137
  // Rehydrate message is received when the widget is reloaded, this is to ensure that we are not tracking messages that are not part of the current conversation
88
138
  // No need to keep listerning for tracking, enforcing disconnection for the listners
139
+ /**
140
+ * Listener for history message event.
141
+ * Resets tracking and disconnects listeners when history is loaded.
142
+ */
89
143
  const historyListener = BroadcastService.getMessageByEventName(BroadcastEvent.HistoryMessageReceived).subscribe(() => {
90
- startTracking = false;
91
- stopTracking = false;
144
+ isTracking = false;
92
145
  disconnectListener();
93
146
  });
147
+
148
+ /**
149
+ * Listener for network disconnection event.
150
+ * Resets tracking, disconnects listeners, and logs a telemetry error.
151
+ */
94
152
  const offlineNetworkListener = BroadcastService.getMessageByEventName(TelemetryEvent.NetworkDisconnected).subscribe(() => {
95
- startTracking = false;
96
- stopTracking = false;
153
+ isTracking = false;
97
154
  disconnectListener();
98
155
  TelemetryHelper.logActionEvent(LogLevel.INFO, {
99
156
  Event: TelemetryEvent.BotFirstMessageLapTrackError,
@@ -102,7 +159,15 @@ export const createTrackingForFirstMessage = () => {
102
159
  });
103
160
 
104
161
  // this is to ensure that we are not tracking messages that are not part of the current conversation
162
+ /**
163
+ * Disconnects all listeners and clears the tracking timeout.
164
+ * Used for cleanup when tracking is stopped or reset.
165
+ */
105
166
  const disconnectListener = () => {
167
+ if (trackingTimeoutId) {
168
+ clearTimeout(trackingTimeoutId);
169
+ trackingTimeoutId = undefined;
170
+ }
106
171
  historyListener.unsubscribe();
107
172
  rehydrateListener.unsubscribe();
108
173
  newMessageListener.unsubscribe();
@@ -11,14 +11,13 @@ export let FirstResponseLatencyTracker = /*#__PURE__*/function () {
11
11
  function FirstResponseLatencyTracker() {
12
12
  _classCallCheck(this, FirstResponseLatencyTracker);
13
13
  _defineProperty(this, "isABotConversation", false);
14
- _defineProperty(this, "isStarted", false);
15
- _defineProperty(this, "isEnded", false);
14
+ _defineProperty(this, "isTracking", false);
16
15
  _defineProperty(this, "startTrackingMessage", void 0);
17
16
  _defineProperty(this, "stopTrackingMessage", void 0);
18
17
  _defineProperty(this, "isReady", false);
18
+ _defineProperty(this, "trackingTimeoutId", void 0);
19
19
  _defineProperty(this, "offlineNetworkListener", BroadcastService.getMessageByEventName(TelemetryEvent.NetworkDisconnected).subscribe(() => {
20
- this.isStarted = false;
21
- this.isEnded = false;
20
+ this.isTracking = false;
22
21
  TelemetryHelper.logActionEvent(LogLevel.INFO, {
23
22
  Event: TelemetryEvent.MessageStopLapTrackError,
24
23
  Description: "Tracker Stopped due to network disconnection"
@@ -66,7 +65,7 @@ export let FirstResponseLatencyTracker = /*#__PURE__*/function () {
66
65
  value: function startTracking(payload) {
67
66
  if (!this.isReady) return;
68
67
  // this prevents to initiate tracking for multiple incoming messages
69
- if (this.isStarted) {
68
+ if (this.isTracking) {
70
69
  return;
71
70
  }
72
71
  // this is to ensure we track only messages where bot is engaged
@@ -74,10 +73,24 @@ export let FirstResponseLatencyTracker = /*#__PURE__*/function () {
74
73
  return;
75
74
  }
76
75
  // control of states to prevent clashing of messages
77
- this.isStarted = true;
78
- this.isEnded = false;
76
+ this.isTracking = true;
79
77
  // The idea of using types is to enrich telemetry data
80
78
  this.startTrackingMessage = this.createTrackingMessage(payload, "userMessage");
79
+
80
+ // Start a 5-second timeout to auto-stop tracking if not stopped
81
+ if (this.trackingTimeoutId) {
82
+ clearTimeout(this.trackingTimeoutId);
83
+ }
84
+ this.trackingTimeoutId = setTimeout(() => {
85
+ // this means the start process is in progress, but the end wasn't called within the time limit
86
+ if (this.isTracking) {
87
+ // Reset state variables and skip stopTracking
88
+ this.isTracking = false;
89
+ this.startTrackingMessage = undefined;
90
+ this.stopTrackingMessage = undefined;
91
+ this.trackingTimeoutId = undefined;
92
+ }
93
+ }, 5000);
81
94
  }
82
95
  }, {
83
96
  key: "handleAgentMessage",
@@ -93,13 +106,16 @@ export let FirstResponseLatencyTracker = /*#__PURE__*/function () {
93
106
  value: function stopTracking(payload) {
94
107
  var _this$stopTrackingMes, _this$startTrackingMe;
95
108
  // this prevents execution for multiple incoming messages from the bot.
96
- if (this.isEnded && !this.isStarted) {
109
+ if (!this.isTracking) {
97
110
  return;
98
111
  }
99
-
112
+ // Clear the timeout if it exists
113
+ if (this.trackingTimeoutId) {
114
+ clearTimeout(this.trackingTimeoutId);
115
+ this.trackingTimeoutId = undefined;
116
+ }
100
117
  // control of states to prevent clashing of messages
101
- this.isEnded = true;
102
- this.isStarted = false;
118
+ this.isTracking = false;
103
119
 
104
120
  // The idea of using types is to enrich telemetry data
105
121
  this.stopTrackingMessage = this.createTrackingMessage(payload, "botMessage");
@@ -154,12 +170,16 @@ export let FirstResponseLatencyTracker = /*#__PURE__*/function () {
154
170
  if (!payload || !payload.Id) {
155
171
  throw new Error("Invalid payload");
156
172
  }
157
- if (!this.isMessageFromValidSender(payload)) return;
158
- if (this.isABotConversation && this.isStarted) {
173
+
174
+ // Only allow stopTracking if sender is valid and tracking is active
175
+ if (!this.isMessageFromValidSender(payload)) {
176
+ // Do not change isTracking or stopTrackingMessage
177
+ return;
178
+ }
179
+ if (this.isABotConversation && this.isTracking) {
159
180
  this.stopTracking(payload);
160
181
  }
161
182
  } catch (e) {
162
- console.error("FRL : error while trying to stop the tracker", e);
163
183
  TelemetryHelper.logActionEvent(LogLevel.ERROR, {
164
184
  Event: TelemetryEvent.MessageStopLapTrackError,
165
185
  Description: "Error while stopping the clock",
@@ -168,11 +188,6 @@ export let FirstResponseLatencyTracker = /*#__PURE__*/function () {
168
188
  payload: payload
169
189
  }
170
190
  });
171
- //reset state
172
- this.startTrackingMessage = undefined;
173
- this.stopTrackingMessage = undefined;
174
- this.isStarted = false;
175
- this.isEnded = false;
176
191
  }
177
192
  }
178
193
  }, {
@@ -180,10 +195,13 @@ export let FirstResponseLatencyTracker = /*#__PURE__*/function () {
180
195
  value: function deregister() {
181
196
  // Reset State
182
197
  this.isABotConversation = false;
183
- this.isStarted = false;
184
- this.isEnded = false;
198
+ this.isTracking = false;
185
199
  this.startTrackingMessage = undefined;
186
200
  this.stopTrackingMessage = undefined;
201
+ if (this.trackingTimeoutId) {
202
+ clearTimeout(this.trackingTimeoutId);
203
+ this.trackingTimeoutId = undefined;
204
+ }
187
205
  this.offlineNetworkListener.unsubscribe();
188
206
  this.fmltrackingListener.unsubscribe();
189
207
  this.rehydrateListener.unsubscribe();
@@ -1,38 +1,66 @@
1
1
  import { ScenarioType } from "./Constants";
2
2
  import { Constants } from "../common/Constants";
3
+ const DELTA_WITHIN_LIMITS_IN_MS = 250;
4
+
5
+ /**
6
+ * Determines whether a given activity is a historical message.
7
+ *
8
+ * This function checks if the activity is a message type and uses a combination
9
+ * of legacy tags and timestamp-based logic to determine if the message is historical.
10
+ *
11
+ * @param {IActivity} activity - The activity object to evaluate.
12
+ * @param {number} startTime - The start time (in milliseconds since epoch) to compare against.
13
+ * @returns {boolean} - Returns true if the activity is a historical message, false otherwise.
14
+ *
15
+ * Logic:
16
+ * - If the activity type is not a message, it is not historical.
17
+ * - If the activity contains a legacy history message tag, it is considered historical.
18
+ * - Otherwise, the function extracts a timestamp from the activity ID using `extractTimestampFromId`.
19
+ * - If the ID is valid and the timestamp is older than the start time, the message is historical.
20
+ */
3
21
  export const isHistoryMessage = (activity, startTime) => {
4
- try {
5
- if ((activity === null || activity === void 0 ? void 0 : activity.type) === Constants.message) {
6
- var _activity$channelData, _activity$channelData2;
7
- // this is an old piece of code, probably no longer relevant
8
- 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.historyMessageTag)) return true;
22
+ var _activity$channelData;
23
+ // Only process message activities
24
+ if ((activity === null || activity === void 0 ? void 0 : activity.type) !== Constants.message) {
25
+ return false;
26
+ }
9
27
 
10
- // Id is an epoch time in milliseconds , in utc format, for some reason is in a string format
11
- if (activity !== null && activity !== void 0 && activity.id) {
12
- /// activity.id is an string that contains epoch time in milliseconds
13
- const activityId = parseInt(activity === null || activity === void 0 ? void 0 : activity.id);
28
+ // Prioritize legacy history tag
29
+ if (activity !== null && activity !== void 0 && (_activity$channelData = activity.channelData) !== null && _activity$channelData !== void 0 && _activity$channelData.tags && activity.channelData.tags.includes(Constants.historyMessageTag)) {
30
+ return true;
31
+ }
32
+ const activityId = extractTimestampFromId(activity);
33
+ const isValidId = !isNaN(activityId) && activityId > 0;
34
+ const difference = startTime - activityId;
35
+ // Only consider historical if activityId < startTime and difference >= DELTA_WITHIN_LIMITS_IN_MS
36
+ const isOlderThanStartTime = activityId < startTime && difference >= DELTA_WITHIN_LIMITS_IN_MS;
37
+ const isHistoryById = isValidId && isOlderThanStartTime;
38
+ return isHistoryById;
39
+ };
40
+ export const extractTimestampFromId = activity => {
41
+ const id = (activity === null || activity === void 0 ? void 0 : activity.id) ?? "";
14
42
 
15
- // if the activity id is not a number, we default to new message
16
- if (isNaN(activityId)) {
17
- return false;
18
- }
43
+ // Helper function to get timestamp fallback
44
+ const getTimestampFallback = () => {
45
+ const timestamp = new Date((activity === null || activity === void 0 ? void 0 : activity.timestamp) ?? "").getTime();
46
+ return isNaN(timestamp) ? 0 : timestamp;
47
+ };
19
48
 
20
- // if the activity id is less than the start time, it means that the message is a history message
21
- if (activityId < startTime) {
22
- return true;
23
- }
24
- }
25
- // anything else will be considered a new message
26
- return false;
27
- }
28
- } catch (e) {
29
- // if there is an error in parsing the activity id, we will consider it a new message
30
- console.error("Error in parsing activity id: ", e);
49
+ // Check if ID looks like a UUID (contains dashes or is very long)
50
+ const UUID_LENGTH_THRESHOLD = 13; // Threshold to distinguish UUIDs from epoch timestamps
51
+ if (id.includes("-") || id.length > UUID_LENGTH_THRESHOLD) {
52
+ // Likely UUID, use timestamp instead
53
+ return getTimestampFallback();
54
+ }
55
+ const activityId = parseInt(id);
56
+ // if activity id is not a number, then we use timestamp field
57
+ if (isNaN(activityId)) {
58
+ return getTimestampFallback();
31
59
  }
32
- return false;
60
+ return activityId;
33
61
  };
34
62
  export const buildMessagePayload = (activity, userId) => {
35
- var _text, _text2, _activity$channelData3, _activity$from;
63
+ var _text, _text2, _activity$channelData2, _activity$from;
36
64
  return {
37
65
  // To identify hidden contents vs empty content
38
66
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -40,7 +68,7 @@ export const buildMessagePayload = (activity, userId) => {
40
68
  type: activity === null || activity === void 0 ? void 0 : activity.type,
41
69
  timestamp: activity === null || activity === void 0 ? void 0 : activity.timestamp,
42
70
  userId: userId,
43
- tags: activity === null || activity === void 0 ? void 0 : (_activity$channelData3 = activity.channelData) === null || _activity$channelData3 === void 0 ? void 0 : _activity$channelData3.tags,
71
+ tags: (activity === null || activity === void 0 ? void 0 : (_activity$channelData2 = activity.channelData) === null || _activity$channelData2 === void 0 ? void 0 : _activity$channelData2.tags) || [],
44
72
  messageType: "",
45
73
  Id: activity === null || activity === void 0 ? void 0 : activity.id,
46
74
  role: activity === null || activity === void 0 ? void 0 : (_activity$from = activity.from) === null || _activity$from === void 0 ? void 0 : _activity$from.role,
@@ -64,9 +92,9 @@ export const polyfillMessagePayloadForEvent = (activity, payload, conversationId
64
92
  };
65
93
  };
66
94
  export const getScenarioType = activity => {
67
- var _activity$from3, _activity$channelData4;
95
+ var _activity$from3, _activity$channelData3;
68
96
  const role = activity === null || activity === void 0 ? void 0 : (_activity$from3 = activity.from) === null || _activity$from3 === void 0 ? void 0 : _activity$from3.role;
69
- const tags = activity === null || activity === void 0 ? void 0 : (_activity$channelData4 = activity.channelData) === null || _activity$channelData4 === void 0 ? void 0 : _activity$channelData4.tags;
97
+ const tags = activity === null || activity === void 0 ? void 0 : (_activity$channelData3 = activity.channelData) === null || _activity$channelData3 === void 0 ? void 0 : _activity$channelData3.tags;
70
98
  if (role === Constants.userMessageTag) {
71
99
  return ScenarioType.UserSendMessageStrategy;
72
100
  }