@microsoft/omnichannel-chat-widget 1.7.8-main.9e74278 → 1.7.8-main.d38af40

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 (33) hide show
  1. package/lib/cjs/common/telemetry/TelemetryConstants.js +5 -0
  2. package/lib/cjs/components/livechatwidget/common/ActivitySubscriber/BotAuthActivitySubscriber.js +4 -1
  3. package/lib/cjs/components/livechatwidget/common/endChat.js +4 -18
  4. package/lib/cjs/components/livechatwidget/livechatwidgetstateful/LiveChatWidgetStateful.js +14 -8
  5. package/lib/cjs/components/ooohpanestateful/OOOHPaneStateful.js +4 -0
  6. package/lib/cjs/components/postchatsurveypanestateful/PostChatSurveyPaneStateful.js +22 -6
  7. package/lib/cjs/components/postchatsurveypanestateful/common/isValidSurveyUrl.js +28 -0
  8. package/lib/cjs/contexts/common/LiveChatWidgetContextInitialState.js +4 -4
  9. package/lib/cjs/firstresponselatency/Constants.js +13 -0
  10. package/lib/cjs/firstresponselatency/FirstResponseLatencyTracker.js +152 -0
  11. package/lib/cjs/firstresponselatency/util.js +85 -0
  12. package/lib/cjs/plugins/createChatTranscript.js +4 -4
  13. package/lib/cjs/plugins/newMessageEventHandler.js +102 -88
  14. package/lib/esm/common/telemetry/TelemetryConstants.js +5 -0
  15. package/lib/esm/components/livechatwidget/common/ActivitySubscriber/BotAuthActivitySubscriber.js +4 -1
  16. package/lib/esm/components/livechatwidget/common/endChat.js +4 -18
  17. package/lib/esm/components/livechatwidget/livechatwidgetstateful/LiveChatWidgetStateful.js +14 -8
  18. package/lib/esm/components/ooohpanestateful/OOOHPaneStateful.js +4 -0
  19. package/lib/esm/components/postchatsurveypanestateful/PostChatSurveyPaneStateful.js +22 -6
  20. package/lib/esm/components/postchatsurveypanestateful/common/isValidSurveyUrl.js +21 -0
  21. package/lib/esm/contexts/common/LiveChatWidgetContextInitialState.js +4 -4
  22. package/lib/esm/firstresponselatency/Constants.js +6 -0
  23. package/lib/esm/firstresponselatency/FirstResponseLatencyTracker.js +145 -0
  24. package/lib/esm/firstresponselatency/util.js +75 -0
  25. package/lib/esm/plugins/createChatTranscript.js +4 -4
  26. package/lib/esm/plugins/newMessageEventHandler.js +100 -86
  27. package/lib/types/common/telemetry/TelemetryConstants.d.ts +5 -0
  28. package/lib/types/components/postchatsurveypanestateful/common/isValidSurveyUrl.d.ts +2 -0
  29. package/lib/types/contexts/common/ILiveChatWidgetContext.d.ts +1 -1
  30. package/lib/types/firstresponselatency/Constants.d.ts +30 -0
  31. package/lib/types/firstresponselatency/FirstResponseLatencyTracker.d.ts +17 -0
  32. package/lib/types/firstresponselatency/util.d.ts +6 -0
  33. package/package.json +1 -1
@@ -5,102 +5,116 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.createOnNewAdapterActivityHandler = void 0;
7
7
  var _TelemetryConstants = require("../common/telemetry/TelemetryConstants");
8
+ var _Constants = require("../firstresponselatency/Constants");
9
+ var _util = require("../firstresponselatency/util");
8
10
  var _omnichannelChatComponents = require("@microsoft/omnichannel-chat-components");
9
- var _Constants = require("../common/Constants");
11
+ var _Constants2 = require("../common/Constants");
12
+ var _FirstResponseLatencyTracker = require("../firstresponselatency/FirstResponseLatencyTracker");
10
13
  var _TelemetryHelper = require("../common/telemetry/TelemetryHelper");
11
14
  var _TelemetryManager = require("../common/telemetry/TelemetryManager");
12
15
  const createOnNewAdapterActivityHandler = (chatId, userId) => {
16
+ // Hooking the message tracker in the listener, a bit invasive but easier to control.
17
+ const firstResponseLatencyTracker = new _FirstResponseLatencyTracker.FirstResponseLatencyTracker();
18
+ // epoch time in utc for when start to listen.
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
+ // 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();
22
+ let isHistoryMessageReceivedEventRaised = false;
13
23
  const onNewAdapterActivityHandler = activity => {
14
- var _activity$channelData, _activity$channelData2, _activity$channelData3;
15
- const isActivityMessage = (activity === null || activity === void 0 ? void 0 : activity.type) === _Constants.Constants.message;
16
- const isHistoryMessage = isActivityMessage && ((activity === null || activity === void 0 ? void 0 : (_activity$channelData = activity.channelData) === null || _activity$channelData === void 0 ? void 0 : (_activity$channelData2 = _activity$channelData.tags) === null || _activity$channelData2 === void 0 ? void 0 : _activity$channelData2.includes(_Constants.Constants.historyMessageTag)) || (activity === null || activity === void 0 ? void 0 : (_activity$channelData3 = activity.channelData) === null || _activity$channelData3 === void 0 ? void 0 : _activity$channelData3.fromList));
17
- raiseMessageEvent(activity, isHistoryMessage);
24
+ raiseMessageEvent(activity);
18
25
  };
19
- let isHistoryMessageReceivedEventRasied = false;
20
- const raiseMessageEvent = (activity, isHistoryMessage) => {
26
+ const userSendMessageStrategy = activity => {
27
+ var _TelemetryManager$Int;
28
+ const payload = (0, _util.buildMessagePayload)(activity, userId);
21
29
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
- const polyfillMessagePayloadForEvent = payload => {
23
- var _activity$conversatio, _TelemetryManager$Int, _attachments;
24
- return {
25
- ...payload,
26
- channelData: activity === null || activity === void 0 ? void 0 : activity.channelData,
27
- chatId: activity === null || activity === void 0 ? void 0 : (_activity$conversatio = activity.conversation) === null || _activity$conversatio === void 0 ? void 0 : _activity$conversatio.id,
28
- conversationId: (_TelemetryManager$Int = _TelemetryManager.TelemetryManager.InternalTelemetryData) === null || _TelemetryManager$Int === void 0 ? void 0 : _TelemetryManager$Int.conversationId,
29
- id: activity === null || activity === void 0 ? void 0 : activity.id,
30
- isChatComplete: false,
31
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
- text: activity === null || activity === void 0 ? void 0 : activity.text,
33
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
- attachment: (activity === null || activity === void 0 ? void 0 : (_attachments = activity.attachments) === null || _attachments === void 0 ? void 0 : _attachments.length) >= 1 ? activity === null || activity === void 0 ? void 0 : activity.attachments : []
35
- };
30
+ payload.messageType = _Constants2.Constants.userMessageTag;
31
+ const newMessageSentEvent = {
32
+ eventName: _TelemetryConstants.BroadcastEvent.NewMessageSent,
33
+ payload: (0, _util.polyfillMessagePayloadForEvent)(activity, payload, (_TelemetryManager$Int = _TelemetryManager.TelemetryManager.InternalTelemetryData) === null || _TelemetryManager$Int === void 0 ? void 0 : _TelemetryManager$Int.conversationId)
36
34
  };
37
- if ((activity === null || activity === void 0 ? void 0 : activity.type) === _Constants.Constants.message) {
38
- var _text, _text2, _activity$channelData4, _activity$from;
39
- const payload = {
40
- // To identify hidden contents vs empty content
41
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
42
- text: (activity === null || activity === void 0 ? void 0 : (_text = activity.text) === null || _text === void 0 ? void 0 : _text.length) >= 1 ? `*contents hidden (${activity === null || activity === void 0 ? void 0 : (_text2 = activity.text) === null || _text2 === void 0 ? void 0 : _text2.length} chars)*` : "",
43
- type: activity === null || activity === void 0 ? void 0 : activity.type,
44
- timestamp: activity === null || activity === void 0 ? void 0 : activity.timestamp,
45
- userId: userId,
46
- tags: activity === null || activity === void 0 ? void 0 : (_activity$channelData4 = activity.channelData) === null || _activity$channelData4 === void 0 ? void 0 : _activity$channelData4.tags,
47
- messageType: ""
48
- };
49
- if ((activity === null || activity === void 0 ? void 0 : (_activity$from = activity.from) === null || _activity$from === void 0 ? void 0 : _activity$from.role) === _Constants.Constants.userMessageTag) {
50
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
51
- payload.messageType = _Constants.Constants.userMessageTag;
52
- const newMessageSentEvent = {
53
- eventName: _TelemetryConstants.BroadcastEvent.NewMessageSent,
54
- payload: polyfillMessagePayloadForEvent(payload)
55
- };
56
- _omnichannelChatComponents.BroadcastService.postMessage(newMessageSentEvent);
57
- _TelemetryHelper.TelemetryHelper.logActionEvent(_TelemetryConstants.LogLevel.INFO, {
58
- Event: _TelemetryConstants.TelemetryEvent.MessageSent,
59
- Description: "New message sent"
60
- });
61
- } else {
62
- var _activity$channelData5, _activity$channelData6;
63
- if (activity !== null && activity !== void 0 && (_activity$channelData5 = activity.channelData) !== null && _activity$channelData5 !== void 0 && (_activity$channelData6 = _activity$channelData5.tags) !== null && _activity$channelData6 !== void 0 && _activity$channelData6.includes(_Constants.Constants.systemMessageTag)) {
64
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
65
- payload.messageType = _Constants.Constants.systemMessageTag;
66
- _TelemetryHelper.TelemetryHelper.logActionEvent(_TelemetryConstants.LogLevel.INFO, {
67
- Event: _TelemetryConstants.TelemetryEvent.SystemMessageReceived,
68
- Description: "System message received"
69
- });
70
- } else {
71
- var _activity$channelData7, _activity$channelData8, _activity$channelData9;
72
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
73
- const messageHasNoText = !(activity !== null && activity !== void 0 && activity.text);
74
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
75
- const messageHasNoTags = !(activity !== null && activity !== void 0 && activity.channelData) || !(activity !== null && activity !== void 0 && (_activity$channelData7 = activity.channelData) !== null && _activity$channelData7 !== void 0 && _activity$channelData7.tags) || (activity === null || activity === void 0 ? void 0 : (_activity$channelData8 = activity.channelData) === null || _activity$channelData8 === void 0 ? void 0 : (_activity$channelData9 = _activity$channelData8.tags) === null || _activity$channelData9 === void 0 ? void 0 : _activity$channelData9.length) === 0;
76
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
77
- const messageHasNoAttachments = !(activity !== null && activity !== void 0 && activity.attachments) || (activity === null || activity === void 0 ? void 0 : activity.attachments.length) === 0;
78
- if (messageHasNoTags && messageHasNoText && messageHasNoAttachments) {
79
- return;
80
- }
81
- payload.messageType = _Constants.Constants.userMessageTag;
82
- }
83
- const newMessageReceivedEvent = {
84
- eventName: isHistoryMessage ? _TelemetryConstants.BroadcastEvent.HistoryMessageReceived : _TelemetryConstants.BroadcastEvent.NewMessageReceived,
85
- payload: polyfillMessagePayloadForEvent(payload)
86
- };
87
- _omnichannelChatComponents.BroadcastService.postMessage(newMessageReceivedEvent);
88
- if (!isHistoryMessage) {
89
- _TelemetryHelper.TelemetryHelper.logActionEvent(_TelemetryConstants.LogLevel.INFO, {
90
- Event: _TelemetryConstants.TelemetryEvent.MessageReceived,
91
- Description: "New message received",
92
- CustomProperties: payload
93
- });
94
- } else {
95
- if (!isHistoryMessageReceivedEventRasied) {
96
- isHistoryMessageReceivedEventRasied = true;
97
- _TelemetryHelper.TelemetryHelper.logActionEvent(_TelemetryConstants.LogLevel.INFO, {
98
- Event: _TelemetryConstants.TelemetryEvent.RehydrateMessageReceived,
99
- Description: "History message received",
100
- CustomProperties: payload
101
- });
102
- }
103
- }
35
+ _omnichannelChatComponents.BroadcastService.postMessage(newMessageSentEvent);
36
+ if (!(0, _util.isHistoryMessage)(activity, startTime)) {
37
+ firstResponseLatencyTracker.startClock(payload);
38
+ }
39
+ _TelemetryHelper.TelemetryHelper.logActionEvent(_TelemetryConstants.LogLevel.INFO, {
40
+ Event: _TelemetryConstants.TelemetryEvent.MessageSent,
41
+ Description: "New message sent"
42
+ });
43
+ };
44
+ const systemMessageStrategy = activity => {
45
+ const payload = (0, _util.buildMessagePayload)(activity, userId);
46
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
47
+ payload.messageType = _Constants2.Constants.systemMessageTag;
48
+ _TelemetryHelper.TelemetryHelper.logActionEvent(_TelemetryConstants.LogLevel.INFO, {
49
+ Event: _TelemetryConstants.TelemetryEvent.SystemMessageReceived,
50
+ Description: "System message received"
51
+ });
52
+ };
53
+ const historyMessageStrategy = payload => {
54
+ const newMessageReceivedEvent = {
55
+ eventName: _TelemetryConstants.BroadcastEvent.HistoryMessageReceived,
56
+ payload: payload
57
+ };
58
+ _omnichannelChatComponents.BroadcastService.postMessage(newMessageReceivedEvent);
59
+ if (!isHistoryMessageReceivedEventRaised) {
60
+ // this is needed for reload scenarios, it helps to identify the last message received before the reload
61
+ isHistoryMessageReceivedEventRaised = true;
62
+ _TelemetryHelper.TelemetryHelper.logActionEvent(_TelemetryConstants.LogLevel.INFO, {
63
+ Event: _TelemetryConstants.TelemetryEvent.RehydrateMessageReceived,
64
+ Description: "History message received",
65
+ CustomProperties: payload
66
+ });
67
+ }
68
+ };
69
+ const isValidMessage = activity => {
70
+ var _activity$channelData, _activity$channelData2, _activity$channelData3;
71
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
72
+ const messageHasNoText = !(activity !== null && activity !== void 0 && activity.text);
73
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
74
+ const messageHasNoTags = !(activity !== null && activity !== void 0 && activity.channelData) || !(activity !== null && activity !== void 0 && (_activity$channelData = activity.channelData) !== null && _activity$channelData !== void 0 && _activity$channelData.tags) || (activity === null || activity === void 0 ? void 0 : (_activity$channelData2 = activity.channelData) === null || _activity$channelData2 === void 0 ? void 0 : (_activity$channelData3 = _activity$channelData2.tags) === null || _activity$channelData3 === void 0 ? void 0 : _activity$channelData3.length) === 0;
75
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
76
+ const messageHasNoAttachments = !(activity !== null && activity !== void 0 && activity.attachments) || (activity === null || activity === void 0 ? void 0 : activity.attachments.length) === 0;
77
+ if (messageHasNoTags && messageHasNoText && messageHasNoAttachments) {
78
+ return false;
79
+ }
80
+ return true;
81
+ };
82
+ const receivedMessageStrategy = activity => {
83
+ var _TelemetryManager$Int3;
84
+ if (!isValidMessage(activity)) return;
85
+ const isHistoryMessageReceived = (0, _util.isHistoryMessage)(activity, startTime);
86
+ const payload = (0, _util.buildMessagePayload)(activity, userId);
87
+ payload.messageType = _Constants2.Constants.userMessageTag;
88
+ if (isHistoryMessageReceived) {
89
+ var _TelemetryManager$Int2;
90
+ historyMessageStrategy((0, _util.polyfillMessagePayloadForEvent)(activity, payload, (_TelemetryManager$Int2 = _TelemetryManager.TelemetryManager.InternalTelemetryData) === null || _TelemetryManager$Int2 === void 0 ? void 0 : _TelemetryManager$Int2.conversationId));
91
+ return;
92
+ }
93
+ firstResponseLatencyTracker.stopClock(payload);
94
+ const newMessageReceivedEvent = {
95
+ eventName: _TelemetryConstants.BroadcastEvent.NewMessageReceived,
96
+ payload: (0, _util.polyfillMessagePayloadForEvent)(activity, payload, (_TelemetryManager$Int3 = _TelemetryManager.TelemetryManager.InternalTelemetryData) === null || _TelemetryManager$Int3 === void 0 ? void 0 : _TelemetryManager$Int3.conversationId)
97
+ };
98
+ _omnichannelChatComponents.BroadcastService.postMessage(newMessageReceivedEvent);
99
+ _TelemetryHelper.TelemetryHelper.logActionEvent(_TelemetryConstants.LogLevel.INFO, {
100
+ Event: _TelemetryConstants.TelemetryEvent.MessageReceived,
101
+ Description: "New message received",
102
+ CustomProperties: payload
103
+ });
104
+ };
105
+ const raiseMessageEvent = activity => {
106
+ if ((activity === null || activity === void 0 ? void 0 : activity.type) === _Constants2.Constants.message) {
107
+ const scenarioType = (0, _util.getScenarioType)(activity);
108
+ switch (scenarioType) {
109
+ case _Constants.ScenarioType.UserSendMessageStrategy:
110
+ userSendMessageStrategy(activity);
111
+ break;
112
+ case _Constants.ScenarioType.SystemMessageStrategy:
113
+ systemMessageStrategy(activity);
114
+ break;
115
+ case _Constants.ScenarioType.ReceivedMessageStrategy:
116
+ receivedMessageStrategy(activity);
117
+ break;
104
118
  }
105
119
  }
106
120
  };
@@ -199,6 +199,9 @@ export let TelemetryEvent;
199
199
  TelemetryEvent["ReconnectChatMinimize"] = "ReconnectChatMinimize";
200
200
  TelemetryEvent["MessageSent"] = "MessageSent";
201
201
  TelemetryEvent["MessageReceived"] = "MessageReceived";
202
+ TelemetryEvent["MessageLapTrack"] = "MessageLapTrack";
203
+ TelemetryEvent["MessageStartLapTrackError"] = "MessageStartLapTrackError";
204
+ TelemetryEvent["MessageStopLapTrackError"] = "MessageStopLapTrackError";
202
205
  TelemetryEvent["SystemMessageReceived"] = "SystemMessageReceived";
203
206
  TelemetryEvent["RehydrateMessageReceived"] = "RehydrateMessageReceived";
204
207
  TelemetryEvent["CustomContextReceived"] = "CustomContextReceived";
@@ -213,6 +216,8 @@ export let TelemetryEvent;
213
216
  TelemetryEvent["PostChatContextCallFailed"] = "PostChatContextCallFailed";
214
217
  TelemetryEvent["PostChatSurveyLoadingPaneLoaded"] = "PostChatSurveyLoadingPaneLoaded";
215
218
  TelemetryEvent["PostChatSurveyLoaded"] = "PostChatSurveyLoaded";
219
+ TelemetryEvent["PostChatSurveyUrlValidationCompleted"] = "PostChatSurveyUrlValidationCompleted";
220
+ TelemetryEvent["PostChatSurveyUrlValidationFailed"] = "PostChatSurveyUrlValidationFailed";
216
221
  TelemetryEvent["ChatDisconnectThreadEventReceived"] = "ChatDisconnectThreadEventReceived";
217
222
  TelemetryEvent["HiddenAdaptiveCardMessageReceived"] = "HiddenAdaptiveCardMessageReceived";
218
223
  TelemetryEvent["EndingAdapterAfterDisconnectionError"] = "EndingAdapterAfterDisconnectionError";
@@ -6,6 +6,7 @@ function _toPrimitive(input, hint) { if (typeof input !== "object" || input ===
6
6
  import { BroadcastService } from "@microsoft/omnichannel-chat-components";
7
7
  import { BroadcastEvent, LogLevel, TelemetryEvent } from "../../../../common/telemetry/TelemetryConstants";
8
8
  import { TelemetryHelper } from "../../../../common/telemetry/TelemetryHelper";
9
+ import { TelemetryManager } from "../../../../common/telemetry/TelemetryManager";
9
10
  const supportedSignInCardContentTypes = ["application/vnd.microsoft.card.signin", "application/vnd.microsoft.card.oauth"];
10
11
  const botOauthUrlRegex = /[\S]+.botframework.com\/api\/oauth\/signin\?signin=([\S]+)/;
11
12
  const delay = t => new Promise(resolve => setTimeout(resolve, t));
@@ -83,6 +84,7 @@ export class BotAuthActivitySubscriber {
83
84
  return (activity === null || activity === void 0 ? void 0 : (_activity$attachments = activity.attachments) === null || _activity$attachments === void 0 ? void 0 : _activity$attachments.length) > 0 && activity.attachments[0] && supportedSignInCardContentTypes.indexOf(activity.attachments[0].contentType) >= 0;
84
85
  }
85
86
  async apply(activity) {
87
+ var _TelemetryManager$Int;
86
88
  this.observer.next(false); // Hides card
87
89
  const attachment = activity.attachments[0];
88
90
  const signInUrl = attachment.content.buttons[0].value;
@@ -102,7 +104,8 @@ export class BotAuthActivitySubscriber {
102
104
  const event = {
103
105
  eventName: BroadcastEvent.SigninCardReceived,
104
106
  payload: {
105
- sasUrl
107
+ sasUrl,
108
+ conversationId: (_TelemetryManager$Int = TelemetryManager.InternalTelemetryData) === null || _TelemetryManager$Int === void 0 ? void 0 : _TelemetryManager$Int.conversationId
106
109
  }
107
110
  };
108
111
  if (!sasUrl) {
@@ -203,7 +203,7 @@ const endChat = async (props, facadeChatSDK, state, dispatch, setAdapter, setWeb
203
203
  payload: undefined
204
204
  });
205
205
  // Always allow to close the chat for embedded mode irrespective of end chat errors
206
- closeChatWidget(dispatch, props, state);
206
+ closeChatWidget(dispatch);
207
207
  facadeChatSDK.destroy();
208
208
  }
209
209
  }
@@ -327,21 +327,7 @@ export const endVoiceVideoCallIfOngoing = async (facadeChatSDK, dispatch) => {
327
327
  }, callId);
328
328
  }
329
329
  };
330
- const closeChatWidget = (dispatch, props, state) => {
331
- var _state$appStates5;
332
- if (state !== null && state !== void 0 && (_state$appStates5 = state.appStates) !== null && _state$appStates5 !== void 0 && _state$appStates5.hideStartChatButton) {
333
- var _props$controlProps2, _props$controlProps3;
334
- // Only close chat if header is enabled for popout
335
- // TODO : This condition needs to be removed eventually when the filler UX is ready for popout, removing this condition would show a blank screen for OOB Widget
336
- if ((props === null || props === void 0 ? void 0 : (_props$controlProps2 = props.controlProps) === null || _props$controlProps2 === void 0 ? void 0 : _props$controlProps2.hideHeader) === undefined || (props === null || props === void 0 ? void 0 : (_props$controlProps3 = props.controlProps) === null || _props$controlProps3 === void 0 ? void 0 : _props$controlProps3.hideHeader) === false) {
337
- dispatch({
338
- type: LiveChatWidgetActionType.SET_CONVERSATION_STATE,
339
- payload: ConversationState.Closed
340
- });
341
- }
342
- return;
343
- }
344
-
330
+ const closeChatWidget = dispatch => {
345
331
  // Embedded chat
346
332
  dispatch({
347
333
  type: LiveChatWidgetActionType.SET_CONVERSATION_STATE,
@@ -361,7 +347,7 @@ const chatTokenCleanUp = async dispatch => {
361
347
  };
362
348
 
363
349
  const getEndChatEventName = async (facadeChatSDK, props) => {
364
- var _facadeChatSDK$getCha2, _facadeChatSDK$getCha3, _facadeChatSDK$getCha4, _facadeChatSDK$getCha5, _props$controlProps4;
365
- return getWidgetEndChatEventName((_facadeChatSDK$getCha2 = facadeChatSDK.getChatSDK()) === null || _facadeChatSDK$getCha2 === void 0 ? void 0 : (_facadeChatSDK$getCha3 = _facadeChatSDK$getCha2.omnichannelConfig) === null || _facadeChatSDK$getCha3 === void 0 ? void 0 : _facadeChatSDK$getCha3.orgId, (_facadeChatSDK$getCha4 = facadeChatSDK.getChatSDK()) === null || _facadeChatSDK$getCha4 === void 0 ? void 0 : (_facadeChatSDK$getCha5 = _facadeChatSDK$getCha4.omnichannelConfig) === null || _facadeChatSDK$getCha5 === void 0 ? void 0 : _facadeChatSDK$getCha5.widgetId, (props === null || props === void 0 ? void 0 : (_props$controlProps4 = props.controlProps) === null || _props$controlProps4 === void 0 ? void 0 : _props$controlProps4.widgetInstanceId) ?? "");
350
+ var _facadeChatSDK$getCha2, _facadeChatSDK$getCha3, _facadeChatSDK$getCha4, _facadeChatSDK$getCha5, _props$controlProps2;
351
+ return getWidgetEndChatEventName((_facadeChatSDK$getCha2 = facadeChatSDK.getChatSDK()) === null || _facadeChatSDK$getCha2 === void 0 ? void 0 : (_facadeChatSDK$getCha3 = _facadeChatSDK$getCha2.omnichannelConfig) === null || _facadeChatSDK$getCha3 === void 0 ? void 0 : _facadeChatSDK$getCha3.orgId, (_facadeChatSDK$getCha4 = facadeChatSDK.getChatSDK()) === null || _facadeChatSDK$getCha4 === void 0 ? void 0 : (_facadeChatSDK$getCha5 = _facadeChatSDK$getCha4.omnichannelConfig) === null || _facadeChatSDK$getCha5 === void 0 ? void 0 : _facadeChatSDK$getCha5.widgetId, (props === null || props === void 0 ? void 0 : (_props$controlProps2 = props.controlProps) === null || _props$controlProps2 === void 0 ? void 0 : _props$controlProps2.widgetInstanceId) ?? "");
366
352
  };
367
353
  export { prepareEndChat, endChat };
@@ -409,7 +409,7 @@ export const LiveChatWidgetStateful = props => {
409
409
 
410
410
  // Start chat from SDK Event
411
411
  BroadcastService.getMessageByEventName(BroadcastEvent.StartChat).subscribe(msg => {
412
- var _props$chatConfig5, _props$chatConfig5$Li, _props$chatConfig6, _props$chatConfig6$Li, _msg$payload5, _msg$payload6, _msg$payload7, _msg$payload9, _inMemoryState$appSta2, _inMemoryState$appSta3, _inMemoryState$appSta4, _inMemoryState$appSta5;
412
+ var _props$chatConfig5, _props$chatConfig5$Li, _props$chatConfig6, _props$chatConfig6$Li, _msg$payload5, _msg$payload6, _msg$payload7, _msg$payload9, _inMemoryState$appSta2, _inMemoryState$appSta3;
413
413
  // If chat is out of operating hours chat widget sets the conversation state to OutOfOffice.
414
414
  if (typeof (props === null || props === void 0 ? void 0 : (_props$chatConfig5 = props.chatConfig) === null || _props$chatConfig5 === void 0 ? void 0 : (_props$chatConfig5$Li = _props$chatConfig5.LiveWSAndLiveChatEngJoin) === null || _props$chatConfig5$Li === void 0 ? void 0 : _props$chatConfig5$Li.OutOfOperatingHours) === "string" && (props === null || props === void 0 ? void 0 : (_props$chatConfig6 = props.chatConfig) === null || _props$chatConfig6 === void 0 ? void 0 : (_props$chatConfig6$Li = _props$chatConfig6.LiveWSAndLiveChatEngJoin) === null || _props$chatConfig6$Li === void 0 ? void 0 : _props$chatConfig6$Li.OutOfOperatingHours.toLowerCase()) === "true") {
415
415
  (state === null || state === void 0 ? void 0 : state.appStates.isMinimized) && dispatch({
@@ -447,8 +447,14 @@ export const LiveChatWidgetStateful = props => {
447
447
  });
448
448
  inMemoryState.domainStates.customContext = msg === null || msg === void 0 ? void 0 : (_msg$payload9 = msg.payload) === null || _msg$payload9 === void 0 ? void 0 : _msg$payload9.customContext;
449
449
 
450
- // Only initiate new chat if widget runtime state is one of the followings
451
- if (((_inMemoryState$appSta2 = inMemoryState.appStates) === null || _inMemoryState$appSta2 === void 0 ? void 0 : _inMemoryState$appSta2.conversationState) === ConversationState.Closed || ((_inMemoryState$appSta3 = inMemoryState.appStates) === null || _inMemoryState$appSta3 === void 0 ? void 0 : _inMemoryState$appSta3.conversationState) === ConversationState.InActive || ((_inMemoryState$appSta4 = inMemoryState.appStates) === null || _inMemoryState$appSta4 === void 0 ? void 0 : _inMemoryState$appSta4.conversationState) === ConversationState.Postchat) {
450
+ /*
451
+ * If the conversation is in closed state then we start a new chat,
452
+ * else if the conversation is in active state then we maximize the chat
453
+ * If the conversation is in inactive or postchat state then we maximize the chat.
454
+ *
455
+ * To start a new chat, it needs to be called via the close button or close chat via SDK.
456
+ **/
457
+ if (((_inMemoryState$appSta2 = inMemoryState.appStates) === null || _inMemoryState$appSta2 === void 0 ? void 0 : _inMemoryState$appSta2.conversationState) === ConversationState.Closed) {
452
458
  BroadcastService.postMessage({
453
459
  eventName: BroadcastEvent.ChatInitiated
454
460
  });
@@ -457,7 +463,7 @@ export const LiveChatWidgetStateful = props => {
457
463
  }
458
464
 
459
465
  // If minimized, maximize the chat
460
- if ((inMemoryState === null || inMemoryState === void 0 ? void 0 : (_inMemoryState$appSta5 = inMemoryState.appStates) === null || _inMemoryState$appSta5 === void 0 ? void 0 : _inMemoryState$appSta5.isMinimized) === true) {
466
+ if ((inMemoryState === null || inMemoryState === void 0 ? void 0 : (_inMemoryState$appSta3 = inMemoryState.appStates) === null || _inMemoryState$appSta3 === void 0 ? void 0 : _inMemoryState$appSta3.isMinimized) === true) {
461
467
  var _inMemoryState$domain, _inMemoryState$domain2, _inMemoryState$domain3, _inMemoryState$domain4;
462
468
  dispatch({
463
469
  type: LiveChatWidgetActionType.SET_MINIMIZED,
@@ -585,12 +591,12 @@ export const LiveChatWidgetStateful = props => {
585
591
  disablePolling: true
586
592
  });
587
593
  facadeChatSDK === null || facadeChatSDK === void 0 ? void 0 : facadeChatSDK.onAgentEndSession(event => {
588
- var _inMemoryState$appSta6;
594
+ var _inMemoryState$appSta4;
589
595
  const inMemoryState = executeReducer(state, {
590
596
  type: LiveChatWidgetActionType.GET_IN_MEMORY_STATE,
591
597
  payload: null
592
598
  });
593
- if ("participantsRemoved" in event && (inMemoryState === null || inMemoryState === void 0 ? void 0 : (_inMemoryState$appSta6 = inMemoryState.appStates) === null || _inMemoryState$appSta6 === void 0 ? void 0 : _inMemoryState$appSta6.conversationState) === ConversationState.Active) {
599
+ if ("participantsRemoved" in event && (inMemoryState === null || inMemoryState === void 0 ? void 0 : (_inMemoryState$appSta4 = inMemoryState.appStates) === null || _inMemoryState$appSta4 === void 0 ? void 0 : _inMemoryState$appSta4.conversationState) === ConversationState.Active) {
594
600
  setWebChatStyles(styles => {
595
601
  return {
596
602
  ...styles,
@@ -733,13 +739,13 @@ export const LiveChatWidgetStateful = props => {
733
739
 
734
740
  // Handle Chat disconnect cases
735
741
  useEffect(() => {
736
- var _inMemoryState$appSta7;
742
+ var _inMemoryState$appSta5;
737
743
  const inMemoryState = executeReducer(state, {
738
744
  type: LiveChatWidgetActionType.GET_IN_MEMORY_STATE,
739
745
  payload: null
740
746
  });
741
747
  handleChatDisconnect(props, inMemoryState, setWebChatStyles);
742
- const chatDisconnectState = inMemoryState === null || inMemoryState === void 0 ? void 0 : (_inMemoryState$appSta7 = inMemoryState.appStates) === null || _inMemoryState$appSta7 === void 0 ? void 0 : _inMemoryState$appSta7.chatDisconnectEventReceived;
748
+ const chatDisconnectState = inMemoryState === null || inMemoryState === void 0 ? void 0 : (_inMemoryState$appSta5 = inMemoryState.appStates) === null || _inMemoryState$appSta5 === void 0 ? void 0 : _inMemoryState$appSta5.chatDisconnectEventReceived;
743
749
  if (chatDisconnectState && adapter) {
744
750
  try {
745
751
  adapter.end();
@@ -5,6 +5,7 @@ import { OutOfOfficeHoursPane } from "@microsoft/omnichannel-chat-components";
5
5
  import { TelemetryHelper } from "../../common/telemetry/TelemetryHelper";
6
6
  import { defaultGeneralStyleProps } from "./common/defaultStyleProps/defaultgeneralOOOHPaneStyleProps";
7
7
  import useChatContextStore from "../../hooks/useChatContextStore";
8
+ import DOMPurify from "dompurify";
8
9
  let uiTimer;
9
10
  export const OutOfOfficeHoursPaneStateful = props => {
10
11
  var _props$styleProps;
@@ -40,6 +41,9 @@ export const OutOfOfficeHoursPaneStateful = props => {
40
41
  ElapsedTimeInMilliseconds: uiTimer.milliSecondsElapsed
41
42
  });
42
43
  }, []);
44
+ if (controlProps !== null && controlProps !== void 0 && controlProps.titleText) {
45
+ controlProps.titleText = DOMPurify.sanitize(controlProps.titleText);
46
+ }
43
47
  return /*#__PURE__*/React.createElement(OutOfOfficeHoursPane, {
44
48
  componentOverrides: props.componentOverrides,
45
49
  controlProps: controlProps,
@@ -8,14 +8,16 @@ import { TelemetryHelper } from "../../common/telemetry/TelemetryHelper";
8
8
  import { defaultGeneralPostChatSurveyPaneStyleProps } from "./common/defaultStyleProps/defaultgeneralPostChatSurveyPaneStyleProps";
9
9
  import { findAllFocusableElement } from "../../common/utils";
10
10
  import useChatContextStore from "../../hooks/useChatContextStore";
11
+ import isValidSurveyUrl from "./common/isValidSurveyUrl";
11
12
  const generateSurveyInviteLink = function (surveyInviteLink, isEmbed, locale, compact) {
12
13
  let showMultiLingual = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
13
- const surveyLink = `${surveyInviteLink}
14
- &embed=${isEmbed.toString()}
15
- &compact=${compact.toString() ?? "true"}
16
- &lang=${locale ?? "en-us"}
17
- &showmultilingual=${showMultiLingual.toString() ?? "false"}`;
18
- return surveyLink;
14
+ const surveyLinkParams = new URLSearchParams({
15
+ embed: isEmbed.toString(),
16
+ compact: (compact ?? true).toString(),
17
+ lang: locale ?? "en-us",
18
+ showmultilingual: (showMultiLingual ?? false).toString()
19
+ });
20
+ return `${surveyInviteLink}&${surveyLinkParams.toString()}`;
19
21
  };
20
22
  export const PostChatSurveyPaneStateful = props => {
21
23
  var _props$styleProps, _state$appStates, _props$controlProps;
@@ -45,6 +47,20 @@ export const PostChatSurveyPaneStateful = props => {
45
47
  surveyURL: ((_props$controlProps = props.controlProps) === null || _props$controlProps === void 0 ? void 0 : _props$controlProps.surveyURL) ?? surveyInviteLink,
46
48
  ...props.controlProps
47
49
  };
50
+ if (controlProps.surveyURL) {
51
+ if (!isValidSurveyUrl(controlProps.surveyURL)) {
52
+ TelemetryHelper.logLoadingEvent(LogLevel.ERROR, {
53
+ Event: TelemetryEvent.PostChatSurveyUrlValidationFailed,
54
+ Description: `${controlProps.surveyURL} is not a valid Survey URL`
55
+ });
56
+ controlProps.surveyURL = "";
57
+ } else {
58
+ TelemetryHelper.logLoadingEvent(LogLevel.INFO, {
59
+ Event: TelemetryEvent.PostChatSurveyUrlValidationCompleted,
60
+ Description: `${controlProps.surveyURL} is a valid Survey URL`
61
+ });
62
+ }
63
+ }
48
64
 
49
65
  // Move focus to the first button
50
66
  useEffect(() => {
@@ -0,0 +1,21 @@
1
+ import { isNullOrEmptyString } from "../../../common/utils";
2
+ const validRootDomains = ["microsoft.com", "microsoft.us", "appsplatform.us", "powervirtualagents.cn"];
3
+ const isValidSurveyUrl = url => {
4
+ if (isNullOrEmptyString(url)) {
5
+ return false;
6
+ }
7
+ try {
8
+ const objectUrl = new URL(url);
9
+ if (!objectUrl.origin || objectUrl.origin === "null") {
10
+ return false;
11
+ }
12
+ const validDomain = validRootDomains.find(domain => objectUrl.origin.endsWith(domain));
13
+ if (validDomain) {
14
+ return true;
15
+ }
16
+ } catch (error) {
17
+ return false;
18
+ }
19
+ return false;
20
+ };
21
+ export default isValidSurveyUrl;
@@ -1,9 +1,9 @@
1
- import { ConversationState } from "./ConversationState";
2
- import { defaultMiddlewareLocalizedTexts } from "../../components/webchatcontainerstateful/common/defaultProps/defaultMiddlewareLocalizedTexts";
3
- import { getWidgetCacheIdfromProps, isNullOrUndefined } from "../../common/utils";
4
- import { defaultClientDataStoreProvider } from "../../common/storage/default/defaultClientDataStoreProvider";
5
1
  import { ConfirmationState, Constants, ConversationEndEntity, StorageType } from "../../common/Constants";
2
+ import { getWidgetCacheIdfromProps, isNullOrUndefined } from "../../common/utils";
3
+ import { ConversationState } from "./ConversationState";
6
4
  import { StartChatFailureType } from "./StartChatFailureType";
5
+ import { defaultClientDataStoreProvider } from "../../common/storage/default/defaultClientDataStoreProvider";
6
+ import { defaultMiddlewareLocalizedTexts } from "../../components/webchatcontainerstateful/common/defaultProps/defaultMiddlewareLocalizedTexts";
7
7
  export const getLiveChatWidgetContextInitialState = props => {
8
8
  var _props$controlProps, _props$webChatContain;
9
9
  const widgetCacheId = getWidgetCacheIdfromProps(props);
@@ -0,0 +1,6 @@
1
+ export let ScenarioType;
2
+ (function (ScenarioType) {
3
+ ScenarioType["UserSendMessageStrategy"] = "UserSendMessageStrategy";
4
+ ScenarioType["SystemMessageStrategy"] = "SystemMessageStrategy";
5
+ ScenarioType["ReceivedMessageStrategy"] = "ReceivedMessageStrategy";
6
+ })(ScenarioType || (ScenarioType = {}));
@@ -0,0 +1,145 @@
1
+ function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
2
+ function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
3
+ function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
4
+ import { LogLevel, TelemetryEvent } from "../common/telemetry/TelemetryConstants";
5
+ import { TelemetryHelper } from "../common/telemetry/TelemetryHelper";
6
+ export class FirstResponseLatencyTracker {
7
+ constructor() {
8
+ _defineProperty(this, "isABotConversation", false);
9
+ _defineProperty(this, "isStarted", false);
10
+ _defineProperty(this, "isEnded", false);
11
+ _defineProperty(this, "startTrackingMessage", void 0);
12
+ _defineProperty(this, "stopTrackingMessage", void 0);
13
+ // this is a workaround to ensure in reload we track effectively the messages
14
+ // we do have a mechanism in place to prevent log agent messages.
15
+ this.isABotConversation = true;
16
+ }
17
+ createTrackingMessage(payload, type) {
18
+ return {
19
+ Id: payload.Id,
20
+ role: payload.role,
21
+ timestamp: payload === null || payload === void 0 ? void 0 : payload.timestamp,
22
+ tags: payload.tags,
23
+ messageType: payload.messageType,
24
+ text: payload.text,
25
+ type: type,
26
+ checkTime: new Date().getTime()
27
+ };
28
+ }
29
+
30
+ // Tracking Functions
31
+ startTracking(payload) {
32
+ // this prevents to initiate tracking for multiple incoming messages
33
+ if (this.isStarted) {
34
+ return;
35
+ }
36
+ // this is to ensure we track only messages where bot is engaged
37
+ if (!this.isABotConversation) {
38
+ return;
39
+ }
40
+
41
+ // control of states to prevent clashing of messages
42
+ this.isStarted = true;
43
+ this.isEnded = false;
44
+
45
+ // The idea of using types is to enrich telemetry data
46
+ this.startTrackingMessage = this.createTrackingMessage(payload, "userMessage");
47
+ }
48
+ handleAgentMessage(payload) {
49
+ var _payload$tags;
50
+ // this tag so far is only present in agent messages
51
+ if (payload !== null && payload !== void 0 && (_payload$tags = payload.tags) !== null && _payload$tags !== void 0 && _payload$tags.includes("public")) {
52
+ this.deregister();
53
+ }
54
+ }
55
+ stopTracking(payload) {
56
+ var _this$stopTrackingMes, _this$startTrackingMe;
57
+ // this prevents execution for multiple incoming messages from the bot.
58
+ if (this.isEnded && !this.isStarted) {
59
+ return;
60
+ }
61
+
62
+ // control of states to prevent clashing of messages
63
+ this.isEnded = true;
64
+ this.isStarted = false;
65
+
66
+ // The idea of using types is to enrich telemetry data
67
+ this.stopTrackingMessage = this.createTrackingMessage(payload, "botMessage");
68
+ // calculating elapsed time
69
+ const elapsedTime = (((_this$stopTrackingMes = this.stopTrackingMessage) === null || _this$stopTrackingMes === void 0 ? void 0 : _this$stopTrackingMes.checkTime) ?? 0) - (((_this$startTrackingMe = this.startTrackingMessage) === null || _this$startTrackingMe === void 0 ? void 0 : _this$startTrackingMe.checkTime) ?? 0);
70
+ TelemetryHelper.logActionEvent(LogLevel.INFO, {
71
+ Event: TelemetryEvent.MessageLapTrack,
72
+ Description: "First response latency tracking",
73
+ CustomProperties: {
74
+ elapsedTime,
75
+ userMessage: this.startTrackingMessage,
76
+ botMessage: this.stopTrackingMessage
77
+ }
78
+ });
79
+ }
80
+
81
+ // mechanism to ensure we track only allowed conversations
82
+ isMessageFromValidSender(payload) {
83
+ var _payload$tags2;
84
+ // agent scenario
85
+ if (payload !== null && payload !== void 0 && (_payload$tags2 = payload.tags) !== null && _payload$tags2 !== void 0 && _payload$tags2.includes("public")) {
86
+ this.handleAgentMessage(payload);
87
+ return false;
88
+ }
89
+ return true;
90
+ }
91
+ startClock(payload) {
92
+ try {
93
+ if (!payload || !payload.Id) {
94
+ throw new Error("Invalid payload");
95
+ }
96
+ // in the case of a reload, tracker will be paused, until last history message is received
97
+ // this is because we dont have a way to identidy send messages as part of the history
98
+ //if (this.inPause) return;
99
+ this.startTracking(payload);
100
+ } catch (e) {
101
+ TelemetryHelper.logActionEvent(LogLevel.ERROR, {
102
+ Event: TelemetryEvent.MessageStartLapTrackError,
103
+ Description: "Error while starting the clock",
104
+ ExceptionDetails: e,
105
+ CustomProperties: {
106
+ payload: payload
107
+ }
108
+ });
109
+ }
110
+ }
111
+ stopClock(payload) {
112
+ try {
113
+ if (!payload || !payload.Id) {
114
+ throw new Error("Invalid payload");
115
+ }
116
+ if (!this.isMessageFromValidSender(payload)) return;
117
+ if (this.isABotConversation && this.isStarted) {
118
+ this.stopTracking(payload);
119
+ }
120
+ } catch (e) {
121
+ console.error("FRL : error while trying to stop the tracker", e);
122
+ TelemetryHelper.logActionEvent(LogLevel.ERROR, {
123
+ Event: TelemetryEvent.MessageStopLapTrackError,
124
+ Description: "Error while stopping the clock",
125
+ ExceptionDetails: e,
126
+ CustomProperties: {
127
+ payload: payload
128
+ }
129
+ });
130
+ //reset state
131
+ this.startTrackingMessage = undefined;
132
+ this.stopTrackingMessage = undefined;
133
+ this.isStarted = false;
134
+ this.isEnded = false;
135
+ }
136
+ }
137
+ deregister() {
138
+ // Reset State
139
+ this.isABotConversation = false;
140
+ this.isStarted = false;
141
+ this.isEnded = false;
142
+ this.startTrackingMessage = undefined;
143
+ this.stopTrackingMessage = undefined;
144
+ }
145
+ }