@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
@@ -12,54 +12,96 @@ var _util = require("./util");
12
12
  // with different timeline, therefore this is a functional approach to track the events, instead of a class based approach
13
13
  const createTrackingForFirstMessage = () => {
14
14
  // Reset the tracking variables
15
- let startTracking = false;
16
- let stopTracking = false;
15
+ let isTracking = false;
17
16
  let startTime = 0;
18
17
  let stopTime = 0;
19
- let stopTrackingMessage;
18
+ let stopTrackingMessage = null;
20
19
  let flag = false;
20
+ let trackingTimeoutId;
21
+
22
+ /**
23
+ * Checks if the message payload is from a valid sender (not an agent).
24
+ * Returns false if the message is from an agent (tag 'public'), true otherwise.
25
+ */
21
26
  const isMessageFromValidSender = payload => {
22
27
  var _payload$tags;
23
- // agent scenario
24
28
  if (payload !== null && payload !== void 0 && (_payload$tags = payload.tags) !== null && _payload$tags !== void 0 && _payload$tags.includes("public")) {
25
29
  return false;
26
30
  }
27
31
  return true;
28
32
  };
33
+
34
+ /**
35
+ * Listener for widget load completion event.
36
+ * Starts tracking the time for the first bot message after widget loads.
37
+ * Sets a 5-second timeout to auto-reset if no bot message is received.
38
+ */
29
39
  const widgetLoadListener = _omnichannelChatComponents.BroadcastService.getMessageByEventName(_TelemetryConstants.TelemetryEvent.WidgetLoadComplete).subscribe(() => {
30
- if (startTracking) return;
31
- startTracking = true;
40
+ if (isTracking) return;
41
+ isTracking = true;
32
42
  startTime = new Date().getTime();
43
+ // Start a 5-second timeout to auto-stop tracking if not stopped
44
+ if (trackingTimeoutId) {
45
+ clearTimeout(trackingTimeoutId);
46
+ }
47
+ trackingTimeoutId = setTimeout(() => {
48
+ if (isTracking) {
49
+ // Reset state and disengage, no telemetry or FMLTrackingCompleted
50
+ isTracking = false;
51
+ startTime = 0;
52
+ stopTime = 0;
53
+ stopTrackingMessage = null;
54
+ trackingTimeoutId = undefined;
55
+ disconnectListener();
56
+ }
57
+ }, 10000); //adding more time since it meassures from widget load complete till message received
33
58
  });
59
+
60
+ /**
61
+ * Listener for new bot message event.
62
+ * If a valid bot message is received, stops tracking and logs telemetry.
63
+ * If the message is invalid, resets and disengages listeners.
64
+ */
34
65
  const newMessageListener = _omnichannelChatComponents.BroadcastService.getMessageByEventName(_TelemetryConstants.BroadcastEvent.NewMessageReceived).subscribe(message => {
35
66
  const payload = message.payload;
36
-
37
- // we only care for bot, so we need to check if the message is from the bot
38
- // pending to add typing message indicator signal detection
39
-
40
- if (isMessageFromValidSender(payload)) {
41
- if (startTracking && !stopTracking) {
42
- stopTime = new Date().getTime();
43
- const elapsedTime = stopTime - startTime;
44
- stopTracking = true;
45
- stopTrackingMessage = (0, _util.createTrackingMessage)(payload, "botMessage");
46
- notifyFMLTrackingCompleted();
47
- _TelemetryHelper.TelemetryHelper.logActionEvent(_TelemetryConstants.LogLevel.INFO, {
48
- Event: _TelemetryConstants.TelemetryEvent.BotFirstMessageLapTrack,
49
- Description: "First Message from Bot latency tracking",
50
- CustomProperties: {
51
- elapsedTime,
52
- widgetLoadedAt: startTime,
53
- botMessage: stopTrackingMessage
54
- }
55
- });
67
+ if (!isMessageFromValidSender(payload)) {
68
+ // If not valid, stop everything and clean up
69
+ isTracking = false;
70
+ if (trackingTimeoutId) {
71
+ clearTimeout(trackingTimeoutId);
72
+ trackingTimeoutId = undefined;
56
73
  }
74
+ disconnectListener();
75
+ return;
76
+ }
77
+ if (isTracking) {
78
+ isTracking = false;
79
+ // Clear the timeout if it exists
80
+ if (trackingTimeoutId) {
81
+ clearTimeout(trackingTimeoutId);
82
+ trackingTimeoutId = undefined;
83
+ }
84
+ stopTime = new Date().getTime();
85
+ const elapsedTime = stopTime - startTime;
86
+ stopTrackingMessage = (0, _util.createTrackingMessage)(payload, "botMessage");
87
+ notifyFMLTrackingCompleted();
88
+ _TelemetryHelper.TelemetryHelper.logActionEvent(_TelemetryConstants.LogLevel.INFO, {
89
+ Event: _TelemetryConstants.TelemetryEvent.BotFirstMessageLapTrack,
90
+ Description: "First Message from Bot latency tracking",
91
+ CustomProperties: {
92
+ elapsedTime,
93
+ widgetLoadedAt: startTime,
94
+ botMessage: stopTrackingMessage
95
+ }
96
+ });
97
+ disconnectListener();
57
98
  }
58
-
59
- // this track only first message, if coming from the bot or not
60
- // the only difference is that it logs only those from bot
61
- disconnectListener();
62
99
  });
100
+
101
+ /**
102
+ * Notifies that FML (First Message Latency) tracking is completed.
103
+ * Retries sending the completion event until acknowledged.
104
+ */
63
105
  const notifyFMLTrackingCompleted = () => {
64
106
  ackListener();
65
107
  // Retry sending until flag is true, but do not block the main thread
@@ -74,6 +116,11 @@ const createTrackingForFirstMessage = () => {
74
116
  }
75
117
  }, 100);
76
118
  };
119
+
120
+ /**
121
+ * Listener for FMLTrackingCompletedAck event.
122
+ * Sets the flag to true when acknowledgment is received.
123
+ */
77
124
  const ackListener = () => {
78
125
  const listen = _omnichannelChatComponents.BroadcastService.getMessageByEventName(_TelemetryConstants.BroadcastEvent.FMLTrackingCompletedAck).subscribe(() => {
79
126
  flag = true;
@@ -83,22 +130,32 @@ const createTrackingForFirstMessage = () => {
83
130
 
84
131
  // 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
85
132
  // No need to keep listerning for tracking, enforcing disconnection for the listners
133
+ /**
134
+ * Listener for widget rehydration event.
135
+ * Resets tracking and disconnects listeners when widget is reloaded.
136
+ */
86
137
  const rehydrateListener = _omnichannelChatComponents.BroadcastService.getMessageByEventName(_TelemetryConstants.TelemetryEvent.RehydrateMessageReceived).subscribe(() => {
87
- startTracking = false;
88
- stopTracking = false;
138
+ isTracking = false;
89
139
  disconnectListener();
90
140
  });
91
141
 
92
142
  // 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
93
143
  // No need to keep listerning for tracking, enforcing disconnection for the listners
144
+ /**
145
+ * Listener for history message event.
146
+ * Resets tracking and disconnects listeners when history is loaded.
147
+ */
94
148
  const historyListener = _omnichannelChatComponents.BroadcastService.getMessageByEventName(_TelemetryConstants.BroadcastEvent.HistoryMessageReceived).subscribe(() => {
95
- startTracking = false;
96
- stopTracking = false;
149
+ isTracking = false;
97
150
  disconnectListener();
98
151
  });
152
+
153
+ /**
154
+ * Listener for network disconnection event.
155
+ * Resets tracking, disconnects listeners, and logs a telemetry error.
156
+ */
99
157
  const offlineNetworkListener = _omnichannelChatComponents.BroadcastService.getMessageByEventName(_TelemetryConstants.TelemetryEvent.NetworkDisconnected).subscribe(() => {
100
- startTracking = false;
101
- stopTracking = false;
158
+ isTracking = false;
102
159
  disconnectListener();
103
160
  _TelemetryHelper.TelemetryHelper.logActionEvent(_TelemetryConstants.LogLevel.INFO, {
104
161
  Event: _TelemetryConstants.TelemetryEvent.BotFirstMessageLapTrackError,
@@ -107,7 +164,15 @@ const createTrackingForFirstMessage = () => {
107
164
  });
108
165
 
109
166
  // this is to ensure that we are not tracking messages that are not part of the current conversation
167
+ /**
168
+ * Disconnects all listeners and clears the tracking timeout.
169
+ * Used for cleanup when tracking is stopped or reset.
170
+ */
110
171
  const disconnectListener = () => {
172
+ if (trackingTimeoutId) {
173
+ clearTimeout(trackingTimeoutId);
174
+ trackingTimeoutId = undefined;
175
+ }
111
176
  historyListener.unsubscribe();
112
177
  rehydrateListener.unsubscribe();
113
178
  newMessageListener.unsubscribe();
@@ -17,14 +17,13 @@ let FirstResponseLatencyTracker = /*#__PURE__*/function () {
17
17
  function FirstResponseLatencyTracker() {
18
18
  _classCallCheck(this, FirstResponseLatencyTracker);
19
19
  _defineProperty(this, "isABotConversation", false);
20
- _defineProperty(this, "isStarted", false);
21
- _defineProperty(this, "isEnded", false);
20
+ _defineProperty(this, "isTracking", false);
22
21
  _defineProperty(this, "startTrackingMessage", void 0);
23
22
  _defineProperty(this, "stopTrackingMessage", void 0);
24
23
  _defineProperty(this, "isReady", false);
24
+ _defineProperty(this, "trackingTimeoutId", void 0);
25
25
  _defineProperty(this, "offlineNetworkListener", _omnichannelChatComponents.BroadcastService.getMessageByEventName(_TelemetryConstants.TelemetryEvent.NetworkDisconnected).subscribe(() => {
26
- this.isStarted = false;
27
- this.isEnded = false;
26
+ this.isTracking = false;
28
27
  _TelemetryHelper.TelemetryHelper.logActionEvent(_TelemetryConstants.LogLevel.INFO, {
29
28
  Event: _TelemetryConstants.TelemetryEvent.MessageStopLapTrackError,
30
29
  Description: "Tracker Stopped due to network disconnection"
@@ -72,7 +71,7 @@ let FirstResponseLatencyTracker = /*#__PURE__*/function () {
72
71
  value: function startTracking(payload) {
73
72
  if (!this.isReady) return;
74
73
  // this prevents to initiate tracking for multiple incoming messages
75
- if (this.isStarted) {
74
+ if (this.isTracking) {
76
75
  return;
77
76
  }
78
77
  // this is to ensure we track only messages where bot is engaged
@@ -80,10 +79,24 @@ let FirstResponseLatencyTracker = /*#__PURE__*/function () {
80
79
  return;
81
80
  }
82
81
  // control of states to prevent clashing of messages
83
- this.isStarted = true;
84
- this.isEnded = false;
82
+ this.isTracking = true;
85
83
  // The idea of using types is to enrich telemetry data
86
84
  this.startTrackingMessage = this.createTrackingMessage(payload, "userMessage");
85
+
86
+ // Start a 5-second timeout to auto-stop tracking if not stopped
87
+ if (this.trackingTimeoutId) {
88
+ clearTimeout(this.trackingTimeoutId);
89
+ }
90
+ this.trackingTimeoutId = setTimeout(() => {
91
+ // this means the start process is in progress, but the end wasn't called within the time limit
92
+ if (this.isTracking) {
93
+ // Reset state variables and skip stopTracking
94
+ this.isTracking = false;
95
+ this.startTrackingMessage = undefined;
96
+ this.stopTrackingMessage = undefined;
97
+ this.trackingTimeoutId = undefined;
98
+ }
99
+ }, 5000);
87
100
  }
88
101
  }, {
89
102
  key: "handleAgentMessage",
@@ -99,13 +112,16 @@ let FirstResponseLatencyTracker = /*#__PURE__*/function () {
99
112
  value: function stopTracking(payload) {
100
113
  var _this$stopTrackingMes, _this$startTrackingMe;
101
114
  // this prevents execution for multiple incoming messages from the bot.
102
- if (this.isEnded && !this.isStarted) {
115
+ if (!this.isTracking) {
103
116
  return;
104
117
  }
105
-
118
+ // Clear the timeout if it exists
119
+ if (this.trackingTimeoutId) {
120
+ clearTimeout(this.trackingTimeoutId);
121
+ this.trackingTimeoutId = undefined;
122
+ }
106
123
  // control of states to prevent clashing of messages
107
- this.isEnded = true;
108
- this.isStarted = false;
124
+ this.isTracking = false;
109
125
 
110
126
  // The idea of using types is to enrich telemetry data
111
127
  this.stopTrackingMessage = this.createTrackingMessage(payload, "botMessage");
@@ -160,12 +176,16 @@ let FirstResponseLatencyTracker = /*#__PURE__*/function () {
160
176
  if (!payload || !payload.Id) {
161
177
  throw new Error("Invalid payload");
162
178
  }
163
- if (!this.isMessageFromValidSender(payload)) return;
164
- if (this.isABotConversation && this.isStarted) {
179
+
180
+ // Only allow stopTracking if sender is valid and tracking is active
181
+ if (!this.isMessageFromValidSender(payload)) {
182
+ // Do not change isTracking or stopTrackingMessage
183
+ return;
184
+ }
185
+ if (this.isABotConversation && this.isTracking) {
165
186
  this.stopTracking(payload);
166
187
  }
167
188
  } catch (e) {
168
- console.error("FRL : error while trying to stop the tracker", e);
169
189
  _TelemetryHelper.TelemetryHelper.logActionEvent(_TelemetryConstants.LogLevel.ERROR, {
170
190
  Event: _TelemetryConstants.TelemetryEvent.MessageStopLapTrackError,
171
191
  Description: "Error while stopping the clock",
@@ -174,11 +194,6 @@ let FirstResponseLatencyTracker = /*#__PURE__*/function () {
174
194
  payload: payload
175
195
  }
176
196
  });
177
- //reset state
178
- this.startTrackingMessage = undefined;
179
- this.stopTrackingMessage = undefined;
180
- this.isStarted = false;
181
- this.isEnded = false;
182
197
  }
183
198
  }
184
199
  }, {
@@ -186,10 +201,13 @@ let FirstResponseLatencyTracker = /*#__PURE__*/function () {
186
201
  value: function deregister() {
187
202
  // Reset State
188
203
  this.isABotConversation = false;
189
- this.isStarted = false;
190
- this.isEnded = false;
204
+ this.isTracking = false;
191
205
  this.startTrackingMessage = undefined;
192
206
  this.stopTrackingMessage = undefined;
207
+ if (this.trackingTimeoutId) {
208
+ clearTimeout(this.trackingTimeoutId);
209
+ this.trackingTimeoutId = undefined;
210
+ }
193
211
  this.offlineNetworkListener.unsubscribe();
194
212
  this.fmltrackingListener.unsubscribe();
195
213
  this.rehydrateListener.unsubscribe();
@@ -3,43 +3,72 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.polyfillMessagePayloadForEvent = exports.isHistoryMessage = exports.getScenarioType = exports.createTrackingMessage = exports.buildMessagePayload = void 0;
6
+ exports.polyfillMessagePayloadForEvent = exports.isHistoryMessage = exports.getScenarioType = exports.extractTimestampFromId = exports.createTrackingMessage = exports.buildMessagePayload = void 0;
7
7
  var _Constants = require("./Constants");
8
8
  var _Constants2 = require("../common/Constants");
9
+ const DELTA_WITHIN_LIMITS_IN_MS = 250;
10
+
11
+ /**
12
+ * Determines whether a given activity is a historical message.
13
+ *
14
+ * This function checks if the activity is a message type and uses a combination
15
+ * of legacy tags and timestamp-based logic to determine if the message is historical.
16
+ *
17
+ * @param {IActivity} activity - The activity object to evaluate.
18
+ * @param {number} startTime - The start time (in milliseconds since epoch) to compare against.
19
+ * @returns {boolean} - Returns true if the activity is a historical message, false otherwise.
20
+ *
21
+ * Logic:
22
+ * - If the activity type is not a message, it is not historical.
23
+ * - If the activity contains a legacy history message tag, it is considered historical.
24
+ * - Otherwise, the function extracts a timestamp from the activity ID using `extractTimestampFromId`.
25
+ * - If the ID is valid and the timestamp is older than the start time, the message is historical.
26
+ */
9
27
  const isHistoryMessage = (activity, startTime) => {
10
- try {
11
- if ((activity === null || activity === void 0 ? void 0 : activity.type) === _Constants2.Constants.message) {
12
- var _activity$channelData, _activity$channelData2;
13
- // this is an old piece of code, probably no longer relevant
14
- 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(_Constants2.Constants.historyMessageTag)) return true;
28
+ var _activity$channelData;
29
+ // Only process message activities
30
+ if ((activity === null || activity === void 0 ? void 0 : activity.type) !== _Constants2.Constants.message) {
31
+ return false;
32
+ }
15
33
 
16
- // Id is an epoch time in milliseconds , in utc format, for some reason is in a string format
17
- if (activity !== null && activity !== void 0 && activity.id) {
18
- /// activity.id is an string that contains epoch time in milliseconds
19
- const activityId = parseInt(activity === null || activity === void 0 ? void 0 : activity.id);
34
+ // Prioritize legacy history tag
35
+ if (activity !== null && activity !== void 0 && (_activity$channelData = activity.channelData) !== null && _activity$channelData !== void 0 && _activity$channelData.tags && activity.channelData.tags.includes(_Constants2.Constants.historyMessageTag)) {
36
+ return true;
37
+ }
38
+ const activityId = extractTimestampFromId(activity);
39
+ const isValidId = !isNaN(activityId) && activityId > 0;
40
+ const difference = startTime - activityId;
41
+ // Only consider historical if activityId < startTime and difference >= DELTA_WITHIN_LIMITS_IN_MS
42
+ const isOlderThanStartTime = activityId < startTime && difference >= DELTA_WITHIN_LIMITS_IN_MS;
43
+ const isHistoryById = isValidId && isOlderThanStartTime;
44
+ return isHistoryById;
45
+ };
46
+ exports.isHistoryMessage = isHistoryMessage;
47
+ const extractTimestampFromId = activity => {
48
+ const id = (activity === null || activity === void 0 ? void 0 : activity.id) ?? "";
20
49
 
21
- // if the activity id is not a number, we default to new message
22
- if (isNaN(activityId)) {
23
- return false;
24
- }
50
+ // Helper function to get timestamp fallback
51
+ const getTimestampFallback = () => {
52
+ const timestamp = new Date((activity === null || activity === void 0 ? void 0 : activity.timestamp) ?? "").getTime();
53
+ return isNaN(timestamp) ? 0 : timestamp;
54
+ };
25
55
 
26
- // if the activity id is less than the start time, it means that the message is a history message
27
- if (activityId < startTime) {
28
- return true;
29
- }
30
- }
31
- // anything else will be considered a new message
32
- return false;
33
- }
34
- } catch (e) {
35
- // if there is an error in parsing the activity id, we will consider it a new message
36
- console.error("Error in parsing activity id: ", e);
56
+ // Check if ID looks like a UUID (contains dashes or is very long)
57
+ const UUID_LENGTH_THRESHOLD = 13; // Threshold to distinguish UUIDs from epoch timestamps
58
+ if (id.includes("-") || id.length > UUID_LENGTH_THRESHOLD) {
59
+ // Likely UUID, use timestamp instead
60
+ return getTimestampFallback();
37
61
  }
38
- return false;
62
+ const activityId = parseInt(id);
63
+ // if activity id is not a number, then we use timestamp field
64
+ if (isNaN(activityId)) {
65
+ return getTimestampFallback();
66
+ }
67
+ return activityId;
39
68
  };
40
- exports.isHistoryMessage = isHistoryMessage;
69
+ exports.extractTimestampFromId = extractTimestampFromId;
41
70
  const buildMessagePayload = (activity, userId) => {
42
- var _text, _text2, _activity$channelData3, _activity$from;
71
+ var _text, _text2, _activity$channelData2, _activity$from;
43
72
  return {
44
73
  // To identify hidden contents vs empty content
45
74
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -47,7 +76,7 @@ const buildMessagePayload = (activity, userId) => {
47
76
  type: activity === null || activity === void 0 ? void 0 : activity.type,
48
77
  timestamp: activity === null || activity === void 0 ? void 0 : activity.timestamp,
49
78
  userId: userId,
50
- tags: activity === null || activity === void 0 ? void 0 : (_activity$channelData3 = activity.channelData) === null || _activity$channelData3 === void 0 ? void 0 : _activity$channelData3.tags,
79
+ tags: (activity === null || activity === void 0 ? void 0 : (_activity$channelData2 = activity.channelData) === null || _activity$channelData2 === void 0 ? void 0 : _activity$channelData2.tags) || [],
51
80
  messageType: "",
52
81
  Id: activity === null || activity === void 0 ? void 0 : activity.id,
53
82
  role: activity === null || activity === void 0 ? void 0 : (_activity$from = activity.from) === null || _activity$from === void 0 ? void 0 : _activity$from.role,
@@ -73,9 +102,9 @@ const polyfillMessagePayloadForEvent = (activity, payload, conversationId) => {
73
102
  };
74
103
  exports.polyfillMessagePayloadForEvent = polyfillMessagePayloadForEvent;
75
104
  const getScenarioType = activity => {
76
- var _activity$from3, _activity$channelData4;
105
+ var _activity$from3, _activity$channelData3;
77
106
  const role = activity === null || activity === void 0 ? void 0 : (_activity$from3 = activity.from) === null || _activity$from3 === void 0 ? void 0 : _activity$from3.role;
78
- const tags = activity === null || activity === void 0 ? void 0 : (_activity$channelData4 = activity.channelData) === null || _activity$channelData4 === void 0 ? void 0 : _activity$channelData4.tags;
107
+ const tags = activity === null || activity === void 0 ? void 0 : (_activity$channelData3 = activity.channelData) === null || _activity$channelData3 === void 0 ? void 0 : _activity$channelData3.tags;
79
108
  if (role === _Constants2.Constants.userMessageTag) {
80
109
  return _Constants.ScenarioType.UserSendMessageStrategy;
81
110
  }
@@ -12,13 +12,13 @@ var _Constants2 = require("../common/Constants");
12
12
  var _FirstResponseLatencyTracker = require("../firstresponselatency/FirstResponseLatencyTracker");
13
13
  var _TelemetryHelper = require("../common/telemetry/TelemetryHelper");
14
14
  var _TelemetryManager = require("../common/telemetry/TelemetryManager");
15
- const createOnNewAdapterActivityHandler = (chatId, userId) => {
15
+ const createOnNewAdapterActivityHandler = (chatId, userId, startTime) => {
16
16
  // Hooking the message tracker in the listener, a bit invasive but easier to control.
17
17
  const firstResponseLatencyTracker = new _FirstResponseLatencyTracker.FirstResponseLatencyTracker();
18
18
  // epoch time in utc for when start to listen.
19
19
  // We dont longer have a mechanism to know if a message is history or new, so any message older than the time we start listening will be considered a history message.
20
20
  // 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
21
- const startTime = new Date().getTime();
21
+
22
22
  let isHistoryMessageReceivedEventRaised = false;
23
23
  const onNewAdapterActivityHandler = activity => {
24
24
  raiseMessageEvent(activity);
@@ -41,6 +41,7 @@ const createOnNewAdapterActivityHandler = (chatId, userId) => {
41
41
  });
42
42
  };
43
43
  const systemMessageStrategy = activity => {
44
+ var _TelemetryManager$Int3;
44
45
  const payload = (0, _util.buildMessagePayload)(activity, userId);
45
46
  payload.messageType = _Constants2.Constants.systemMessageTag;
46
47
  if ((0, _util.isHistoryMessage)(activity, startTime)) {
@@ -48,6 +49,11 @@ const createOnNewAdapterActivityHandler = (chatId, userId) => {
48
49
  historyMessageStrategy((0, _util.polyfillMessagePayloadForEvent)(activity, payload, (_TelemetryManager$Int2 = _TelemetryManager.TelemetryManager.InternalTelemetryData) === null || _TelemetryManager$Int2 === void 0 ? void 0 : _TelemetryManager$Int2.conversationId));
49
50
  return;
50
51
  }
52
+ const newMessageReceivedEvent = {
53
+ eventName: _TelemetryConstants.BroadcastEvent.NewMessageReceived,
54
+ payload: (0, _util.polyfillMessagePayloadForEvent)(activity, payload, (_TelemetryManager$Int3 = _TelemetryManager.TelemetryManager.InternalTelemetryData) === null || _TelemetryManager$Int3 === void 0 ? void 0 : _TelemetryManager$Int3.conversationId)
55
+ };
56
+ _omnichannelChatComponents.BroadcastService.postMessage(newMessageReceivedEvent);
51
57
  _TelemetryHelper.TelemetryHelper.logActionEventToAllTelemetry(_TelemetryConstants.LogLevel.INFO, {
52
58
  Event: _TelemetryConstants.TelemetryEvent.SystemMessageReceived,
53
59
  Description: "System message received"
@@ -83,20 +89,20 @@ const createOnNewAdapterActivityHandler = (chatId, userId) => {
83
89
  return true;
84
90
  };
85
91
  const receivedMessageStrategy = activity => {
86
- var _TelemetryManager$Int4;
92
+ var _TelemetryManager$Int5;
87
93
  if (!isValidMessage(activity)) return;
88
94
  const isHistoryMessageReceived = (0, _util.isHistoryMessage)(activity, startTime);
89
95
  const payload = (0, _util.buildMessagePayload)(activity, userId);
90
96
  payload.messageType = _Constants2.Constants.userMessageTag;
91
97
  if (isHistoryMessageReceived) {
92
- var _TelemetryManager$Int3;
93
- historyMessageStrategy((0, _util.polyfillMessagePayloadForEvent)(activity, payload, (_TelemetryManager$Int3 = _TelemetryManager.TelemetryManager.InternalTelemetryData) === null || _TelemetryManager$Int3 === void 0 ? void 0 : _TelemetryManager$Int3.conversationId));
98
+ var _TelemetryManager$Int4;
99
+ historyMessageStrategy((0, _util.polyfillMessagePayloadForEvent)(activity, payload, (_TelemetryManager$Int4 = _TelemetryManager.TelemetryManager.InternalTelemetryData) === null || _TelemetryManager$Int4 === void 0 ? void 0 : _TelemetryManager$Int4.conversationId));
94
100
  return;
95
101
  }
96
102
  firstResponseLatencyTracker.stopClock(payload);
97
103
  const newMessageReceivedEvent = {
98
104
  eventName: _TelemetryConstants.BroadcastEvent.NewMessageReceived,
99
- payload: (0, _util.polyfillMessagePayloadForEvent)(activity, payload, (_TelemetryManager$Int4 = _TelemetryManager.TelemetryManager.InternalTelemetryData) === null || _TelemetryManager$Int4 === void 0 ? void 0 : _TelemetryManager$Int4.conversationId)
105
+ payload: (0, _util.polyfillMessagePayloadForEvent)(activity, payload, (_TelemetryManager$Int5 = _TelemetryManager.TelemetryManager.InternalTelemetryData) === null || _TelemetryManager$Int5 === void 0 ? void 0 : _TelemetryManager$Int5.conversationId)
100
106
  };
101
107
  _omnichannelChatComponents.BroadcastService.postMessage(newMessageReceivedEvent);
102
108
  _TelemetryHelper.TelemetryHelper.logActionEventToAllTelemetry(_TelemetryConstants.LogLevel.INFO, {
@@ -16,6 +16,9 @@ _defineProperty(Constants, "channelMessageTag", "channel");
16
16
  _defineProperty(Constants, "historyMessageTag", "history");
17
17
  _defineProperty(Constants, "agentEndConversationMessageTag", "agentendconversation");
18
18
  _defineProperty(Constants, "supervisorForceCloseMessageTag", "supervisorforceclosedconversation");
19
+ _defineProperty(Constants, "endConversationalSurveyMessageTag", "endconversationalsurvey");
20
+ _defineProperty(Constants, "startConversationalSurveyMessageTag", "startconversationalsurvey");
21
+ _defineProperty(Constants, "c2ConversationalSurveyMessageTag", "c2conversationalsurvey");
19
22
  _defineProperty(Constants, "receivedMessageClassName", "ms_lcw_webchat_received_message");
20
23
  _defineProperty(Constants, "sentMessageClassName", "ms_lcw_webchat_sent_message");
21
24
  _defineProperty(Constants, "webchatChannelId", "webchat");
@@ -105,8 +108,7 @@ _defineProperty(Constants, "TargetRelationshipAttributes", "noopener noreferrer"
105
108
  // Markdown icons
106
109
  _defineProperty(Constants, "OpenLinkIconCssClass", "webchat__render-markdown__external-link-icon");
107
110
  // internet connection test
108
- _defineProperty(Constants, "internetConnectionTestUrl", "https://ocsdk-prod.azureedge.net/public/connecttest.txt");
109
- _defineProperty(Constants, "internetConnectionTestUrlText", "Omnichannel Connect Test");
111
+ _defineProperty(Constants, "internetConnectionTestPath", "/livechatwidget/version.txt");
110
112
  _defineProperty(Constants, "ChatWidgetStateChangedPrefix", "ChatWidgetStateChanged");
111
113
  _defineProperty(Constants, "PostChatLoadingDurationInMs", 2000);
112
114
  _defineProperty(Constants, "BrowserUnloadConfirmationMessage", "Do you want to leave chat?");
@@ -122,6 +124,12 @@ _defineProperty(Constants, "InitContextParamsResponse", "initContextParamsRespon
122
124
  _defineProperty(Constants, "OCOriginalMessageId", "OriginalMessageId");
123
125
  _defineProperty(Constants, "WebchatSequenceIdAttribute", "webchat:sequence-id");
124
126
  _defineProperty(Constants, "MessageSequenceIdOverride", "MessageSequenceIdOverride");
127
+ _defineProperty(Constants, "sendCustomEvent", "sendCustomEvent");
128
+ _defineProperty(Constants, "onCustomEvent", "onCustomEvent");
129
+ _defineProperty(Constants, "customEventName", "customEventName");
130
+ _defineProperty(Constants, "customEventValue", "customEventValue");
131
+ _defineProperty(Constants, "Hidden", "Hidden");
132
+ _defineProperty(Constants, "EndConversationDueToOverflow", "endconversationduetooverflow");
125
133
  export const Regex = (_class = /*#__PURE__*/_createClass(function Regex() {
126
134
  _classCallCheck(this, Regex);
127
135
  }), _defineProperty(_class, "EmailRegex", "^(?:[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\")@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?)*|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\])$"), _class);
@@ -220,6 +228,10 @@ export let ConversationMode;
220
228
  ConversationMode["Regular"] = "192350000";
221
229
  ConversationMode["Persistent"] = "192350001";
222
230
  })(ConversationMode || (ConversationMode = {}));
231
+ export let SurveyProvider;
232
+ (function (SurveyProvider) {
233
+ SurveyProvider["MicrosoftCopilotStudio"] = "600990001";
234
+ })(SurveyProvider || (SurveyProvider = {}));
223
235
  export let LiveWorkItemState;
224
236
  (function (LiveWorkItemState) {
225
237
  LiveWorkItemState["Active"] = "Active";
@@ -184,6 +184,7 @@ export let TelemetryEvent;
184
184
  TelemetryEvent["BotAuthActivityUndefinedSignInId"] = "BotAuthActivityUndefinedSignInId";
185
185
  TelemetryEvent["ThirdPartyCookiesBlocked"] = "ThirdPartyCookiesBlocked";
186
186
  TelemetryEvent["ParticipantsRemovedEvent"] = "ParticipantsRemovedEvent";
187
+ TelemetryEvent["QueueOverflowEvent"] = "QueueOverflowEvent";
187
188
  TelemetryEvent["ProcessingHTMLTextMiddlewareFailed"] = "ProcessingHTMLTextMiddlewareFailed";
188
189
  TelemetryEvent["ProcessingSanitizationMiddlewareFailed"] = "ProcessingSanitizationMiddlewareFailed";
189
190
  TelemetryEvent["FormatTagsMiddlewareJSONStringifyFailed"] = "FormatTagsMiddlewareJSONStringifyFailed";
@@ -222,6 +223,7 @@ export let TelemetryEvent;
222
223
  TelemetryEvent["SystemMessageReceived"] = "SystemMessageReceived";
223
224
  TelemetryEvent["RehydrateMessageReceived"] = "RehydrateMessageReceived";
224
225
  TelemetryEvent["CustomContextReceived"] = "CustomContextReceived";
226
+ TelemetryEvent["CustomEventAction"] = "CustomEventAction";
225
227
  TelemetryEvent["NetworkDisconnected"] = "NetworkDisconnected";
226
228
  TelemetryEvent["NetworkReconnected"] = "NetworkReconnected";
227
229
  TelemetryEvent["LinkModePostChatWorkflowStarted"] = "LinkModePostChatWorkflowStarted";
@@ -453,4 +453,25 @@ export function getDeviceType() {
453
453
  } else {
454
454
  return "standard";
455
455
  }
456
+ }
457
+
458
+ //Bots expect a payload containing:
459
+ //1. customEventName: this should be string describe the event name
460
+ //2. customEventValue: given the value is from customer with unknown type, it is required to stringify the payload later
461
+ export const isValidCustomEvent = payload => {
462
+ if (Constants.customEventName in payload && payload.customEventName && typeof payload.customEventName === Constants.String && Constants.customEventValue in payload && payload.customEventValue) return true;
463
+ return false;
464
+ };
465
+ export const getCustomEventValue = customEventPayload => {
466
+ let returnVal = "";
467
+ try {
468
+ returnVal = typeof customEventPayload.customEventValue === Constants.String ? customEventPayload.customEventValue : JSON.stringify(customEventPayload.customEventValue);
469
+ } catch (error) {
470
+ console.error(error);
471
+ }
472
+ return returnVal;
473
+ };
474
+ export function isEndConversationDueToOverflowActivity(activity) {
475
+ var _activity$channelData, _activity$channelData2;
476
+ return (activity === null || activity === void 0 ? void 0 : (_activity$channelData = activity.channelData) === null || _activity$channelData === void 0 ? void 0 : _activity$channelData.tags) && Array.isArray(activity === null || activity === void 0 ? void 0 : (_activity$channelData2 = activity.channelData) === null || _activity$channelData2 === void 0 ? void 0 : _activity$channelData2.tags) && activity.channelData.tags.includes(Constants.EndConversationDueToOverflow);
456
477
  }
@@ -65,7 +65,6 @@ export const ChatButtonStateful = props => {
65
65
  };
66
66
  const outOfOfficeStyleProps = Object.assign({}, defaultOutOfOfficeChatButtonStyleProps, outOfOfficeButtonProps === null || outOfOfficeButtonProps === void 0 ? void 0 : outOfOfficeButtonProps.styleProps);
67
67
  const controlProps = {
68
- ...(buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.controlProps),
69
68
  id: "oc-lcw-chat-button",
70
69
  dir: state.domainStates.globalDir,
71
70
  titleText: "Let's Chat!",
@@ -74,7 +73,8 @@ export const ChatButtonStateful = props => {
74
73
  unreadMessageCount: state.appStates.unreadMessageCount ? state.appStates.unreadMessageCount > Constants.maximumUnreadMessageCount ? (_props$buttonProps = props.buttonProps) === null || _props$buttonProps === void 0 ? void 0 : (_props$buttonProps$co = _props$buttonProps.controlProps) === null || _props$buttonProps$co === void 0 ? void 0 : _props$buttonProps$co.largeUnreadMessageString : state.appStates.unreadMessageCount.toString() : "0",
75
74
  unreadMessageString: (_props$buttonProps2 = props.buttonProps) === null || _props$buttonProps2 === void 0 ? void 0 : (_props$buttonProps2$c = _props$buttonProps2.controlProps) === null || _props$buttonProps2$c === void 0 ? void 0 : _props$buttonProps2$c.unreadMessageString,
76
75
  // Regular chat button onClick - this will always take precedence
77
- onClick: () => ref.current()
76
+ onClick: () => ref.current(),
77
+ ...(buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.controlProps)
78
78
  };
79
79
  const outOfOfficeControlProps = {
80
80
  // Only take specific properties from outOfOfficeButtonProps, never onClick
@@ -83,7 +83,6 @@ export const ChatButtonStateful = props => {
83
83
  titleText: (outOfOfficeButtonProps === null || outOfOfficeButtonProps === void 0 ? void 0 : (_outOfOfficeButtonPro = outOfOfficeButtonProps.controlProps) === null || _outOfOfficeButtonPro === void 0 ? void 0 : _outOfOfficeButtonPro.titleText) || "We're Offline",
84
84
  subtitleText: (outOfOfficeButtonProps === null || outOfOfficeButtonProps === void 0 ? void 0 : (_outOfOfficeButtonPro2 = outOfOfficeButtonProps.controlProps) === null || _outOfOfficeButtonPro2 === void 0 ? void 0 : _outOfOfficeButtonPro2.subtitleText) || "No agents available",
85
85
  unreadMessageString: (_props$buttonProps3 = props.buttonProps) === null || _props$buttonProps3 === void 0 ? void 0 : (_props$buttonProps3$c = _props$buttonProps3.controlProps) === null || _props$buttonProps3$c === void 0 ? void 0 : _props$buttonProps3$c.unreadMessageString,
86
- ...(outOfOfficeButtonProps === null || outOfOfficeButtonProps === void 0 ? void 0 : outOfOfficeButtonProps.controlProps),
87
86
  // Out-of-office specific onClick - this will ALWAYS take precedence
88
87
  onClick: () => {
89
88
  if (state.appStates.isMinimized) {
@@ -96,7 +95,8 @@ export const ChatButtonStateful = props => {
96
95
  type: LiveChatWidgetActionType.SET_CONVERSATION_STATE,
97
96
  payload: ConversationState.OutOfOffice
98
97
  });
99
- }
98
+ },
99
+ ...(outOfOfficeButtonProps === null || outOfOfficeButtonProps === void 0 ? void 0 : outOfOfficeButtonProps.controlProps)
100
100
  };
101
101
  useEffect(() => {
102
102
  TelemetryHelper.logLoadingEvent(LogLevel.INFO, {