@microsoft/omnichannel-chat-widget 2.0.0 → 2.0.1-main.6ca1911
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.
- package/README.md +5 -2
- package/lib/cjs/common/telemetry/TelemetryConstants.js +6 -0
- package/lib/cjs/components/chatbuttonstateful/ChatButtonStateful.js +42 -25
- package/lib/cjs/components/livechatwidget/common/endChat.js +39 -0
- package/lib/cjs/components/livechatwidget/common/initWebChatComposer.js +11 -6
- package/lib/cjs/components/livechatwidget/common/recoveryEligibilityTelemetry.js +60 -0
- package/lib/cjs/components/livechatwidget/common/startChat.js +107 -33
- package/lib/cjs/components/livechatwidget/livechatwidgetstateful/LiveChatWidgetStateful.js +8 -6
- package/lib/cjs/components/webchatcontainerstateful/WebChatContainerStateful.js +2 -2
- package/lib/cjs/components/webchatcontainerstateful/common/defaultProps/defaultMiddlewareLocalizedTexts.js +2 -1
- package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/attachments/AdaptiveCardAccessibilityWrapper.js +16 -2
- package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/attachmentSentAnnouncementMiddleware.js +68 -40
- package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/attachmentUploadValidatorMiddleware.js +0 -27
- package/lib/esm/common/telemetry/TelemetryConstants.js +6 -0
- package/lib/esm/components/chatbuttonstateful/ChatButtonStateful.js +42 -25
- package/lib/esm/components/livechatwidget/common/endChat.js +39 -0
- package/lib/esm/components/livechatwidget/common/initWebChatComposer.js +12 -7
- package/lib/esm/components/livechatwidget/common/recoveryEligibilityTelemetry.js +52 -0
- package/lib/esm/components/livechatwidget/common/startChat.js +104 -31
- package/lib/esm/components/livechatwidget/livechatwidgetstateful/LiveChatWidgetStateful.js +8 -6
- package/lib/esm/components/webchatcontainerstateful/WebChatContainerStateful.js +2 -2
- package/lib/esm/components/webchatcontainerstateful/common/defaultProps/defaultMiddlewareLocalizedTexts.js +2 -1
- package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/attachments/AdaptiveCardAccessibilityWrapper.js +16 -2
- package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/attachmentSentAnnouncementMiddleware.js +65 -39
- package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/attachmentUploadValidatorMiddleware.js +0 -27
- package/lib/types/common/telemetry/TelemetryConstants.d.ts +3 -0
- package/lib/types/components/livechatwidget/common/recoveryEligibilityTelemetry.d.ts +17 -0
- package/lib/types/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/attachmentSentAnnouncementMiddleware.d.ts +11 -4
- package/lib/types/contexts/common/ILiveChatWidgetLocalizedTexts.d.ts +9 -2
- package/package.json +8 -7
package/README.md
CHANGED
|
@@ -53,10 +53,12 @@ This package requires Node.js `>=22.12.0` for consumer builds. Node 20 is not su
|
|
|
53
53
|
For a production application, pin the exact stable versions:
|
|
54
54
|
|
|
55
55
|
```bash
|
|
56
|
-
npm install --save-exact @microsoft/omnichannel-chat-widget@2.0.
|
|
56
|
+
npm install --save-exact @microsoft/omnichannel-chat-widget@2.0.1 @microsoft/omnichannel-chat-components@1.2.0 @microsoft/omnichannel-chat-sdk@2.0.1
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
`2.0.
|
|
59
|
+
`2.0.1` release date is 2026-10-05. Official support ends on 2027-10-05. See the [root support table](https://github.com/microsoft/omnichannel-chat-widget#chat-widget).
|
|
60
|
+
|
|
61
|
+
_**Known issue:**_ Chat SDK versions earlier than `1.11.6` can start duplicate conversations when `startChat()` is called again before the previous call is complete. Chat Widget `1.8.1` and earlier can use these versions. Upgrade the Chat SDK to `1.11.8` or later. See [Duplicate conversations with Chat SDK versions before 1.11.6](https://github.com/microsoft/omnichannel-chat-widget/blob/main/docs/KNOWN_ISSUE_CONCURRENT_STARTCHAT.md).
|
|
60
62
|
|
|
61
63
|
TypeScript consumers that imported the exported Web Chat middleware factories now receive Redux 5 `unknown` actions. Narrow with `isWebChatAction` before reading `type` or `payload`. Runtime behavior is unchanged.
|
|
62
64
|
|
|
@@ -79,6 +81,7 @@ This hotfix still declares these vulnerable transitive versions:
|
|
|
79
81
|
| `swiper` | `8.4.7` |
|
|
80
82
|
| `valibot` | `1.1.0` |
|
|
81
83
|
| `uuid` | `3.4.0`, `8.3.2`, and `9.0.1` |
|
|
84
|
+
| `deepmerge` (via `sanitize-html@2.14.0`) | `4.3.1` |
|
|
82
85
|
|
|
83
86
|
The consumer audit permits only the reviewed advisories from this hotfix. It rejects each new advisory.
|
|
84
87
|
|
|
@@ -111,6 +111,7 @@ let TelemetryEvent = exports.TelemetryEvent = /*#__PURE__*/function (TelemetryEv
|
|
|
111
111
|
TelemetryEvent["MidConversationAuthSucceeded"] = "MidConversationAuthSucceeded";
|
|
112
112
|
TelemetryEvent["MidConversationAuthFailed"] = "MidConversationAuthFailed";
|
|
113
113
|
TelemetryEvent["MidConversationAuthReset"] = "MidConversationAuthReset";
|
|
114
|
+
TelemetryEvent["RecoveryEligibilityEvaluated"] = "RecoveryEligibilityEvaluated";
|
|
114
115
|
TelemetryEvent["GetConversationDetailsCallStarted"] = "GetConversationDetailsCallStarted";
|
|
115
116
|
TelemetryEvent["GetConversationDetailsCallFailed"] = "GetConversationDetailsCallFailed";
|
|
116
117
|
TelemetryEvent["EndChatSDKCallFailed"] = "EndChatSDKCallFailed";
|
|
@@ -142,6 +143,8 @@ let TelemetryEvent = exports.TelemetryEvent = /*#__PURE__*/function (TelemetryEv
|
|
|
142
143
|
TelemetryEvent["PrechatSubmitted"] = "PrechatSubmitted";
|
|
143
144
|
TelemetryEvent["StartChatSDKCall"] = "StartChatCall";
|
|
144
145
|
TelemetryEvent["StartChatEventReceived"] = "StartChatEventReceived";
|
|
146
|
+
TelemetryEvent["ConcurrentStartChatRejected"] = "ConcurrentStartChatRejected";
|
|
147
|
+
TelemetryEvent["ChatSDKStateCleanUpDisconnectFailed"] = "ChatSDKStateCleanUpDisconnectFailed";
|
|
145
148
|
TelemetryEvent["EndChatSDKCall"] = "EndChatSDKCall";
|
|
146
149
|
TelemetryEvent["PrepareEndChat"] = "PrepareEndChat";
|
|
147
150
|
TelemetryEvent["EndChatEventReceived"] = "EndChatEventReceived";
|
|
@@ -414,6 +417,9 @@ let TelemetryConstants = exports.TelemetryConstants = /*#__PURE__*/function () {
|
|
|
414
417
|
case TelemetryEvent.SecureEventBusDispatchError:
|
|
415
418
|
case TelemetryEvent.AgentJoinedConversation:
|
|
416
419
|
case TelemetryEvent.BrowserTabHidden:
|
|
420
|
+
case TelemetryEvent.ConcurrentStartChatRejected:
|
|
421
|
+
case TelemetryEvent.ChatSDKStateCleanUpDisconnectFailed:
|
|
422
|
+
case TelemetryEvent.RecoveryEligibilityEvaluated:
|
|
417
423
|
return ScenarioType.ACTIONS;
|
|
418
424
|
case TelemetryEvent.StartChatSDKCall:
|
|
419
425
|
case TelemetryEvent.StartChatEventReceived:
|
|
@@ -58,37 +58,54 @@ const ChatButtonStateful = props => {
|
|
|
58
58
|
const ref = (0, _react.useRef)(() => {
|
|
59
59
|
return;
|
|
60
60
|
});
|
|
61
|
+
// Optimistic guard against rapid double-clicks. `conversationState` only flips to
|
|
62
|
+
// `Loading` after React re-renders, so the button stays visible and clickable for the
|
|
63
|
+
// whole duration of the first click's async work. Without this ref two concurrent
|
|
64
|
+
// startChat() calls can create two conversations on the backend.
|
|
65
|
+
const startChatInProgressRef = (0, _react.useRef)(false);
|
|
61
66
|
ref.current = async () => {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
ConversationStage: _TelemetryConstants.ConversationStage.Initialization
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
if (state.appStates.isMinimized) {
|
|
70
|
-
dispatch({
|
|
71
|
-
type: _LiveChatWidgetActionType.LiveChatWidgetActionType.SET_MINIMIZED,
|
|
72
|
-
payload: false
|
|
67
|
+
if (startChatInProgressRef.current) {
|
|
68
|
+
_TelemetryHelper.TelemetryHelper.logActionEvent(_TelemetryConstants.LogLevel.WARN, {
|
|
69
|
+
Event: _TelemetryConstants.TelemetryEvent.ConcurrentStartChatRejected,
|
|
70
|
+
Description: "Chat button click ignored, a start chat operation is already in progress."
|
|
73
71
|
});
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
startChatInProgressRef.current = true;
|
|
75
|
+
try {
|
|
76
|
+
_TelemetryHelper.TelemetryHelper.logActionEventToAllTelemetry(_TelemetryConstants.LogLevel.INFO, {
|
|
77
|
+
Event: _TelemetryConstants.TelemetryEvent.LCWChatButtonClicked,
|
|
78
|
+
Description: "Chat button click action started",
|
|
79
|
+
CustomProperties: {
|
|
80
|
+
ConversationStage: _TelemetryConstants.ConversationStage.Initialization
|
|
81
|
+
}
|
|
77
82
|
});
|
|
78
|
-
|
|
79
|
-
|
|
83
|
+
if (state.appStates.isMinimized) {
|
|
84
|
+
dispatch({
|
|
85
|
+
type: _LiveChatWidgetActionType.LiveChatWidgetActionType.SET_MINIMIZED,
|
|
86
|
+
payload: false
|
|
87
|
+
});
|
|
88
|
+
dispatch({
|
|
89
|
+
type: _LiveChatWidgetActionType.LiveChatWidgetActionType.SET_UNREAD_MESSAGE_COUNT,
|
|
90
|
+
payload: 0
|
|
91
|
+
});
|
|
92
|
+
// If chat is minimized and then unminimized, start a chat if convesation state is closed.
|
|
93
|
+
if (state.appStates.conversationState === _ConversationState.ConversationState.Closed) {
|
|
94
|
+
await startChat();
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
80
97
|
await startChat();
|
|
81
98
|
}
|
|
82
|
-
|
|
83
|
-
|
|
99
|
+
_TelemetryHelper.TelemetryHelper.logActionEventToAllTelemetry(_TelemetryConstants.LogLevel.INFO, {
|
|
100
|
+
Event: _TelemetryConstants.TelemetryEvent.LCWChatButtonActionCompleted,
|
|
101
|
+
Description: "Chat button action completed",
|
|
102
|
+
CustomProperties: {
|
|
103
|
+
ConversationStage: _TelemetryConstants.ConversationStage.Initialization
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
} finally {
|
|
107
|
+
startChatInProgressRef.current = false;
|
|
84
108
|
}
|
|
85
|
-
_TelemetryHelper.TelemetryHelper.logActionEventToAllTelemetry(_TelemetryConstants.LogLevel.INFO, {
|
|
86
|
-
Event: _TelemetryConstants.TelemetryEvent.LCWChatButtonActionCompleted,
|
|
87
|
-
Description: "Chat button action completed",
|
|
88
|
-
CustomProperties: {
|
|
89
|
-
ConversationStage: _TelemetryConstants.ConversationStage.Initialization
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
109
|
};
|
|
93
110
|
const outOfOfficeStyleProps = Object.assign({}, _defaultOutOfOfficeChatButtonStyleProps.defaultOutOfOfficeChatButtonStyleProps, outOfOfficeButtonProps === null || outOfOfficeButtonProps === void 0 ? void 0 : outOfOfficeButtonProps.styleProps);
|
|
94
111
|
const controlProps = {
|
|
@@ -360,13 +360,52 @@ const closeChatStateCleanUp = dispatch => {
|
|
|
360
360
|
|
|
361
361
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
362
362
|
exports.closeChatStateCleanUp = closeChatStateCleanUp;
|
|
363
|
+
const logCleanUpDisconnectFailure = error => {
|
|
364
|
+
_TelemetryHelper.TelemetryHelper.logActionEvent(_TelemetryConstants.LogLevel.WARN, {
|
|
365
|
+
Event: _TelemetryConstants.TelemetryEvent.ChatSDKStateCleanUpDisconnectFailed,
|
|
366
|
+
Description: "Failed to disconnect the previous conversation during chat SDK state clean up.",
|
|
367
|
+
ExceptionDetails: {
|
|
368
|
+
exception: (error === null || error === void 0 ? void 0 : error.message) ?? error
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
// Releases the previous session's conversation. Mirrors the cleanup OmnichannelChatSDK.endChat()
|
|
374
|
+
// runs in its finally block (disconnect, then drop the reference). Without it, a conversation
|
|
375
|
+
// left over from a failed or orphaned start chat, or from a chat ended in another tab (which
|
|
376
|
+
// skips the SDK endChat), keeps its messaging session open and leaks into the next chat.
|
|
377
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
378
|
+
const disconnectConversation = chatSDK => {
|
|
379
|
+
const conversation = chatSDK.conversation;
|
|
380
|
+
if (!conversation || typeof conversation.disconnect !== "function") {
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
try {
|
|
384
|
+
// ACS conversations disconnect asynchronously; legacy IC3 conversations may not return a promise.
|
|
385
|
+
const result = conversation.disconnect();
|
|
386
|
+
if (result && typeof result.catch === "function") {
|
|
387
|
+
result.catch(logCleanUpDisconnectFailure);
|
|
388
|
+
}
|
|
389
|
+
} catch (error) {
|
|
390
|
+
logCleanUpDisconnectFailure(error);
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
363
395
|
const chatSDKStateCleanUp = chatSDK => {
|
|
396
|
+
if (!chatSDK) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
364
400
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
365
401
|
chatSDK.requestId = (0, _omnichannelChatSdk.uuidv4)();
|
|
366
402
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
367
403
|
chatSDK.chatToken = {};
|
|
368
404
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
369
405
|
chatSDK.reconnectId = null;
|
|
406
|
+
disconnectConversation(chatSDK);
|
|
407
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
408
|
+
chatSDK.conversation = null;
|
|
370
409
|
};
|
|
371
410
|
|
|
372
411
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -67,7 +67,7 @@ const initWebChatComposer = (props, state, dispatch, facadeChatSDK, endChat) =>
|
|
|
67
67
|
// Initialize Web Chat's redux store
|
|
68
68
|
let webChatStore = _WebChatStoreLoader.WebChatStoreLoader.store;
|
|
69
69
|
if (!webChatStore) {
|
|
70
|
-
var _state$domainStates$l, _state$domainStates$l2, _state$domainStates$l3, _props$webChatContain7;
|
|
70
|
+
var _state$domainStates$l, _state$domainStates$l2, _props$controlProps, _state$domainStates$l3, _props$webChatContain7;
|
|
71
71
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
72
72
|
const addConversationalSurveyTagsCallback = action => {
|
|
73
73
|
var _inMemoryState$appSta;
|
|
@@ -126,7 +126,7 @@ const initWebChatComposer = (props, state, dispatch, facadeChatSDK, endChat) =>
|
|
|
126
126
|
};
|
|
127
127
|
webChatStore = (0, _botframeworkWebchat.createStore)({},
|
|
128
128
|
//initial state
|
|
129
|
-
_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), _attachmentSentAnnouncementMiddleware.default, (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, (0, _citationsMiddleware.createCitationsMiddleware)(state, dispatch), _gifUploadMiddleware.default, _htmlPlayerMiddleware.default, (0, _htmlTextMiddleware.default)(honorsTargetInHTMLLinks), (0, _maxMessageSizeValidator.default)(localizedTexts), _sanitizationMiddleware.default, (0, _callActionMiddleware.default)(),
|
|
129
|
+
_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, _attachmentSentAnnouncementMiddleware.default)(localizedTexts, ((_props$controlProps = props.controlProps) === null || _props$controlProps === void 0 ? void 0 : _props$controlProps.id) || "oc-lcw"), (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, (0, _citationsMiddleware.createCitationsMiddleware)(state, dispatch), _gifUploadMiddleware.default, _htmlPlayerMiddleware.default, (0, _htmlTextMiddleware.default)(honorsTargetInHTMLLinks), (0, _maxMessageSizeValidator.default)(localizedTexts), _sanitizationMiddleware.default, (0, _callActionMiddleware.default)(),
|
|
130
130
|
// Pass a callback so middleware can push initials into React context for reactivity
|
|
131
131
|
(0, _localizedStringsBotInitialsMiddleware.localizedStringsBotInitialsMiddleware)(initials => {
|
|
132
132
|
dispatch({
|
|
@@ -256,6 +256,12 @@ const initWebChatComposer = (props, state, dispatch, facadeChatSDK, endChat) =>
|
|
|
256
256
|
// Track what would be removed
|
|
257
257
|
const removedTags = [];
|
|
258
258
|
const removedAttributes = [];
|
|
259
|
+
const trackAttribute = name => {
|
|
260
|
+
const attrName = name.toLowerCase();
|
|
261
|
+
if (!strictConfig.ALLOWED_ATTR.includes(attrName) && attrName !== "class" && attrName !== "id") {
|
|
262
|
+
removedAttributes.push(attrName);
|
|
263
|
+
}
|
|
264
|
+
};
|
|
259
265
|
|
|
260
266
|
// Add hooks to the isolated monitoring instance
|
|
261
267
|
monitorDOMPurify.addHook("uponSanitizeElement", (node, data) => {
|
|
@@ -264,6 +270,8 @@ const initWebChatComposer = (props, state, dispatch, facadeChatSDK, endChat) =>
|
|
|
264
270
|
// Filter out "body" tag which is DOMPurify's internal wrapper
|
|
265
271
|
if (node.nodeType === 1 && !strictConfig.ALLOWED_TAGS.includes(tagName) && tagName !== "body") {
|
|
266
272
|
removedTags.push(tagName);
|
|
273
|
+
// DOMPurify 3.4.16+ skips uponSanitizeAttribute for removed elements
|
|
274
|
+
Array.from(node.attributes ?? []).forEach(attr => trackAttribute(attr.name));
|
|
267
275
|
}
|
|
268
276
|
} catch (hookError) {
|
|
269
277
|
// Silently ignore hook errors
|
|
@@ -271,10 +279,7 @@ const initWebChatComposer = (props, state, dispatch, facadeChatSDK, endChat) =>
|
|
|
271
279
|
});
|
|
272
280
|
monitorDOMPurify.addHook("uponSanitizeAttribute", (node, data) => {
|
|
273
281
|
try {
|
|
274
|
-
|
|
275
|
-
if (!strictConfig.ALLOWED_ATTR.includes(attrName) && attrName !== "class" && attrName !== "id") {
|
|
276
|
-
removedAttributes.push(attrName);
|
|
277
|
-
}
|
|
282
|
+
trackAttribute(data.attrName);
|
|
278
283
|
} catch (hookError) {
|
|
279
284
|
// Silently ignore hook errors
|
|
280
285
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.logRecoveryEligibilityTelemetry = exports.getRecoveryEligibilityTelemetryProperties = exports.RecoveryEligibilityReason = void 0;
|
|
7
|
+
var _ConversationState = require("../../../contexts/common/ConversationState");
|
|
8
|
+
var _TelemetryConstants = require("../../../common/telemetry/TelemetryConstants");
|
|
9
|
+
var _TelemetryHelper = require("../../../common/telemetry/TelemetryHelper");
|
|
10
|
+
var _utils = require("../../../common/utils");
|
|
11
|
+
let RecoveryEligibilityReason = exports.RecoveryEligibilityReason = /*#__PURE__*/function (RecoveryEligibilityReason) {
|
|
12
|
+
RecoveryEligibilityReason["ReconnectIdPresent"] = "ReconnectIdPresent";
|
|
13
|
+
RecoveryEligibilityReason["ActiveContextPresent"] = "ActiveContextPresent";
|
|
14
|
+
RecoveryEligibilityReason["MissingContext"] = "MissingContext";
|
|
15
|
+
RecoveryEligibilityReason["CachedContextNonActive"] = "CachedContextNonActive";
|
|
16
|
+
return RecoveryEligibilityReason;
|
|
17
|
+
}({});
|
|
18
|
+
const getConversationStateName = conversationState => {
|
|
19
|
+
if (typeof conversationState !== "number") {
|
|
20
|
+
return "Unknown";
|
|
21
|
+
}
|
|
22
|
+
const stateName = _ConversationState.ConversationState[conversationState];
|
|
23
|
+
return typeof stateName === "string" ? stateName : "Unknown";
|
|
24
|
+
};
|
|
25
|
+
const getRecoveryEligibilityTelemetryProperties = (state, eligible) => {
|
|
26
|
+
var _state$appStates, _state$domainStates, _state$appStates2, _state$appStates3;
|
|
27
|
+
const reconnectId = (_state$appStates = state.appStates) === null || _state$appStates === void 0 ? void 0 : _state$appStates.reconnectId;
|
|
28
|
+
const liveChatContext = (_state$domainStates = state.domainStates) === null || _state$domainStates === void 0 ? void 0 : _state$domainStates.liveChatContext;
|
|
29
|
+
const hasReconnectId = !(0, _utils.isUndefinedOrEmpty)(reconnectId);
|
|
30
|
+
const hasLiveChatContext = !(0, _utils.isUndefinedOrEmpty)(liveChatContext);
|
|
31
|
+
const hasRequestId = !(0, _utils.isUndefinedOrEmpty)(liveChatContext === null || liveChatContext === void 0 ? void 0 : liveChatContext.requestId);
|
|
32
|
+
let reason = RecoveryEligibilityReason.ActiveContextPresent;
|
|
33
|
+
if (hasReconnectId) {
|
|
34
|
+
reason = RecoveryEligibilityReason.ReconnectIdPresent;
|
|
35
|
+
} else if (!hasLiveChatContext) {
|
|
36
|
+
reason = RecoveryEligibilityReason.MissingContext;
|
|
37
|
+
} else if (((_state$appStates2 = state.appStates) === null || _state$appStates2 === void 0 ? void 0 : _state$appStates2.conversationState) !== _ConversationState.ConversationState.Active) {
|
|
38
|
+
reason = RecoveryEligibilityReason.CachedContextNonActive;
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
conversationState: getConversationStateName((_state$appStates3 = state.appStates) === null || _state$appStates3 === void 0 ? void 0 : _state$appStates3.conversationState),
|
|
42
|
+
hasReconnectId,
|
|
43
|
+
hasLiveChatContext,
|
|
44
|
+
hasRequestId,
|
|
45
|
+
eligible,
|
|
46
|
+
reason
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
exports.getRecoveryEligibilityTelemetryProperties = getRecoveryEligibilityTelemetryProperties;
|
|
50
|
+
const logRecoveryEligibilityTelemetry = (state, eligible) => {
|
|
51
|
+
try {
|
|
52
|
+
_TelemetryHelper.TelemetryHelper.logActionEvent(_TelemetryConstants.LogLevel.INFO, {
|
|
53
|
+
Event: _TelemetryConstants.TelemetryEvent.RecoveryEligibilityEvaluated,
|
|
54
|
+
CustomProperties: getRecoveryEligibilityTelemetryProperties(state, eligible)
|
|
55
|
+
});
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.warn(error);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
exports.logRecoveryEligibilityTelemetry = logRecoveryEligibilityTelemetry;
|
|
@@ -24,18 +24,87 @@ var _authHelper = require("./authHelper");
|
|
|
24
24
|
var _setPostChatContextAndLoadSurvey = require("./setPostChatContextAndLoadSurvey");
|
|
25
25
|
var _persistentChatHelper = require("./persistentChatHelper");
|
|
26
26
|
var _updateSessionDataForTelemetry = require("./updateSessionDataForTelemetry");
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Per widget instance mutexes for the two chat creation entry points.
|
|
29
|
+
*
|
|
30
|
+
* Every guard in the prepareStartChat -> setPreChatAndInitiateChat -> initStartChat chain
|
|
31
|
+
* checks `conversationState === Closed`, but React state is updated asynchronously. A second
|
|
32
|
+
* invocation fired within the same render cycle (for example a rapid double click on the chat
|
|
33
|
+
* button, or a chat button click racing a BroadcastEvent.StartChat) therefore still observes
|
|
34
|
+
* the stale `Closed` state, reaches `facadeChatSDK.startChat()` and creates a second
|
|
35
|
+
* conversation on the backend which is then orphaned and never ended.
|
|
36
|
+
*
|
|
37
|
+
* The locks are keyed by FacadeChatSDK instance rather than being module wide, because a host
|
|
38
|
+
* page can mount several independent widgets, each with its own widgetInstanceId and its own
|
|
39
|
+
* FacadeChatSDK (created once per widget in LiveChatWidget.tsx). A module wide lock would make
|
|
40
|
+
* widget A's start chat silently drop widget B's legitimate simultaneous start chat.
|
|
41
|
+
*
|
|
42
|
+
* The two maps are separate because prepareStartChat calls initStartChat; sharing a single lock
|
|
43
|
+
* would make the nested call see its own parent as in flight and drop it. prepareStartChat is
|
|
44
|
+
* additionally blocked by an in flight initStartChat (pre-chat submit, reconnect, or the
|
|
45
|
+
* hideStartChatButton bootstrap call initStartChat directly). Otherwise a later prepareStartChat
|
|
46
|
+
* would dispatch Loading and maximize the widget, and only then have its nested initStartChat
|
|
47
|
+
* dropped.
|
|
48
|
+
*/
|
|
49
|
+
const prepareStartChatGuards = new WeakMap();
|
|
50
|
+
const initStartChatGuards = new WeakMap();
|
|
51
|
+
|
|
52
|
+
// Fallback owner used only when no FacadeChatSDK instance is available to key on, so the guard
|
|
53
|
+
// degrades to the module wide behaviour instead of disappearing altogether.
|
|
54
|
+
const unkeyedGuardOwner = {};
|
|
55
|
+
const getGuard = (guards, facadeChatSDK) => {
|
|
56
|
+
const owner = facadeChatSDK ?? unkeyedGuardOwner;
|
|
57
|
+
let guard = guards.get(owner);
|
|
58
|
+
if (!guard) {
|
|
59
|
+
guard = {
|
|
60
|
+
inFlight: false
|
|
61
|
+
};
|
|
62
|
+
guards.set(owner, guard);
|
|
63
|
+
}
|
|
64
|
+
return guard;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Runs `operation` only if no other invocation for the same widget instance is already in
|
|
69
|
+
* flight. Concurrent invocations are dropped (and reported through telemetry) rather than
|
|
70
|
+
* awaiting the in-flight promise, so a future re-entrant call can never deadlock on itself.
|
|
71
|
+
* No caller acts on the resolved value, so dropping is safe.
|
|
72
|
+
*
|
|
73
|
+
* `blockedBy` lets an operation also yield to a different, already running operation for the
|
|
74
|
+
* same widget instance without claiming that operation's lock.
|
|
75
|
+
*/
|
|
76
|
+
const runExclusively = async (guards, facadeChatSDK, description, operation, props, blockedBy) => {
|
|
77
|
+
const guard = getGuard(guards, facadeChatSDK);
|
|
78
|
+
const blockingGuard = blockedBy ? getGuard(blockedBy, facadeChatSDK) : undefined;
|
|
79
|
+
if (guard.inFlight || blockingGuard !== null && blockingGuard !== void 0 && blockingGuard.inFlight) {
|
|
80
|
+
_TelemetryHelper.TelemetryHelper.logActionEvent(_TelemetryConstants.LogLevel.WARN, {
|
|
81
|
+
Event: _TelemetryConstants.TelemetryEvent.ConcurrentStartChatRejected,
|
|
82
|
+
Description: description,
|
|
83
|
+
CustomProperties: {
|
|
84
|
+
WidgetCacheId: props ? (0, _utils.getWidgetCacheIdfromProps)(props) : undefined
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Claim the lock before invoking the operation, so a re-entrant call made from the
|
|
91
|
+
// operation's synchronous preamble (before its first await) cannot slip past the check above.
|
|
92
|
+
guard.inFlight = true;
|
|
93
|
+
try {
|
|
94
|
+
await operation();
|
|
95
|
+
} finally {
|
|
96
|
+
guard.inFlight = false;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
33
99
|
|
|
34
100
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
35
101
|
const prepareStartChat = async (props, facadeChatSDK, state, dispatch, setAdapter) => {
|
|
36
|
-
|
|
37
|
-
|
|
102
|
+
return runExclusively(prepareStartChatGuards, facadeChatSDK, "Concurrent prepareStartChat call ignored, a start chat operation is already in progress for this widget instance.", () => prepareStartChatInternal(props, facadeChatSDK, state, dispatch, setAdapter), props, initStartChatGuards);
|
|
103
|
+
};
|
|
38
104
|
|
|
105
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
106
|
+
exports.prepareStartChat = prepareStartChat;
|
|
107
|
+
const prepareStartChatInternal = async (props, facadeChatSDK, state, dispatch, setAdapter) => {
|
|
39
108
|
// reconnect > chat from cache
|
|
40
109
|
if ((0, _reconnectChatHelper.isReconnectEnabled)(props.chatConfig) === true && !(0, _reconnectChatHelper.isPersistentEnabled)(props.chatConfig)) {
|
|
41
110
|
const shouldStartChatNormally = await (0, _reconnectChatHelper.handleChatReconnect)(facadeChatSDK, props, dispatch, setAdapter, initStartChat, state);
|
|
@@ -63,7 +132,6 @@ const prepareStartChat = async (props, facadeChatSDK, state, dispatch, setAdapte
|
|
|
63
132
|
};
|
|
64
133
|
|
|
65
134
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
66
|
-
exports.prepareStartChat = prepareStartChat;
|
|
67
135
|
const setPreChatAndInitiateChat = async (facadeChatSDK, dispatch, setAdapter, isProactiveChat, proactiveChatEnablePrechatState, state, props) => {
|
|
68
136
|
var _props$preChatSurveyP, _props$controlProps, _state$domainStates, _state$appStates2;
|
|
69
137
|
// Reset before loading prechat to avoid starting chat with previous requestId
|
|
@@ -163,6 +231,12 @@ const setPreChatAndInitiateChat = async (facadeChatSDK, dispatch, setAdapter, is
|
|
|
163
231
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
164
232
|
exports.setPreChatAndInitiateChat = setPreChatAndInitiateChat;
|
|
165
233
|
const initStartChat = async (facadeChatSDK, dispatch, setAdapter, state, props, params, persistedState) => {
|
|
234
|
+
return runExclusively(initStartChatGuards, facadeChatSDK, "Concurrent initStartChat call ignored, a start chat operation is already in progress for this widget instance.", () => initStartChatInternal(facadeChatSDK, dispatch, setAdapter, state, props, params, persistedState), props);
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
238
|
+
exports.initStartChat = initStartChat;
|
|
239
|
+
const initStartChatInternal = async (facadeChatSDK, dispatch, setAdapter, state, props, params, persistedState) => {
|
|
166
240
|
var _state$domainStates7;
|
|
167
241
|
let isStartChatSuccessful = false;
|
|
168
242
|
const persistentChatEnabled = (0, _liveChatConfigUtils.isPersistentChatEnabled)(state === null || state === void 0 || (_state$domainStates7 = state.domainStates) === null || _state$domainStates7 === void 0 || (_state$domainStates7 = _state$domainStates7.liveChatConfig) === null || _state$domainStates7 === void 0 || (_state$domainStates7 = _state$domainStates7.LiveWSAndLiveChatEngJoin) === null || _state$domainStates7 === void 0 ? void 0 : _state$domainStates7.msdyn_conversationmode);
|
|
@@ -201,14 +275,15 @@ const initStartChat = async (facadeChatSDK, dispatch, setAdapter, state, props,
|
|
|
201
275
|
}
|
|
202
276
|
try {
|
|
203
277
|
var _window$Microsoft, _state$domainStates8;
|
|
204
|
-
// Set custom context params
|
|
205
|
-
|
|
278
|
+
// Set custom context params. Returned (not stored module-side) so that concurrent
|
|
279
|
+
// widget instances cannot overwrite each other's custom context.
|
|
280
|
+
const customContextParams = await setCustomContextParams(state, props);
|
|
206
281
|
const defaultOptionalParams = {
|
|
207
282
|
sendDefaultInitContext: true,
|
|
208
283
|
isProactiveChat: !!(params !== null && params !== void 0 && params.isProactiveChat),
|
|
209
284
|
portalContactId: (_window$Microsoft = window.Microsoft) === null || _window$Microsoft === void 0 || (_window$Microsoft = _window$Microsoft.Dynamic365) === null || _window$Microsoft === void 0 || (_window$Microsoft = _window$Microsoft.Portal) === null || _window$Microsoft === void 0 || (_window$Microsoft = _window$Microsoft.User) === null || _window$Microsoft === void 0 ? void 0 : _window$Microsoft.contactId
|
|
210
285
|
};
|
|
211
|
-
const startChatOptionalParams = Object.assign({}, params,
|
|
286
|
+
const startChatOptionalParams = Object.assign({}, params, customContextParams, defaultOptionalParams);
|
|
212
287
|
|
|
213
288
|
// MID-AUTH: Add wasAuthenticated flag for reconnect scenarios
|
|
214
289
|
// Tells FacadeChatSDK whether the previous session was authenticated
|
|
@@ -291,14 +366,10 @@ const initStartChat = async (facadeChatSDK, dispatch, setAdapter, state, props,
|
|
|
291
366
|
await (0, _updateSessionDataForTelemetry.updateTelemetryData)(facadeChatSDK, dispatch);
|
|
292
367
|
} catch (ex) {
|
|
293
368
|
(0, _startChatErrorHandler.handleStartChatError)(dispatch, facadeChatSDK, props, ex, isStartChatSuccessful);
|
|
294
|
-
} finally {
|
|
295
|
-
optionalParams = {};
|
|
296
|
-
widgetInstanceId = "";
|
|
297
369
|
}
|
|
298
370
|
};
|
|
299
371
|
|
|
300
372
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
301
|
-
exports.initStartChat = initStartChat;
|
|
302
373
|
const createAdapterAndSubscribe = async (facadeChatSDK, dispatch, setAdapter, startTime, props) => {
|
|
303
374
|
// New adapter creation
|
|
304
375
|
const newAdapter = await (0, _createAdapter.createAdapter)(facadeChatSDK, props);
|
|
@@ -338,19 +409,22 @@ const canConnectToExistingChat = async (props, facadeChatSDK, state, dispatch, s
|
|
|
338
409
|
return false;
|
|
339
410
|
};
|
|
340
411
|
|
|
341
|
-
|
|
412
|
+
/**
|
|
413
|
+
* Resolves the custom context to start the chat with.
|
|
414
|
+
*
|
|
415
|
+
* Returns the resolved params instead of mutating module-level state: a page can host several
|
|
416
|
+
* independent widgets, and once per-instance start-chat guards allow them to run concurrently,
|
|
417
|
+
* shared mutable state would let one widget overwrite another widget's custom context.
|
|
418
|
+
*/
|
|
342
419
|
const setCustomContextParams = async (state, props) => {
|
|
343
420
|
var _state$domainStates9, _persistedState$domai5;
|
|
344
421
|
if (state !== null && state !== void 0 && (_state$domainStates9 = state.domainStates) !== null && _state$domainStates9 !== void 0 && _state$domainStates9.customContext) {
|
|
345
422
|
var _state$domainStates0;
|
|
346
|
-
|
|
423
|
+
return {
|
|
347
424
|
customContext: JSON.parse(JSON.stringify(state === null || state === void 0 || (_state$domainStates0 = state.domainStates) === null || _state$domainStates0 === void 0 ? void 0 : _state$domainStates0.customContext))
|
|
348
|
-
}
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
if ((0, _utils.isNullOrEmptyString)(widgetInstanceId)) {
|
|
352
|
-
widgetInstanceId = (0, _utils.getWidgetCacheIdfromProps)(props);
|
|
425
|
+
};
|
|
353
426
|
}
|
|
427
|
+
const widgetInstanceId = props ? (0, _utils.getWidgetCacheIdfromProps)(props) : "";
|
|
354
428
|
// Add custom context only for unauthenticated chat
|
|
355
429
|
const persistedState = (0, _utils.getStateFromCache)(widgetInstanceId);
|
|
356
430
|
const customContextLocal = (persistedState === null || persistedState === void 0 || (_persistedState$domai5 = persistedState.domainStates) === null || _persistedState$domai5 === void 0 ? void 0 : _persistedState$domai5.customContext) ?? (props === null || props === void 0 ? void 0 : props.initialCustomContext);
|
|
@@ -359,23 +433,23 @@ const setCustomContextParams = async (state, props) => {
|
|
|
359
433
|
Event: _TelemetryConstants.TelemetryEvent.SettingCustomContext,
|
|
360
434
|
Description: "Setting custom context for unauthenticated chat"
|
|
361
435
|
});
|
|
362
|
-
|
|
436
|
+
return {
|
|
363
437
|
customContext: JSON.parse(JSON.stringify(customContextLocal))
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
}
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
const customContextFromParent = await getInitContextParamsForPopout();
|
|
441
|
+
if (!(0, _utils.isUndefinedOrEmpty)(customContextFromParent === null || customContextFromParent === void 0 ? void 0 : customContextFromParent.contextVariables)) {
|
|
442
|
+
return {
|
|
443
|
+
customContext: JSON.parse(JSON.stringify(customContextFromParent.contextVariables))
|
|
444
|
+
};
|
|
372
445
|
}
|
|
446
|
+
return {};
|
|
373
447
|
};
|
|
374
448
|
const canStartPopoutChat = async props => {
|
|
375
449
|
if (props.allowSdkChatSupport === false) {
|
|
376
450
|
return false;
|
|
377
451
|
}
|
|
378
|
-
popoutWidgetInstanceId = (0, _utils.getWidgetCacheIdfromProps)(props, true);
|
|
452
|
+
const popoutWidgetInstanceId = (0, _utils.getWidgetCacheIdfromProps)(props, true);
|
|
379
453
|
if (!(0, _utils.isNullOrEmptyString)(popoutWidgetInstanceId)) {
|
|
380
454
|
var _persistedState$domai6, _persistedState$appSt3;
|
|
381
455
|
const persistedState = (0, _utils.getStateFromCache)(popoutWidgetInstanceId);
|
|
@@ -60,6 +60,7 @@ var _useChatAdapterStore3 = _interopRequireDefault(require("../../../hooks/useCh
|
|
|
60
60
|
var _useChatContextStore3 = _interopRequireDefault(require("../../../hooks/useChatContextStore"));
|
|
61
61
|
var _useFacadeChatSDKStore = _interopRequireDefault(require("../../../hooks/useFacadeChatSDKStore"));
|
|
62
62
|
var _renderSurveyHelpers = require("../common/renderSurveyHelpers");
|
|
63
|
+
var _recoveryEligibilityTelemetry = require("../common/recoveryEligibilityTelemetry");
|
|
63
64
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
64
65
|
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
65
66
|
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
@@ -157,6 +158,7 @@ const LiveChatWidgetStateful = props => {
|
|
|
157
158
|
activeCachedChatExist = false;
|
|
158
159
|
optionalParams = {};
|
|
159
160
|
}
|
|
161
|
+
(0, _recoveryEligibilityTelemetry.logRecoveryEligibilityTelemetry)(state, activeCachedChatExist);
|
|
160
162
|
};
|
|
161
163
|
|
|
162
164
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -1172,18 +1174,18 @@ const LiveChatWidgetStateful = props => {
|
|
|
1172
1174
|
.webchat__auto-resize-textarea.webchat__send-box-text-box__text-area {
|
|
1173
1175
|
min-height: ${sendBoxTextArea === null || sendBoxTextArea === void 0 ? void 0 : sendBoxTextArea.minHeight} !important;
|
|
1174
1176
|
}`}
|
|
1175
|
-
`), /*#__PURE__*/_react2.default.createElement(_DraggableChatWidget.default, chatWidgetDraggableConfig, /*#__PURE__*/_react2.default.createElement(
|
|
1176
|
-
userID: userID,
|
|
1177
|
-
styleOptions: styleOptions,
|
|
1178
|
-
directLine: directLine
|
|
1179
|
-
}), /*#__PURE__*/_react2.default.createElement(_react.Stack, {
|
|
1177
|
+
`), /*#__PURE__*/_react2.default.createElement(_DraggableChatWidget.default, chatWidgetDraggableConfig, /*#__PURE__*/_react2.default.createElement(_react.Stack, {
|
|
1180
1178
|
id: widgetElementId,
|
|
1181
1179
|
styles: generalStyles,
|
|
1182
1180
|
className: (_livechatProps$styleP = livechatProps.styleProps) === null || _livechatProps$styleP === void 0 ? void 0 : _livechatProps$styleP.className,
|
|
1183
1181
|
role: isWidgetOpen ? "dialog" : undefined,
|
|
1184
1182
|
"aria-modal": isWidgetOpen ? true : undefined,
|
|
1185
1183
|
"aria-label": isWidgetOpen ? ((_props$headerProps = props.headerProps) === null || _props$headerProps === void 0 || (_props$headerProps = _props$headerProps.controlProps) === null || _props$headerProps === void 0 || (_props$headerProps = _props$headerProps.headerTitleProps) === null || _props$headerProps === void 0 ? void 0 : _props$headerProps.text) ?? "Live Chat" : undefined
|
|
1186
|
-
},
|
|
1184
|
+
}, /*#__PURE__*/_react2.default.createElement(Composer, _extends({}, webChatProps, {
|
|
1185
|
+
userID: userID,
|
|
1186
|
+
styleOptions: styleOptions,
|
|
1187
|
+
directLine: directLine
|
|
1188
|
+
}), !((_livechatProps$contro = livechatProps.controlProps) !== null && _livechatProps$contro !== void 0 && _livechatProps$contro.hideChatButton) && !((_livechatProps$contro2 = livechatProps.controlProps) !== null && _livechatProps$contro2 !== void 0 && _livechatProps$contro2.hideStartChatButton) && (0, _componentController.shouldShowChatButton)(state) && ((0, _omnichannelChatComponents.decodeComponentString)((_livechatProps$compon = livechatProps.componentOverrides) === null || _livechatProps$compon === void 0 ? void 0 : _livechatProps$compon.chatButton) || /*#__PURE__*/_react2.default.createElement(_ChatButtonStateful.default, {
|
|
1187
1189
|
buttonProps: livechatProps.chatButtonProps,
|
|
1188
1190
|
outOfOfficeButtonProps: livechatProps.outOfOfficeChatButtonProps,
|
|
1189
1191
|
startChat: prepareStartChatRelay
|
|
@@ -537,9 +537,9 @@ const WebChatContainerStateful = props => {
|
|
|
537
537
|
|
|
538
538
|
/* Suggested actions carousel previous/next navigation focus */
|
|
539
539
|
.webchat__suggested-actions .webchat__suggested-actions__carousel .react-film__flipper:focus-visible .react-film__flipper__body {
|
|
540
|
-
outline: ${(webChatContainerProps === null || webChatContainerProps === void 0 || (_webChatContainerProp20 = webChatContainerProps.webChatStyles) === null || _webChatContainerProp20 === void 0 ? void 0 : _webChatContainerProp20.suggestedActionKeyboardFocusIndicatorBorderStyle) ?? "
|
|
540
|
+
outline: ${(webChatContainerProps === null || webChatContainerProps === void 0 || (_webChatContainerProp20 = webChatContainerProps.webChatStyles) === null || _webChatContainerProp20 === void 0 ? void 0 : _webChatContainerProp20.suggestedActionKeyboardFocusIndicatorBorderStyle) ?? "solid"} ${(webChatContainerProps === null || webChatContainerProps === void 0 || (_webChatContainerProp21 = webChatContainerProps.webChatStyles) === null || _webChatContainerProp21 === void 0 ? void 0 : _webChatContainerProp21.suggestedActionKeyboardFocusIndicatorBorderWidth) ?? "2px"} ${(webChatContainerProps === null || webChatContainerProps === void 0 || (_webChatContainerProp22 = webChatContainerProps.webChatStyles) === null || _webChatContainerProp22 === void 0 ? void 0 : _webChatContainerProp22.suggestedActionKeyboardFocusIndicatorBorderColor) ?? "#605E5C"} !important;
|
|
541
541
|
outline-offset: ${(webChatContainerProps === null || webChatContainerProps === void 0 || (_webChatContainerProp23 = webChatContainerProps.webChatStyles) === null || _webChatContainerProp23 === void 0 ? void 0 : _webChatContainerProp23.suggestedActionKeyboardFocusIndicatorInset) ?? "2px"} !important;
|
|
542
|
-
|
|
542
|
+
}
|
|
543
543
|
`), /*#__PURE__*/_react2.default.createElement(_react.Stack, {
|
|
544
544
|
styles: containerStyles,
|
|
545
545
|
className: "webchat__stacked-layout_container"
|
|
@@ -33,7 +33,8 @@ const defaultMiddlewareLocalizedTexts = exports.defaultMiddlewareLocalizedTexts
|
|
|
33
33
|
MIDDLEWARE_BANNER_CHAT_DISCONNECT: "Your conversation has been disconnected. For additional assistance, please start a new chat.",
|
|
34
34
|
THIRD_PARTY_COOKIES_BLOCKED_ALERT_MESSAGE: "Allow sites to save/read cookies in browser settings. Reloading page starts a new chat.",
|
|
35
35
|
MIDDLEWARE_BANNER_FILE_IS_MALICIOUS: "{0} has been blocked because the file may contain a malware.",
|
|
36
|
-
MIDDLEWARE_BANNER_FILE_SENT: "File sent
|
|
36
|
+
MIDDLEWARE_BANNER_FILE_SENT: "File {2} sent",
|
|
37
|
+
MIDDLEWARE_BANNER_FILES_SENT: "{0} files sent",
|
|
37
38
|
MIDDLEWARE_BANNER_FILE_EMAIL_ADDRESS_RECORDED_SUCCESS: "Email will be sent after chat ends!",
|
|
38
39
|
MIDDLEWARE_BANNER_FILE_EMAIL_ADDRESS_RECORDED_ERROR: "Email {0} could not be saved, try again later.",
|
|
39
40
|
MIDDLEWARE_SR_PREFIX_SUCCESS: "Success. ",
|
|
@@ -94,6 +94,19 @@ const AdaptiveCardAccessibilityWrapper = _ref => {
|
|
|
94
94
|
}
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
+
// action-button-double-read: the adaptivecards renderer writes the action
|
|
98
|
+
// title to the button text, aria-label AND title. The title becomes the
|
|
99
|
+
// accessible description, so TalkBack reads the label twice ("Submit,
|
|
100
|
+
// button, Submit"). Drop the title only when it repeats the button name;
|
|
101
|
+
// a distinct title (an action tooltip) adds information and is kept.
|
|
102
|
+
container.querySelectorAll("button[title]").forEach(btn => {
|
|
103
|
+
const title = (btn.getAttribute("title") || "").trim();
|
|
104
|
+
const name = (btn.getAttribute("aria-label") ?? btn.textContent ?? "").trim();
|
|
105
|
+
if (title && title === name) {
|
|
106
|
+
btn.removeAttribute("title");
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
97
110
|
// Find all radio inputs inside the adaptive card
|
|
98
111
|
const radios = container.querySelectorAll("input[type='radio']");
|
|
99
112
|
if (radios.length === 0) return;
|
|
@@ -175,7 +188,8 @@ const AdaptiveCardAccessibilityWrapper = _ref => {
|
|
|
175
188
|
|
|
176
189
|
// Use MutationObserver to patch after WebChat finishes rendering the card.
|
|
177
190
|
// Also watch attribute changes on aria-hidden so we can re-apply if React
|
|
178
|
-
// reconciliation removes it from labels
|
|
191
|
+
// reconciliation removes it from labels, and on title so a re-added
|
|
192
|
+
// duplicate button title is removed again.
|
|
179
193
|
const observer = new MutationObserver(() => {
|
|
180
194
|
patchRadioGroups();
|
|
181
195
|
});
|
|
@@ -183,7 +197,7 @@ const AdaptiveCardAccessibilityWrapper = _ref => {
|
|
|
183
197
|
childList: true,
|
|
184
198
|
subtree: true,
|
|
185
199
|
attributes: true,
|
|
186
|
-
attributeFilter: ["aria-hidden"]
|
|
200
|
+
attributeFilter: ["aria-hidden", "title"]
|
|
187
201
|
});
|
|
188
202
|
|
|
189
203
|
// Also run immediately in case the card is already rendered
|