@microsoft/omnichannel-chat-widget 1.8.2-main.5195aba → 1.8.2-main.5a42a08

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 (40) hide show
  1. package/README.md +46 -1
  2. package/lib/cjs/common/Constants.js +6 -0
  3. package/lib/cjs/common/telemetry/TelemetryConstants.js +1 -0
  4. package/lib/cjs/common/utils.js +21 -2
  5. package/lib/cjs/components/chatbuttonstateful/ChatButtonStateful.js +4 -4
  6. package/lib/cjs/components/livechatwidget/common/createMarkdown.js +54 -1
  7. package/lib/cjs/components/livechatwidget/common/customEventHandler.js +53 -0
  8. package/lib/cjs/components/livechatwidget/common/initWebChatComposer.js +4 -1
  9. package/lib/cjs/components/livechatwidget/common/startChat.js +1 -1
  10. package/lib/cjs/components/livechatwidget/livechatwidgetstateful/LiveChatWidgetStateful.js +5 -1
  11. package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/callActionMiddleware.js +42 -0
  12. package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/customEventMiddleware.js +41 -0
  13. package/lib/cjs/contexts/common/CustomEventType.js +1 -0
  14. package/lib/cjs/firstresponselatency/FirstMessageTrackerFromBot.js +101 -36
  15. package/lib/cjs/firstresponselatency/FirstResponseLatencyTracker.js +39 -21
  16. package/lib/cjs/firstresponselatency/util.js +12 -8
  17. package/lib/esm/common/Constants.js +6 -0
  18. package/lib/esm/common/telemetry/TelemetryConstants.js +1 -0
  19. package/lib/esm/common/utils.js +17 -0
  20. package/lib/esm/components/chatbuttonstateful/ChatButtonStateful.js +4 -4
  21. package/lib/esm/components/livechatwidget/common/createMarkdown.js +54 -1
  22. package/lib/esm/components/livechatwidget/common/customEventHandler.js +45 -0
  23. package/lib/esm/components/livechatwidget/common/initWebChatComposer.js +4 -1
  24. package/lib/esm/components/livechatwidget/common/startChat.js +1 -1
  25. package/lib/esm/components/livechatwidget/livechatwidgetstateful/LiveChatWidgetStateful.js +5 -1
  26. package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/callActionMiddleware.js +36 -0
  27. package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/customEventMiddleware.js +33 -0
  28. package/lib/esm/contexts/common/CustomEventType.js +1 -0
  29. package/lib/esm/firstresponselatency/FirstMessageTrackerFromBot.js +101 -36
  30. package/lib/esm/firstresponselatency/FirstResponseLatencyTracker.js +39 -21
  31. package/lib/esm/firstresponselatency/util.js +12 -8
  32. package/lib/types/common/Constants.d.ts +6 -0
  33. package/lib/types/common/telemetry/TelemetryConstants.d.ts +1 -0
  34. package/lib/types/common/utils.d.ts +3 -0
  35. package/lib/types/components/livechatwidget/common/customEventHandler.d.ts +4 -0
  36. package/lib/types/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/callActionMiddleware.d.ts +8 -0
  37. package/lib/types/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/customEventMiddleware.d.ts +22 -0
  38. package/lib/types/contexts/common/CustomEventType.d.ts +6 -0
  39. package/lib/types/firstresponselatency/FirstResponseLatencyTracker.d.ts +2 -2
  40. package/package.json +2 -2
package/README.md CHANGED
@@ -223,6 +223,51 @@ const customizedFooterProp: IFooterProps = {
223
223
 
224
224
  > :pushpin: Note that [WebChat hooks](https://github.com/microsoft/BotFramework-WebChat/blob/main/docs/HOOKS.md) can also be used in any custom components.
225
225
 
226
+ #### Bidirectional Custom Events
227
+ - Sending events from a hosting web page to bots/agents
228
+ - Register a function to post event
229
+ ```js
230
+ //define sendCustomEvent function
231
+ const sendCustomEvent = (payload) => {
232
+ const customEvent = {
233
+ eventName: "sendCustomEvent",
234
+ payload
235
+ };
236
+ BroadcastService.postMessage(customEvent);
237
+ };
238
+
239
+ //attach the sendCustomEvent function to window object
240
+ window["sendCustomEvent"] = sendCustomEvent;
241
+
242
+ //invoke the sendCustomEvent function with some customized payload
243
+ window.sendCustomEvent({
244
+ customEventName: "TestEvent",
245
+ customEventValue: {
246
+ boolVar: true,
247
+ displayableVar: {
248
+ isDisplayable: true,
249
+ value: "From C2: "+ new Date().toISOString()
250
+ },
251
+ numberVar: -10.5,
252
+ stringVar: "Hello from C2 str: " + new Date().toISOString()
253
+ }
254
+ })
255
+ ```
256
+ - Receiving events from bots/agents
257
+ ```js
258
+ //define setOnCustomEvent function
259
+ const setOnCustomEvent = (callback) => {
260
+ BroadcastService.getMessageByEventName("onCustomEvent").subscribe((event) => {
261
+ if (event && typeof callback === "function") {
262
+ callback(event);
263
+ }
264
+ });
265
+ };
266
+
267
+ //set callback function
268
+ setOnCustomEvent((event) => console.log(event));
269
+ ```
270
+
226
271
  ## See Also
227
272
 
228
273
  [Customizations Dev Guide](https://github.com/microsoft/omnichannel-chat-widget/blob/main/docs/customizations/getstarted.md)\
@@ -232,4 +277,4 @@ const customizedFooterProp: IFooterProps = {
232
277
  [How to Add Visual Regression Tests](https://github.com/microsoft/omnichannel-chat-widget/blob/main/docs/VisualRegressionTestingGuide.md)\
233
278
  [Security](https://github.com/microsoft/omnichannel-chat-widget/blob/main/SECURITY.md)\
234
279
  [Third Party Cookie Support](https://github.com/microsoft/omnichannel-chat-widget/blob/main/docs/Tpc.md)\
235
- [Storybook](https://microsoft.github.io/omnichannel-chat-widget/docs/storybook/)
280
+ [Storybook](https://microsoft.github.io/omnichannel-chat-widget/docs/storybook/)
@@ -32,6 +32,7 @@ _defineProperty(Constants, "webchatChannelId", "webchat");
32
32
  _defineProperty(Constants, "markdown", "markdown");
33
33
  _defineProperty(Constants, "actionType", "actionType");
34
34
  _defineProperty(Constants, "markDownSystemMessageClass", "webchat__basic-transcript__activity-markdown-body");
35
+ _defineProperty(Constants, "MARKDOWN_LIST_INDENTATION", " ");
35
36
  _defineProperty(Constants, "String", "string");
36
37
  _defineProperty(Constants, "ChatMessagesJson", "chatMessagesJson");
37
38
  _defineProperty(Constants, "truePascal", "True");
@@ -131,6 +132,11 @@ _defineProperty(Constants, "InitContextParamsResponse", "initContextParamsRespon
131
132
  _defineProperty(Constants, "OCOriginalMessageId", "OriginalMessageId");
132
133
  _defineProperty(Constants, "WebchatSequenceIdAttribute", "webchat:sequence-id");
133
134
  _defineProperty(Constants, "MessageSequenceIdOverride", "MessageSequenceIdOverride");
135
+ _defineProperty(Constants, "sendCustomEvent", "sendCustomEvent");
136
+ _defineProperty(Constants, "onCustomEvent", "onCustomEvent");
137
+ _defineProperty(Constants, "customEventName", "customEventName");
138
+ _defineProperty(Constants, "customEventValue", "customEventValue");
139
+ _defineProperty(Constants, "Hidden", "Hidden");
134
140
  _defineProperty(Constants, "EndConversationDueToOverflow", "endconversationduetooverflow");
135
141
  const Regex = (_class = /*#__PURE__*/_createClass(function Regex() {
136
142
  _classCallCheck(this, Regex);
@@ -229,6 +229,7 @@ exports.TelemetryEvent = TelemetryEvent;
229
229
  TelemetryEvent["SystemMessageReceived"] = "SystemMessageReceived";
230
230
  TelemetryEvent["RehydrateMessageReceived"] = "RehydrateMessageReceived";
231
231
  TelemetryEvent["CustomContextReceived"] = "CustomContextReceived";
232
+ TelemetryEvent["CustomEventAction"] = "CustomEventAction";
232
233
  TelemetryEvent["NetworkDisconnected"] = "NetworkDisconnected";
233
234
  TelemetryEvent["NetworkReconnected"] = "NetworkReconnected";
234
235
  TelemetryEvent["LinkModePostChatWorkflowStarted"] = "LinkModePostChatWorkflowStarted";
@@ -3,11 +3,11 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.getConversationDetailsCall = exports.getBroadcastChannelName = exports.formatTemplateString = exports.findParentFocusableElementsWithoutChildContainer = exports.findAllFocusableElement = exports.extractPreChatSurveyResponseValues = exports.escapeHtml = exports.debounceLeading = exports.createTimer = exports.createFileAndDownload = exports.checkContactIdError = exports.changeLanguageCodeFormatForWebChat = exports.addDelayInMs = void 0;
6
+ exports.getCustomEventValue = exports.getConversationDetailsCall = exports.getBroadcastChannelName = exports.formatTemplateString = exports.findParentFocusableElementsWithoutChildContainer = exports.findAllFocusableElement = exports.extractPreChatSurveyResponseValues = exports.escapeHtml = exports.debounceLeading = exports.createTimer = exports.createFileAndDownload = exports.checkContactIdError = exports.changeLanguageCodeFormatForWebChat = exports.addDelayInMs = void 0;
7
7
  exports.getDeviceType = getDeviceType;
8
8
  exports.getWidgetEndChatEventName = exports.getWidgetCacheIdfromProps = exports.getWidgetCacheId = exports.getTimestampHourMinute = exports.getStateFromCache = exports.getLocaleDirection = exports.getIconText = exports.getDomain = void 0;
9
9
  exports.isEndConversationDueToOverflowActivity = isEndConversationDueToOverflowActivity;
10
- exports.setTabIndices = exports.setOcUserAgent = exports.setFocusOnSendBox = exports.setFocusOnElement = exports.preventFocusToMoveOutOfElement = exports.parseLowerCaseString = exports.parseAdaptiveCardPayload = exports.newGuid = exports.isUndefinedOrEmpty = exports.isThisSessionPopout = exports.isNullOrUndefined = exports.isNullOrEmptyString = void 0;
10
+ exports.setTabIndices = exports.setOcUserAgent = exports.setFocusOnSendBox = exports.setFocusOnElement = exports.preventFocusToMoveOutOfElement = exports.parseLowerCaseString = exports.parseAdaptiveCardPayload = exports.newGuid = exports.isValidCustomEvent = exports.isUndefinedOrEmpty = exports.isThisSessionPopout = exports.isNullOrUndefined = exports.isNullOrEmptyString = void 0;
11
11
  var _Constants = require("./Constants");
12
12
  var _TelemetryConstants = require("./telemetry/TelemetryConstants");
13
13
  var _omnichannelChatComponents = require("@microsoft/omnichannel-chat-components");
@@ -497,6 +497,25 @@ function getDeviceType() {
497
497
  return "standard";
498
498
  }
499
499
  }
500
+
501
+ //Bots expect a payload containing:
502
+ //1. customEventName: this should be string describe the event name
503
+ //2. customEventValue: given the value is from customer with unknown type, it is required to stringify the payload later
504
+ const isValidCustomEvent = payload => {
505
+ if (_Constants.Constants.customEventName in payload && payload.customEventName && typeof payload.customEventName === _Constants.Constants.String && _Constants.Constants.customEventValue in payload && payload.customEventValue) return true;
506
+ return false;
507
+ };
508
+ exports.isValidCustomEvent = isValidCustomEvent;
509
+ const getCustomEventValue = customEventPayload => {
510
+ let returnVal = "";
511
+ try {
512
+ returnVal = typeof customEventPayload.customEventValue === _Constants.Constants.String ? customEventPayload.customEventValue : JSON.stringify(customEventPayload.customEventValue);
513
+ } catch (error) {
514
+ console.error(error);
515
+ }
516
+ return returnVal;
517
+ };
518
+ exports.getCustomEventValue = getCustomEventValue;
500
519
  function isEndConversationDueToOverflowActivity(activity) {
501
520
  var _activity$channelData, _activity$channelData2;
502
521
  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.Constants.EndConversationDueToOverflow);
@@ -74,7 +74,6 @@ const ChatButtonStateful = props => {
74
74
  };
75
75
  const outOfOfficeStyleProps = Object.assign({}, _defaultOutOfOfficeChatButtonStyleProps.defaultOutOfOfficeChatButtonStyleProps, outOfOfficeButtonProps === null || outOfOfficeButtonProps === void 0 ? void 0 : outOfOfficeButtonProps.styleProps);
76
76
  const controlProps = {
77
- ...(buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.controlProps),
78
77
  id: "oc-lcw-chat-button",
79
78
  dir: state.domainStates.globalDir,
80
79
  titleText: "Let's Chat!",
@@ -83,7 +82,8 @@ const ChatButtonStateful = props => {
83
82
  unreadMessageCount: state.appStates.unreadMessageCount ? state.appStates.unreadMessageCount > _Constants.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",
84
83
  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,
85
84
  // Regular chat button onClick - this will always take precedence
86
- onClick: () => ref.current()
85
+ onClick: () => ref.current(),
86
+ ...(buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.controlProps)
87
87
  };
88
88
  const outOfOfficeControlProps = {
89
89
  // Only take specific properties from outOfOfficeButtonProps, never onClick
@@ -92,7 +92,6 @@ const ChatButtonStateful = props => {
92
92
  titleText: (outOfOfficeButtonProps === null || outOfOfficeButtonProps === void 0 ? void 0 : (_outOfOfficeButtonPro = outOfOfficeButtonProps.controlProps) === null || _outOfOfficeButtonPro === void 0 ? void 0 : _outOfOfficeButtonPro.titleText) || "We're Offline",
93
93
  subtitleText: (outOfOfficeButtonProps === null || outOfOfficeButtonProps === void 0 ? void 0 : (_outOfOfficeButtonPro2 = outOfOfficeButtonProps.controlProps) === null || _outOfOfficeButtonPro2 === void 0 ? void 0 : _outOfOfficeButtonPro2.subtitleText) || "No agents available",
94
94
  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,
95
- ...(outOfOfficeButtonProps === null || outOfOfficeButtonProps === void 0 ? void 0 : outOfOfficeButtonProps.controlProps),
96
95
  // Out-of-office specific onClick - this will ALWAYS take precedence
97
96
  onClick: () => {
98
97
  if (state.appStates.isMinimized) {
@@ -105,7 +104,8 @@ const ChatButtonStateful = props => {
105
104
  type: _LiveChatWidgetActionType.LiveChatWidgetActionType.SET_CONVERSATION_STATE,
106
105
  payload: _ConversationState.ConversationState.OutOfOffice
107
106
  });
108
- }
107
+ },
108
+ ...(outOfOfficeButtonProps === null || outOfOfficeButtonProps === void 0 ? void 0 : outOfOfficeButtonProps.controlProps)
109
109
  };
110
110
  (0, _react.useEffect)(() => {
111
111
  _TelemetryHelper.TelemetryHelper.logLoadingEvent(_TelemetryConstants.LogLevel.INFO, {
@@ -34,12 +34,65 @@ const createMarkdown = (disableMarkdownMessageFormatting, disableNewLineMarkdown
34
34
  // Rule to process html blocks and paragraphs
35
35
  "html_inline",
36
36
  // Rule to process html tags
37
- "newline" // Rule to proceess '\n'
37
+ "newline",
38
+ // Rule to proceess '\n'
39
+ "list" // Enable list parsing rule
38
40
  ]);
39
41
  }
40
42
 
41
43
  markdown.disable(["strikethrough"]);
42
44
 
45
+ // Custom plugin to fix numbered list continuity
46
+ markdown.use(function (md) {
47
+ const originalRender = md.render.bind(md);
48
+ const originalRenderInline = md.renderInline.bind(md);
49
+ function preprocessText(text) {
50
+ // Handle numbered lists that come with double line breaks (knowledge article format)
51
+ // This ensures proper continuous numbering instead of separate lists
52
+
53
+ let result = text;
54
+
55
+ // Only process if the text contains the double line break pattern
56
+ // But exclude simple numbered lists (where content after \n\n starts with another number)
57
+ if (!/\d+\.\s+.*?\n\n(?!\d+\.\s)[\s\S]*?(?:\n\n\d+\.|\s*$)/.test(text)) {
58
+ return result;
59
+ }
60
+
61
+ // Convert "1. Item\n\nContent\n\n2. Item" to proper markdown list format
62
+ // Use improved pattern with negative lookahead to exclude cases where content starts with numbered list
63
+ const listPattern = /(\d+\.\s+[^\n]+)(\n\n(?!\d+\.\s)[\s\S]*?)(?=\n\n\d+\.|\s*$)/g;
64
+ if (listPattern.test(result)) {
65
+ // Reset regex state for actual replacement
66
+ listPattern.lastIndex = 0;
67
+ result = result.replace(listPattern, (match, listItem, content) => {
68
+ if (!content) {
69
+ return match;
70
+ }
71
+
72
+ // Format content with proper indentation
73
+ const cleanContent = content.substring(2); // Remove leading \n\n
74
+ const lines = cleanContent.split("\n");
75
+ const indentedContent = lines.map(line => line.trim() ? `${_Constants.Constants.MARKDOWN_LIST_INDENTATION}${line}` : "").join("\n");
76
+ const lineBreak = disableNewLineMarkdownSupport ? "\n" : "\n\n";
77
+ return `${listItem}${lineBreak}${indentedContent}`;
78
+ });
79
+ }
80
+ return result;
81
+ }
82
+
83
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
84
+ md.render = function (text, env) {
85
+ const processedText = preprocessText(text);
86
+ return originalRender(processedText, env);
87
+ };
88
+
89
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
90
+ md.renderInline = function (text, env) {
91
+ const processedText = preprocessText(text);
92
+ return originalRenderInline(processedText, env);
93
+ };
94
+ });
95
+
43
96
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
44
97
  markdown.use(_markdownItForInline.default, "url_new_win", "link_open", function (tokens, idx, env) {
45
98
  const targetAttrIndex = tokens[idx].attrIndex(_Constants.Constants.Target);
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.subscribeToSendCustomEvent = exports.customEventCallback = void 0;
7
+ var _Constants = require("../../../common/Constants");
8
+ var _TelemetryHelper = require("../../../common/telemetry/TelemetryHelper");
9
+ var _TelemetryConstants = require("../../../common/telemetry/TelemetryConstants");
10
+ var _utils = require("../../../common/utils");
11
+ const customEventCallback = facadeChatSDK => event => {
12
+ if (!(_Constants.Constants.payload in event)) return;
13
+ if ((0, _utils.isValidCustomEvent)(event.payload)) {
14
+ const customEventPayload = event.payload;
15
+ try {
16
+ const customEventValueStr = (0, _utils.getCustomEventValue)(customEventPayload);
17
+ const customEventName = customEventPayload.customEventName;
18
+ const messageMeta = {
19
+ customEvent: _Constants.Constants.true,
20
+ customEventName: customEventName,
21
+ customEventValue: customEventValueStr
22
+ };
23
+ const messagePayload = {
24
+ content: "",
25
+ tags: [_Constants.Constants.Hidden],
26
+ metadata: messageMeta,
27
+ timestamp: new Date()
28
+ };
29
+ facadeChatSDK.sendMessage(messagePayload);
30
+ _TelemetryHelper.TelemetryHelper.logActionEventToAllTelemetry(_TelemetryConstants.LogLevel.DEBUG, {
31
+ Event: _TelemetryConstants.TelemetryEvent.CustomEventAction,
32
+ Description: "Sent customEvent.",
33
+ CustomProperties: {
34
+ customEventName,
35
+ lengthCustomEventValue: customEventValueStr.length
36
+ }
37
+ });
38
+ } catch (error) {
39
+ _TelemetryHelper.TelemetryHelper.logActionEventToAllTelemetry(_TelemetryConstants.LogLevel.ERROR, {
40
+ Event: _TelemetryConstants.TelemetryEvent.CustomEventAction,
41
+ Description: "Failed to process CustomEvent.",
42
+ ExceptionDetails: {
43
+ error
44
+ }
45
+ });
46
+ }
47
+ }
48
+ };
49
+ exports.customEventCallback = customEventCallback;
50
+ const subscribeToSendCustomEvent = (broadcastService, facadeChatSDK, customEventCallback) => {
51
+ broadcastService.getMessageByEventName(_Constants.Constants.sendCustomEvent).subscribe(customEventCallback(facadeChatSDK));
52
+ };
53
+ exports.subscribeToSendCustomEvent = subscribeToSendCustomEvent;
@@ -37,9 +37,12 @@ var _htmlPlayerMiddleware = _interopRequireDefault(require("../../webchatcontain
37
37
  var _htmlTextMiddleware = _interopRequireDefault(require("../../webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/htmlTextMiddleware"));
38
38
  var _preProcessingMiddleware = _interopRequireDefault(require("../../webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/preProcessingMiddleware"));
39
39
  var _sanitizationMiddleware = _interopRequireDefault(require("../../webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/sanitizationMiddleware"));
40
+ var _callActionMiddleware = _interopRequireDefault(require("../../webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/callActionMiddleware"));
41
+ var _customEventMiddleware = _interopRequireDefault(require("../../webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/customEventMiddleware"));
40
42
  var _ConversationState = require("../../../contexts/common/ConversationState");
41
43
  var _createReducer = require("../../../contexts/createReducer");
42
44
  var _queueOverflowHandlerMiddleware = require("../../webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/queueOverflowHandlerMiddleware");
45
+ var _omnichannelChatComponents = require("@microsoft/omnichannel-chat-components");
43
46
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
44
47
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
45
48
  const initWebChatComposer = (props, state, dispatch, facadeChatSDK, endChat) => {
@@ -118,7 +121,7 @@ const initWebChatComposer = (props, state, dispatch, facadeChatSDK, endChat) =>
118
121
  };
119
122
  webChatStore = (0, _botframeworkWebchat.createStore)({},
120
123
  //initial state
121
- _preProcessingMiddleware.default, _attachmentProcessingMiddleware.default, (0, _attachmentUploadValidatorMiddleware.default)((_state$domainStates$l = state.domainStates.liveChatConfig) === null || _state$domainStates$l === void 0 ? void 0 : _state$domainStates$l.allowedFileExtensions, (_state$domainStates$l2 = state.domainStates.liveChatConfig) === null || _state$domainStates$l2 === void 0 ? void 0 : _state$domainStates$l2.maxUploadFileSize, localizedTexts), (0, _queueOverflowHandlerMiddleware.createQueueOverflowMiddleware)(state, dispatch), (0, _channelDataMiddleware.default)(addConversationalSurveyTagsCallback), (0, _conversationEndMiddleware.default)(conversationEndCallback, startConversationalSurveyCallback, endConversationalSurveyCallback), (0, _dataMaskingMiddleware.default)((_state$domainStates$l3 = state.domainStates.liveChatConfig) === null || _state$domainStates$l3 === void 0 ? void 0 : _state$domainStates$l3.DataMaskingInfo), _messageTimestampMiddleware.createMessageTimeStampMiddleware, _messageSequenceIdOverrideMiddleware.createMessageSequenceIdOverrideMiddleware, _gifUploadMiddleware.default, _htmlPlayerMiddleware.default, (0, _htmlTextMiddleware.default)(honorsTargetInHTMLLinks), (0, _maxMessageSizeValidator.default)(localizedTexts), _sanitizationMiddleware.default,
124
+ _preProcessingMiddleware.default, _attachmentProcessingMiddleware.default, (0, _attachmentUploadValidatorMiddleware.default)((_state$domainStates$l = state.domainStates.liveChatConfig) === null || _state$domainStates$l === void 0 ? void 0 : _state$domainStates$l.allowedFileExtensions, (_state$domainStates$l2 = state.domainStates.liveChatConfig) === null || _state$domainStates$l2 === void 0 ? void 0 : _state$domainStates$l2.maxUploadFileSize, localizedTexts), (0, _customEventMiddleware.default)(_omnichannelChatComponents.BroadcastService), (0, _queueOverflowHandlerMiddleware.createQueueOverflowMiddleware)(state, dispatch), (0, _channelDataMiddleware.default)(addConversationalSurveyTagsCallback), (0, _conversationEndMiddleware.default)(conversationEndCallback, startConversationalSurveyCallback, endConversationalSurveyCallback), (0, _dataMaskingMiddleware.default)((_state$domainStates$l3 = state.domainStates.liveChatConfig) === null || _state$domainStates$l3 === void 0 ? void 0 : _state$domainStates$l3.DataMaskingInfo), _messageTimestampMiddleware.createMessageTimeStampMiddleware, _messageSequenceIdOverrideMiddleware.createMessageSequenceIdOverrideMiddleware, _gifUploadMiddleware.default, _htmlPlayerMiddleware.default, (0, _htmlTextMiddleware.default)(honorsTargetInHTMLLinks), (0, _maxMessageSizeValidator.default)(localizedTexts), _sanitizationMiddleware.default, (0, _callActionMiddleware.default)(),
122
125
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
123
126
  ...(((_props$webChatContain7 = props.webChatContainerProps) === null || _props$webChatContain7 === void 0 ? void 0 : _props$webChatContain7.storeMiddlewares) ?? []));
124
127
  _WebChatStoreLoader.WebChatStoreLoader.store = webChatStore;
@@ -154,7 +154,6 @@ const setPreChatAndInitiateChat = async (facadeChatSDK, dispatch, setAdapter, is
154
154
  const optionalParams = {
155
155
  isProactiveChat
156
156
  };
157
- (0, _FirstMessageTrackerFromBot.createTrackingForFirstMessage)();
158
157
  await initStartChat(facadeChatSDK, dispatch, setAdapter, state, props, optionalParams);
159
158
  };
160
159
 
@@ -206,6 +205,7 @@ const initStartChat = async (facadeChatSDK, dispatch, setAdapter, state, props,
206
205
  const startChatOptionalParams = Object.assign({}, params, optionalParams, defaultOptionalParams);
207
206
  // startTime is used to determine if a message is history or new, better to be set before creating the adapter to get bandwidth
208
207
  const startTime = new Date().getTime();
208
+ (0, _FirstMessageTrackerFromBot.createTrackingForFirstMessage)();
209
209
  await facadeChatSDK.startChat(startChatOptionalParams);
210
210
  isStartChatSuccessful = true;
211
211
  await createAdapterAndSubscribe(facadeChatSDK, dispatch, setAdapter, startTime, props);
@@ -57,6 +57,7 @@ var _startProactiveChat = require("../common/startProactiveChat");
57
57
  var _useChatAdapterStore = _interopRequireDefault(require("../../../hooks/useChatAdapterStore"));
58
58
  var _useChatContextStore = _interopRequireDefault(require("../../../hooks/useChatContextStore"));
59
59
  var _useFacadeChatSDKStore = _interopRequireDefault(require("../../../hooks/useFacadeChatSDKStore"));
60
+ var _customEventHandler = require("../common/customEventHandler");
60
61
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
61
62
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
62
63
  function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
@@ -421,7 +422,7 @@ const LiveChatWidgetStateful = props => {
421
422
  _omnichannelChatComponents.BroadcastService.getMessageByEventName(_TelemetryConstants.BroadcastEvent.StartChat).subscribe(msg => {
422
423
  var _msg$payload5, _msg$payload6, _msg$payload7, _msg$payload9, _inMemoryState$appSta2, _inMemoryState$appSta3, _inMemoryState$appSta4;
423
424
  // If chat is out of operating hours chat widget sets the conversation state to OutOfOffice.
424
- if (state.appStates.outsideOperatingHours === true) {
425
+ if (state.appStates.outsideOperatingHours && state.appStates.conversationState !== _ConversationState.ConversationState.Active) {
425
426
  dispatch({
426
427
  type: _LiveChatWidgetActionType.LiveChatWidgetActionType.SET_MINIMIZED,
427
428
  payload: false
@@ -588,6 +589,9 @@ const LiveChatWidgetStateful = props => {
588
589
  }
589
590
  });
590
591
 
592
+ // subscribe custom event
593
+ (0, _customEventHandler.subscribeToSendCustomEvent)(_omnichannelChatComponents.BroadcastService, facadeChatSDK, _customEventHandler.customEventCallback);
594
+
591
595
  // Check for TPC and log in telemetry if blocked
592
596
  (0, _defaultClientDataStoreProvider.isCookieAllowed)();
593
597
  return () => {
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _WebChatActionType = require("../../enums/WebChatActionType");
8
+ /******
9
+ * CallActionMiddleware
10
+ *
11
+ * Intercepts custom call actions and handles tel: URL navigation
12
+ ******/
13
+
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
15
+ const createCallActionMiddleware = () => () => next => action => {
16
+ // Intercept incoming activities to modify suggested actions with call type
17
+ if (action.type === _WebChatActionType.WebChatActionType.DIRECT_LINE_INCOMING_ACTIVITY) {
18
+ var _action$payload, _activity$suggestedAc;
19
+ const activity = (_action$payload = action.payload) === null || _action$payload === void 0 ? void 0 : _action$payload.activity;
20
+
21
+ // Check if activity has suggested actions with call type
22
+ if (activity !== null && activity !== void 0 && (_activity$suggestedAc = activity.suggestedActions) !== null && _activity$suggestedAc !== void 0 && _activity$suggestedAc.actions) {
23
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
24
+ activity.suggestedActions.actions = activity.suggestedActions.actions.map(suggestedAction => {
25
+ if (suggestedAction.type === "call") {
26
+ // Convert call action to openUrl with encoded tel URL scheme
27
+ const telUrl = suggestedAction.value;
28
+ const convertedAction = {
29
+ ...suggestedAction,
30
+ type: "openUrl",
31
+ value: `tel:${telUrl}`
32
+ };
33
+ return convertedAction;
34
+ }
35
+ return suggestedAction;
36
+ });
37
+ }
38
+ }
39
+ return next(action);
40
+ };
41
+ var _default = createCallActionMiddleware;
42
+ exports.default = _default;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.isValidCustomEvent = exports.default = void 0;
7
+ var _Constants = require("../../../../../common/Constants");
8
+ var _WebChatActionType = require("../../enums/WebChatActionType");
9
+ /******
10
+ * CustomEventMiddleware
11
+ *
12
+ * This middleware is invoked when a custom event is received.
13
+ * The callback is then invoked to handle the custom event.
14
+ ******/
15
+
16
+ const isValidCustomEvent = activity => {
17
+ 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;
18
+ 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.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.Constants.true && (activity === null || activity === void 0 ? void 0 : (_activity$from = activity.from) === null || _activity$from === void 0 ? void 0 : _activity$from.role) !== _Constants.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.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);
19
+ };
20
+ exports.isValidCustomEvent = isValidCustomEvent;
21
+ const createCustomEventMiddleware = broadcastservice => () => next => action => {
22
+ var _action$payload;
23
+ if ((action === null || action === void 0 ? void 0 : action.type) == _WebChatActionType.WebChatActionType.DIRECT_LINE_INCOMING_ACTIVITY && (_action$payload = action.payload) !== null && _action$payload !== void 0 && _action$payload.activity) {
24
+ const activity = action.payload.activity;
25
+ if (isValidCustomEvent(activity)) {
26
+ const customEvent = {
27
+ eventName: _Constants.Constants.onCustomEvent,
28
+ payload: {
29
+ messageId: activity.messageid ?? activity.id,
30
+ customEventName: activity.channelData.metadata.customEventName,
31
+ customEventValue: activity.channelData.metadata.customEventValue
32
+ }
33
+ };
34
+ broadcastservice.postMessage(customEvent);
35
+ return;
36
+ }
37
+ }
38
+ return next(action);
39
+ };
40
+ var _default = createCustomEventMiddleware;
41
+ exports.default = _default;
@@ -0,0 +1 @@
1
+ "use strict";
@@ -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();