@qualified-salesforce/web 0.0.1-oidc-bootstrap → 0.1.1
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/CAPABILITY-REFERENCE.md +41 -0
- package/CHANGELOG.md +84 -0
- package/README.md +65 -0
- package/constants.generated.js +30 -2
- package/css/widget/multimodal_v2/index.split-css.panda.css +265 -47
- package/package.json +18 -4
- package/packs/widget/api/docking/viewport_lock.js +93 -2
- package/packs/widget/esm/index.d.ts +134 -0
- package/packs/widget/esm/index.js +18 -2
- package/packs/widget/esm/qualified.css +265 -47
- package/src/graphql-types.js +74 -1
- package/src/url.js +4 -1
- package/vendor/actioncable/connection.js +40 -3
- package/vendor/actioncable/connection_monitor.js +8 -0
- package/vendor/actioncable/consumer.js +7 -1
- package/vendor/actioncable/index.js +5 -2
- package/vendor/actioncable/sse_socket.js +262 -0
- package/widget/analytics.js +8 -0
- package/widget/api_handler/base_api_handler.js +24 -0
- package/widget/booker_outcome_tracker.js +58 -20
- package/widget/capabilities.js +190 -0
- package/widget/channel.js +38 -6
- package/widget/constants.generated.js +2 -1
- package/widget/multimodal_v2/components/AppLayout.js +1 -1
- package/widget/multimodal_v2/components/app/Foreground.js +10 -9
- package/widget/multimodal_v2/components/app/Input.js +8 -3
- package/widget/multimodal_v2/components/app/LightboxExperience.js +17 -3
- package/widget/multimodal_v2/components/app/Main.js +87 -31
- package/widget/multimodal_v2/components/app/MessengerButton.js +18 -1
- package/widget/multimodal_v2/components/chat/ChatInputForm.js +8 -6
- package/widget/multimodal_v2/components/chat/timeline/CalendarEventPickRenderer.js +2 -2
- package/widget/multimodal_v2/components/chat/timeline/PresentDeckRenderer.js +346 -170
- package/widget/multimodal_v2/components/chat/timeline/SystemRenderer.js +4 -7
- package/widget/multimodal_v2/components/decks/index.js +506 -52
- package/widget/multimodal_v2/components/helpers/content_click_payloads.js +67 -1
- package/widget/multimodal_v2/components/lightbox_gather/index.js +4 -4
- package/widget/multimodal_v2/components/meetings/MeetingBookerEntry.js +8 -1
- package/widget/multimodal_v2/components/meetings/MeetingOfferEmailStep.js +1 -1
- package/widget/multimodal_v2/components/meetings/MeetingSummaryRail.js +1 -1
- package/widget/multimodal_v2/components/meetings/MeetingTimeSelector.js +1 -1
- package/widget/multimodal_v2/components/multimodal/Controls.js +11 -3
- package/widget/multimodal_v2/components/multimodal/useMultimodal.js +19 -7
- package/widget/multimodal_v2/components/presentations/index.js +61 -0
- package/widget/multimodal_v2/constants.js +1 -1
- package/widget/multimodal_v2/images/icons.js +17 -1
- package/widget/multimodal_v2/index.panda.js +2 -2
- package/widget/multimodal_v2/locales/cs.js +8 -1
- package/widget/multimodal_v2/locales/da.js +8 -1
- package/widget/multimodal_v2/locales/de.js +8 -1
- package/widget/multimodal_v2/locales/en.js +8 -1
- package/widget/multimodal_v2/locales/en_AU.js +8 -1
- package/widget/multimodal_v2/locales/en_GB.js +8 -1
- package/widget/multimodal_v2/locales/es.js +8 -1
- package/widget/multimodal_v2/locales/es_LA.js +8 -1
- package/widget/multimodal_v2/locales/fi.js +8 -1
- package/widget/multimodal_v2/locales/fr.js +8 -1
- package/widget/multimodal_v2/locales/fr_CA.js +8 -1
- package/widget/multimodal_v2/locales/id.js +8 -1
- package/widget/multimodal_v2/locales/it.js +8 -1
- package/widget/multimodal_v2/locales/ja.js +8 -1
- package/widget/multimodal_v2/locales/ko.js +8 -1
- package/widget/multimodal_v2/locales/nb.js +8 -1
- package/widget/multimodal_v2/locales/nl.js +8 -1
- package/widget/multimodal_v2/locales/pl.js +8 -1
- package/widget/multimodal_v2/locales/pt_BR.js +8 -1
- package/widget/multimodal_v2/locales/ru.js +8 -1
- package/widget/multimodal_v2/locales/sv.js +8 -1
- package/widget/multimodal_v2/locales/th.js +8 -1
- package/widget/multimodal_v2/locales/tr.js +8 -1
- package/widget/multimodal_v2/locales/vi.js +8 -1
- package/widget/multimodal_v2/locales/zh_CN.js +8 -1
- package/widget/multimodal_v2/locales/zh_TW.js +8 -1
- package/widget/multimodal_v2/multimodal.js +5 -1
- package/widget/multimodal_v2/multimodal_channel.js +13 -8
- package/widget/multimodal_v2/signals/messages_signal.js +4 -0
- package/widget/multimodal_v2/stores/ai_conversation_store.js +6 -2
- package/widget/multimodal_v2/stores/artifact_store.js +352 -27
- package/widget/multimodal_v2/stores/call_store.js +40 -1
- package/widget/multimodal_v2/stores/call_ui_adapter.js +1 -1
- package/widget/multimodal_v2/stores/decks_store.js +405 -39
- package/widget/multimodal_v2/stores/meeting_store.js +15 -10
- package/widget/multimodal_v2/stores/presentation_ga_adapter.js +64 -0
- package/widget/multimodal_v2/stores/presentation_telemetry_store.js +556 -0
- package/widget/multimodal_v2/stores/presentations_store.js +1 -0
- package/widget/multimodal_v2/stores/root_store.js +157 -268
- package/widget/multimodal_v2/stores/screen_share_store.js +41 -21
- package/widget/multimodal_v2/stores/translation_store.js +9 -0
- package/widget/multimodal_v2/stores/ui_store.js +7 -7
- package/widget/multimodal_v2/stores/website_navigation_store.js +3 -1
- package/widget/multimodal_v2/text_turn_response_tracker.js +419 -0
- package/widget/multimodal_v2/types.js +21 -1
- package/widget/multimodal_v2/utils/locale.js +2 -1
- package/widget/multimodal_v2/utils/signals.js +35 -4
- package/widget/pardot_visitor.js +7 -1
- package/widget/server-logger.js +97 -2
- package/widget/tracker.js +69 -21
- package/widget/widget/page_mirror_factory.js +9 -1
- package/widget/widget/widget.js +29 -4
|
@@ -2,6 +2,7 @@ import { __webpack_require__ } from "../../js/runtime.js";
|
|
|
2
2
|
import { _class_private_field_loose_base, _class_private_field_loose_key } from "../../js/4101.js";
|
|
3
3
|
import { isIframed } from "../../src/iframes.js";
|
|
4
4
|
import { isIn } from "../../src/utils.js";
|
|
5
|
+
import { isCapabilityAllowed, commandCapability } from "../capabilities.js";
|
|
5
6
|
import { default as WidgetObject } from "../widget_object.js";
|
|
6
7
|
|
|
7
8
|
__webpack_require__("67353");
|
|
@@ -14,6 +15,7 @@ var isFunction_default = /*#__PURE__*/__webpack_require__.n(isFunction);
|
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
|
|
18
|
+
|
|
17
19
|
const API_VERBS = (/* unused pure expression or super */ null && ([
|
|
18
20
|
"initialize",
|
|
19
21
|
"open",
|
|
@@ -124,11 +126,29 @@ class BaseApiHandler extends WidgetObject {
|
|
|
124
126
|
this.queue = [];
|
|
125
127
|
}
|
|
126
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Capability boundary (QAI-5862): whether `fn` routes to a denied capability and so must be
|
|
131
|
+
* dropped before its handler runs or queues. Warns once so the drop is visible. No-op for the
|
|
132
|
+
* hosted widget (allow-all); `initialize` and neutral verbs map to no capability and are never
|
|
133
|
+
* gated. Shared (QAI-5896) so an `execute()` override — the iframe handler's early-verb fast
|
|
134
|
+
* path — enforces the same gate instead of side-stepping it.
|
|
135
|
+
*/ isCommandDenied(fn) {
|
|
136
|
+
const capability = commandCapability(fn);
|
|
137
|
+
if (capability && !isCapabilityAllowed(capability)) {
|
|
138
|
+
// eslint-disable-next-line no-console
|
|
139
|
+
console.warn("Qualified: '" + fn + "' was ignored — the '" + capability + "' capability was not allowed at setup().");
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
127
144
|
execute(fn) {
|
|
128
145
|
for(var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++){
|
|
129
146
|
args[_key - 1] = arguments[_key];
|
|
130
147
|
}
|
|
131
148
|
if (fn && this.handlers[fn]) {
|
|
149
|
+
// Capability boundary (QAI-5862): drop a command routed to a denied capability before it
|
|
150
|
+
// runs or queues. No-op for the hosted widget (allow-all); `initialize` is never gated.
|
|
151
|
+
if (this.isCommandDenied(fn)) return;
|
|
132
152
|
if (this.ready || fn === "initialize") {
|
|
133
153
|
// Should execute immediately?
|
|
134
154
|
this.handlers[fn].apply(undefined, args);
|
|
@@ -225,6 +245,10 @@ class BaseApiHandler extends WidgetObject {
|
|
|
225
245
|
this.handlers = handlers;
|
|
226
246
|
this.queue = [];
|
|
227
247
|
_class_private_field_loose_base(this, _initDeclarativeApi)[_initDeclarativeApi]();
|
|
248
|
+
// Host-page form capture (QAI-5864): this arms the smart-form listeners unconditionally, but
|
|
249
|
+
// egress is gated at the command layer — the submit handler routes through `execute()`, which
|
|
250
|
+
// drops `sendSmartFormData`/`saveSmartFormData` when the `formCapture` capability is denied. So
|
|
251
|
+
// there is no separate capability check here by design.
|
|
228
252
|
_class_private_field_loose_base(this, _initSmartForms)[_initSmartForms]();
|
|
229
253
|
}
|
|
230
254
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { __webpack_require__ } from "../js/runtime.js";
|
|
2
|
-
import "./utils/native_globals.js";
|
|
3
2
|
import { _extends } from "../js/4101.js";
|
|
3
|
+
import "./utils/native_globals.js";
|
|
4
4
|
|
|
5
5
|
/* provided dependency */ var booker_outcome_tracker_setTimeout = __webpack_require__(68034).pS;
|
|
6
6
|
/* provided dependency */ var __nativeWindowAddEventListener = __webpack_require__(68034).zh;
|
|
@@ -112,6 +112,44 @@ const emittedOfferIds = new Set();
|
|
|
112
112
|
/** Test-only: clears the cross-instance dedupe set between cases. */ function resetEmittedBookerOutcomes() {
|
|
113
113
|
emittedOfferIds.clear();
|
|
114
114
|
}
|
|
115
|
+
function emitBookerOutcome(param) {
|
|
116
|
+
let { log, surface, outcome, pathwayOrigin, conversationType, offerMessageId, timing } = param;
|
|
117
|
+
if (emittedOfferIds.has(offerMessageId)) return;
|
|
118
|
+
emittedOfferIds.add(offerMessageId);
|
|
119
|
+
log(_extends({
|
|
120
|
+
type: "log",
|
|
121
|
+
message: "booker_outcome",
|
|
122
|
+
extra: _extends({
|
|
123
|
+
meeting_stage: BOOKER_SHOWN_MEETING_STAGE,
|
|
124
|
+
outcome,
|
|
125
|
+
surface,
|
|
126
|
+
pathway_origin: pathwayOrigin,
|
|
127
|
+
conversation_type: conversationType,
|
|
128
|
+
offer_message_id: offerMessageId
|
|
129
|
+
}, timing === undefined ? {} : {
|
|
130
|
+
timing
|
|
131
|
+
})
|
|
132
|
+
}, outcome === BOOKER_OUTCOME.ABANDONED ? {
|
|
133
|
+
fireAndForget: true
|
|
134
|
+
} : {}));
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Reports an offer whose booker never loaded, for callers with no tracker to
|
|
138
|
+
* resolve — on the multimodal surface the tracker lives in the store inside the
|
|
139
|
+
* chunk that failed, so nothing is armed to time out.
|
|
140
|
+
*
|
|
141
|
+
* Shares the once-per-offer dedupe, which is first-write-wins: an offer the
|
|
142
|
+
* visitor later recovers by retrying still reads as `failed`. Sends no timing,
|
|
143
|
+
* having no baseline to measure from.
|
|
144
|
+
*/ function reportBookerLoadFailed(params) {
|
|
145
|
+
try {
|
|
146
|
+
emitBookerOutcome(_extends({}, params, {
|
|
147
|
+
outcome: BOOKER_OUTCOME.FAILED
|
|
148
|
+
}));
|
|
149
|
+
} catch (unused) {
|
|
150
|
+
// Same bargain as `safely()`: a lost datapoint beats breaking the booker.
|
|
151
|
+
}
|
|
152
|
+
}
|
|
115
153
|
class BookerOutcomeTracker {
|
|
116
154
|
arm(offerMessageId, pathwayOrigin, conversationType) {
|
|
117
155
|
this.safely(()=>{
|
|
@@ -144,8 +182,16 @@ class BookerOutcomeTracker {
|
|
|
144
182
|
}
|
|
145
183
|
});
|
|
146
184
|
}
|
|
147
|
-
|
|
148
|
-
|
|
185
|
+
/**
|
|
186
|
+
* @param offerMessageId The offer the caller rendered. A mark for any other is
|
|
187
|
+
* ignored: callers resolve after paint, so a newer offer can have re-armed
|
|
188
|
+
* in between, and crediting it would invent a `shown` and then suppress its
|
|
189
|
+
* real outcome. Omit when only one offer is in flight per tracker.
|
|
190
|
+
*/ markShown(offerMessageId) {
|
|
191
|
+
this.safely(()=>{
|
|
192
|
+
if (offerMessageId && offerMessageId !== this.offerMessageId) return;
|
|
193
|
+
this.resolve(BOOKER_OUTCOME.SHOWN);
|
|
194
|
+
});
|
|
149
195
|
}
|
|
150
196
|
dispose() {
|
|
151
197
|
this.safely(()=>{
|
|
@@ -182,22 +228,14 @@ class BookerOutcomeTracker {
|
|
|
182
228
|
}
|
|
183
229
|
emit(outcome) {
|
|
184
230
|
if (!this.offerMessageId) return;
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
surface: this.surface,
|
|
194
|
-
pathway_origin: this.pathwayOrigin,
|
|
195
|
-
conversation_type: this.conversationType,
|
|
196
|
-
offer_message_id: this.offerMessageId
|
|
197
|
-
}, this.timing(outcome))
|
|
198
|
-
}, outcome === BOOKER_OUTCOME.ABANDONED ? {
|
|
199
|
-
fireAndForget: true
|
|
200
|
-
} : {}));
|
|
231
|
+
emitBookerOutcome(_extends({
|
|
232
|
+
log: this.log,
|
|
233
|
+
surface: this.surface,
|
|
234
|
+
outcome,
|
|
235
|
+
pathwayOrigin: this.pathwayOrigin,
|
|
236
|
+
conversationType: this.conversationType,
|
|
237
|
+
offerMessageId: this.offerMessageId
|
|
238
|
+
}, this.timing(outcome)));
|
|
201
239
|
}
|
|
202
240
|
/**
|
|
203
241
|
* Elapsed time from arm, or nothing when it cannot be trusted.
|
|
@@ -326,4 +364,4 @@ var booker_outcome_tracker_BOOKER_LOAD_TIMEOUT_MS = /* unused export */ undefine
|
|
|
326
364
|
var booker_outcome_tracker_BOOKER_OUTCOME = /* unused export */ undefined;
|
|
327
365
|
var booker_outcome_tracker_BOOKER_SHOWN_MEETING_STAGE = /* unused export */ undefined;
|
|
328
366
|
var booker_outcome_tracker_resetEmittedBookerOutcomes = /* unused export */ undefined;
|
|
329
|
-
export { BOOKER_PATHWAY_ORIGIN, BOOKER_SURFACE, BookerOutcomeTracker, bookerPathwayOrigin, booker_outcome_tracker_BOOKER_LOAD_TIMEOUT_MS as BOOKER_LOAD_TIMEOUT_MS, booker_outcome_tracker_BOOKER_OUTCOME as BOOKER_OUTCOME, booker_outcome_tracker_BOOKER_SHOWN_MEETING_STAGE as BOOKER_SHOWN_MEETING_STAGE, booker_outcome_tracker_resetEmittedBookerOutcomes as resetEmittedBookerOutcomes };
|
|
367
|
+
export { BOOKER_PATHWAY_ORIGIN, BOOKER_SURFACE, BookerOutcomeTracker, bookerPathwayOrigin, booker_outcome_tracker_BOOKER_LOAD_TIMEOUT_MS as BOOKER_LOAD_TIMEOUT_MS, booker_outcome_tracker_BOOKER_OUTCOME as BOOKER_OUTCOME, booker_outcome_tracker_BOOKER_SHOWN_MEETING_STAGE as BOOKER_SHOWN_MEETING_STAGE, booker_outcome_tracker_resetEmittedBookerOutcomes as resetEmittedBookerOutcomes, reportBookerLoadFailed };
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { _extends } from "../js/4101.js";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Capability boundary for the self-hosted `@qualified/web` npm package (QAI-5862): a consumer-set,
|
|
6
|
+
* default-deny allowlist that Qualified cannot override. Dependency-free with no import-time side
|
|
7
|
+
* effects. Not enrolled (hosted CDN widget, never calls `setup()`) = allow-all; enrolled (npm,
|
|
8
|
+
* `setup()` installs a list) = default-deny and immutable.
|
|
9
|
+
*
|
|
10
|
+
* Enforced in three places: `setup()` installs it (`packs/widget/esm/index.ts`),
|
|
11
|
+
* `filterRuntimeConfig` narrows the fetched config, and the command/message dispatchers drop denied
|
|
12
|
+
* verbs/messages via `commandCapability` / `messageCapability`. This ticket is the mechanism only;
|
|
13
|
+
* per-feature client-side skips are QAI-5863 / QAI-5864, which also extend the tables below.
|
|
14
|
+
*/ /**
|
|
15
|
+
* The gated features. Default-deny: a key not in the allowlist is off. Voice is intentionally
|
|
16
|
+
* absent — this build ships text-only and can't activate voice; a voice build adds `"voice"` here.
|
|
17
|
+
*
|
|
18
|
+
* QAI-5864 deferred the voice/Daily gate for exactly this reason: in the text-only package the
|
|
19
|
+
* Daily call object is never created (a build-constant throw in `call_store.ts` fires first), so
|
|
20
|
+
* `c.daily.co` is unreachable and a `"voice"` capability would strip flags with no reachable
|
|
21
|
+
* effect. When the voice build lands, add `"voice"` here + a `CAPABILITY_CONFIG_ENABLERS.voice`
|
|
22
|
+
* entry (`featureFlags: ["piperXAgentVoice", "piperXAgentAvatar"]`) and the guard flagged in
|
|
23
|
+
* `call_store.ts` `getOrCreateCallObject`.
|
|
24
|
+
*/ const CAPABILITY_KEYS = [
|
|
25
|
+
"pardot",
|
|
26
|
+
"liveView",
|
|
27
|
+
"agentNavigation",
|
|
28
|
+
"analytics",
|
|
29
|
+
"slidePerception",
|
|
30
|
+
"formCapture",
|
|
31
|
+
"marketingIdentity"
|
|
32
|
+
];
|
|
33
|
+
const CAPABILITY_KEY_SET = new Set(CAPABILITY_KEYS);
|
|
34
|
+
/** Narrows an arbitrary string to a known `Capability`, so consumer typos grant nothing. */ function isKnownCapability(key) {
|
|
35
|
+
return CAPABILITY_KEY_SET.has(key);
|
|
36
|
+
}
|
|
37
|
+
// The installed allowlist, or `null` when not enrolled (allow-all). Module-private and only
|
|
38
|
+
// mutated by `installCapabilityBoundary` (once), which is what makes the boundary immutable.
|
|
39
|
+
let allowed = null;
|
|
40
|
+
function normalize(keys) {
|
|
41
|
+
const set = new Set();
|
|
42
|
+
for (const key of keys){
|
|
43
|
+
if (isKnownCapability(key)) {
|
|
44
|
+
set.add(key);
|
|
45
|
+
} else {
|
|
46
|
+
// Ignored (not thrown) so a typo can't brick boot — but warned so it's visible. The valid
|
|
47
|
+
// keys are named inline (QAI-5919) so the warning is self-correcting: the consumer sees what
|
|
48
|
+
// they should have passed without hunting for the reference.
|
|
49
|
+
// eslint-disable-next-line no-console
|
|
50
|
+
console.warn('Qualified: ignoring unknown capability "' + key + '" passed to setup(). ' + ("Valid capabilities: " + CAPABILITY_KEYS.join(", ") + "."));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return set;
|
|
54
|
+
}
|
|
55
|
+
function sameMembers(a, b) {
|
|
56
|
+
if (a.size !== b.size) return false;
|
|
57
|
+
for (const key of a){
|
|
58
|
+
if (!b.has(key)) return false;
|
|
59
|
+
}
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Installs the consumer's allowlist (once, from `setup()`). Unknown keys are ignored, not thrown.
|
|
64
|
+
* Re-installing the identical list is a no-op (retry-safe); a different list throws — so nothing
|
|
65
|
+
* can widen what the consumer allowed.
|
|
66
|
+
*/ function installCapabilityBoundary(capabilities) {
|
|
67
|
+
const requested = normalize(capabilities);
|
|
68
|
+
if (allowed !== null) {
|
|
69
|
+
if (!sameMembers(allowed, requested)) {
|
|
70
|
+
throw new Error("Qualified: the capability boundary is already installed and cannot be changed after " + "setup(). The allowlist is fixed for the lifetime of the page.");
|
|
71
|
+
}
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
allowed = requested;
|
|
75
|
+
}
|
|
76
|
+
/** The one check every enforcement point calls: default-deny once enrolled, allow-all when not. */ function isCapabilityAllowed(capability) {
|
|
77
|
+
if (allowed === null) return true;
|
|
78
|
+
return allowed.has(capability);
|
|
79
|
+
}
|
|
80
|
+
/** Whether a consumer allowlist has been installed (i.e. this is the enrolled npm package). */ function isCapabilityBoundaryInstalled() {
|
|
81
|
+
return allowed !== null;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Config flags/keys whose truthiness turns a capability ON. Denying a capability deletes these —
|
|
85
|
+
* deletion is the filter's only write. Seeded with today's known enablers; capabilities gated by a
|
|
86
|
+
* flag's *absence* (liveView) or by inbound messages only (agentNavigation) have none here and are
|
|
87
|
+
* enforced elsewhere. Feature tickets grow this table.
|
|
88
|
+
*/ const CAPABILITY_CONFIG_ENABLERS = {
|
|
89
|
+
analytics: {
|
|
90
|
+
featureFlags: [
|
|
91
|
+
"piperWebTrackingEvents"
|
|
92
|
+
],
|
|
93
|
+
configKeys: [
|
|
94
|
+
"analytics"
|
|
95
|
+
]
|
|
96
|
+
},
|
|
97
|
+
slidePerception: {
|
|
98
|
+
featureFlags: [
|
|
99
|
+
"piperPerceptionSlides"
|
|
100
|
+
]
|
|
101
|
+
},
|
|
102
|
+
pardot: {
|
|
103
|
+
configKeys: [
|
|
104
|
+
"pardotConfig"
|
|
105
|
+
]
|
|
106
|
+
},
|
|
107
|
+
formCapture: {
|
|
108
|
+
featureFlags: [
|
|
109
|
+
"smartForms",
|
|
110
|
+
"websiteEmbeddedForms"
|
|
111
|
+
],
|
|
112
|
+
configKeys: [
|
|
113
|
+
"hasSmartForms",
|
|
114
|
+
"hasWebsiteEmbeddedForms"
|
|
115
|
+
]
|
|
116
|
+
},
|
|
117
|
+
// QAI-5896: marketing-automation visitor identity (Marketo/HubSpot/Data360/Eloqua). Each `has*`
|
|
118
|
+
// flag makes `tracker.ts` `init()` read a local marketing cookie/global and forward the token to
|
|
119
|
+
// Qualified; stripping the flags is the primary skip, and `init()` carries a client-side backstop.
|
|
120
|
+
marketingIdentity: {
|
|
121
|
+
configKeys: [
|
|
122
|
+
"hasMarketo",
|
|
123
|
+
"hasHubSpot",
|
|
124
|
+
"hasData360",
|
|
125
|
+
"hasEloqua"
|
|
126
|
+
]
|
|
127
|
+
},
|
|
128
|
+
liveView: {},
|
|
129
|
+
agentNavigation: {}
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* Returns a shallow clone of `config` with every denied capability's enablers deleted. Only ever
|
|
133
|
+
* deletes, so it cannot turn a capability ON. Doesn't mutate the input; strips nothing when not
|
|
134
|
+
* enrolled.
|
|
135
|
+
*/ function filterRuntimeConfig(config) {
|
|
136
|
+
const cloned = _extends({}, config);
|
|
137
|
+
if (config.featureFlags) {
|
|
138
|
+
cloned.featureFlags = _extends({}, config.featureFlags);
|
|
139
|
+
}
|
|
140
|
+
for (const capability of CAPABILITY_KEYS){
|
|
141
|
+
if (!isCapabilityAllowed(capability)) {
|
|
142
|
+
const { featureFlags = [], configKeys = [] } = CAPABILITY_CONFIG_ENABLERS[capability];
|
|
143
|
+
for (const flag of featureFlags){
|
|
144
|
+
if (cloned.featureFlags) delete cloned.featureFlags[flag];
|
|
145
|
+
}
|
|
146
|
+
for (const key of configKeys){
|
|
147
|
+
delete cloned[key];
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return cloned;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* The capability a backend websocket message belongs to, or `undefined`. `handleIncomingMessage`
|
|
155
|
+
* drops denied ones upstream of the stores — catching the second `navigate_content` path too.
|
|
156
|
+
*/ const MESSAGE_CAPABILITIES = Object.freeze({
|
|
157
|
+
navigate_content: "agentNavigation",
|
|
158
|
+
point_cursor: "agentNavigation"
|
|
159
|
+
});
|
|
160
|
+
function messageCapability(messageType) {
|
|
161
|
+
return MESSAGE_CAPABILITIES[messageType];
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* The capability a public command (`q(verb, ...)`) belongs to, or `undefined`. `execute()` drops a
|
|
165
|
+
* denied verb before its handler runs; `initialize` and neutral verbs are never gated.
|
|
166
|
+
*/ const COMMAND_CAPABILITIES = Object.freeze({
|
|
167
|
+
initPardotForm: "pardot",
|
|
168
|
+
initPardotFormThankYou: "pardot",
|
|
169
|
+
parsePardotForm: "pardot",
|
|
170
|
+
parsePardotFormForLandingPage: "pardot",
|
|
171
|
+
pardotFormSubmitted: "pardot",
|
|
172
|
+
registerForm: "formCapture",
|
|
173
|
+
showForm: "formCapture",
|
|
174
|
+
showFormExperience: "formCapture",
|
|
175
|
+
showExperienceForForm: "formCapture",
|
|
176
|
+
saveFormData: "formCapture",
|
|
177
|
+
saveSmartFormData: "formCapture",
|
|
178
|
+
sendSmartFormData: "formCapture",
|
|
179
|
+
emitFormFill: "formCapture",
|
|
180
|
+
submitEmbeddedForm: "formCapture"
|
|
181
|
+
});
|
|
182
|
+
function commandCapability(verb) {
|
|
183
|
+
return COMMAND_CAPABILITIES[verb];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
var capabilities_COMMAND_CAPABILITIES = /* unused export */ undefined;
|
|
187
|
+
var capabilities_MESSAGE_CAPABILITIES = /* unused export */ undefined;
|
|
188
|
+
var capabilities_isCapabilityBoundaryInstalled = /* unused export */ undefined;
|
|
189
|
+
var capabilities_isKnownCapability = /* unused export */ undefined;
|
|
190
|
+
export { CAPABILITY_KEYS, capabilities_COMMAND_CAPABILITIES as COMMAND_CAPABILITIES, capabilities_MESSAGE_CAPABILITIES as MESSAGE_CAPABILITIES, capabilities_isCapabilityBoundaryInstalled as isCapabilityBoundaryInstalled, capabilities_isKnownCapability as isKnownCapability, commandCapability, filterRuntimeConfig, installCapabilityBoundary, isCapabilityAllowed, messageCapability };
|
package/widget/channel.js
CHANGED
|
@@ -58,7 +58,17 @@ class Channel extends WidgetObject {
|
|
|
58
58
|
// allow for missed pings (ANYCABLE_PING_INTERVAL=4s in manhattan, so 2x = 8s, not 6s).
|
|
59
59
|
connection_monitor.staleThreshold = (_this_config_staleThresholdSeconds = this.config.staleThresholdSeconds) != null ? _this_config_staleThresholdSeconds : 6;
|
|
60
60
|
const params = this.widget.connectionParams, url = this.config.wsHost + "?" + new URLSearchParams(omitBy_default()(params, (isNil_default()))).toString();
|
|
61
|
-
this._cable = createConsumer(url
|
|
61
|
+
this._cable = createConsumer(url, {
|
|
62
|
+
// Permits the cable to retry itself over SSE + HTTP if the WebSocket turns out to be blocked.
|
|
63
|
+
// Only the upstream half needs an address, because it goes to Rails rather than to
|
|
64
|
+
// anycable-go; the downstream stream is served by the same host as the socket. @see SseSocket
|
|
65
|
+
fallback: {
|
|
66
|
+
upstreamUrl: this.widget.resourceUrl("cable/command", {
|
|
67
|
+
uuid: this.widget.visitor.uuid,
|
|
68
|
+
wu: this.widget.widgetId
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
});
|
|
62
72
|
this.subscription = this._cable.subscriptions.create({
|
|
63
73
|
channel: "Visitors::WidgetChannel"
|
|
64
74
|
}, {
|
|
@@ -164,10 +174,18 @@ class Channel extends WidgetObject {
|
|
|
164
174
|
} else {
|
|
165
175
|
// @ts-expect-error TS does not realize you can concat a non-array to an array and it will be inserted
|
|
166
176
|
[].concat(message).forEach((message)=>{
|
|
167
|
-
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
|
|
177
|
+
// A closeLightbox that IframeManager's outstanding-request gate will
|
|
178
|
+
// withhold isn't the final answer for this journey -- resolving the
|
|
179
|
+
// pre-offer SLI / tracing spans for it would record "no offer" moments
|
|
180
|
+
// before the real offer arrives on the request that's still outstanding
|
|
181
|
+
// (CORE-3488). Skip both for that message; they still run on whichever
|
|
182
|
+
// teardown IS the final one.
|
|
183
|
+
if (message.event !== "closeLightbox" || !this.wouldWithholdTeardown(message.payload)) {
|
|
184
|
+
ButtonsFormsTriggeringTracer.finishFormOrButtonSpan(message);
|
|
185
|
+
// Same message, unsampled: resolves the pre-offer SLI for an armed
|
|
186
|
+
// form/CTA trigger. No-ops when nothing is armed.
|
|
187
|
+
resolveFormCtaBookerIntent(message);
|
|
188
|
+
}
|
|
171
189
|
if (message.event === "redirect" && message.payload.url) {
|
|
172
190
|
var _this_offerScript;
|
|
173
191
|
if (message.payload.isQualifiedForm && ((_this_offerScript = this.offerScript) == null ? void 0 : _this_offerScript.hasActiveForm)) {
|
|
@@ -177,7 +195,11 @@ class Channel extends WidgetObject {
|
|
|
177
195
|
window.location.href = message.payload.url;
|
|
178
196
|
}
|
|
179
197
|
} else if (message.event === "endLoadingState") {
|
|
180
|
-
|
|
198
|
+
// Forward the payload so a formTransactionId can gate a stray teardown
|
|
199
|
+
// against an outstanding request (CORE-3488). The other subscriber
|
|
200
|
+
// (multimodal.ts's onExperienceEnd) takes a zero-arg callback, so the
|
|
201
|
+
// extra argument is harmless there.
|
|
202
|
+
this._emitter.emit(message.event, message.payload);
|
|
181
203
|
} else if (message.event === "personalizeWebsite") {
|
|
182
204
|
this._emitter.emit(message.event, message.payload);
|
|
183
205
|
} else if (message.event === "initOffers") {
|
|
@@ -230,6 +252,16 @@ class Channel extends WidgetObject {
|
|
|
230
252
|
});
|
|
231
253
|
}
|
|
232
254
|
}
|
|
255
|
+
// Non-mutating: whether IframeManager's outstanding-request gate would
|
|
256
|
+
// withhold a closeLightbox carrying this payload (CORE-3488). False (never
|
|
257
|
+
// withhold) on v2, where IframeManager isn't registered.
|
|
258
|
+
wouldWithholdTeardown(payload) {
|
|
259
|
+
try {
|
|
260
|
+
return this.widget.iframeManager.wouldWithholdTeardown(payload.formTransactionId);
|
|
261
|
+
} catch (unused) {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
233
265
|
constructor(injectionProps){
|
|
234
266
|
super(injectionProps), // Private - Subscription events
|
|
235
267
|
this.onConnected = ()=>{
|
|
@@ -342,7 +342,8 @@ const SNIPPET_FEATURE_FLAGS = (/* unused pure expression or super */ null && ([
|
|
|
342
342
|
"widgetDebounceIframeResizes",
|
|
343
343
|
"widgetFullScreenWithScrollDisabler",
|
|
344
344
|
"widgetUseCodeSplitSnippet",
|
|
345
|
-
"widgetUseShadowDynamicImport"
|
|
345
|
+
"widgetUseShadowDynamicImport",
|
|
346
|
+
"widgetVisitorEventsBatching"
|
|
346
347
|
]));
|
|
347
348
|
|
|
348
349
|
var constants_generated_SNIPPET_FEATURE_FLAGS = /* unused export */ undefined;
|
|
@@ -50,7 +50,7 @@ const AppLayout = (param)=>{
|
|
|
50
50
|
when: rootStore.layout.root.visible,
|
|
51
51
|
children: /*#__PURE__*/ jsxRuntime_module_u(FocusTrapDialog, {
|
|
52
52
|
active: rootStore.layout.additionalState.isDialogActive,
|
|
53
|
-
onClose: rootStore.
|
|
53
|
+
onClose: rootStore.endPresentationAndClearMaximizedState,
|
|
54
54
|
ariaLabel: dialogLabel,
|
|
55
55
|
children: [
|
|
56
56
|
/*#__PURE__*/ jsxRuntime_module_u(Background, {
|
|
@@ -26,15 +26,16 @@ const Foreground = (param)=>{
|
|
|
26
26
|
})
|
|
27
27
|
}),
|
|
28
28
|
/*#__PURE__*/ jsxRuntime_module_u(utils_module_u, {
|
|
29
|
-
when: rootStore.displayMode.
|
|
30
|
-
children: /*#__PURE__*/ jsxRuntime_module_u(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
29
|
+
when: rootStore.displayMode.opened,
|
|
30
|
+
children: /*#__PURE__*/ jsxRuntime_module_u(preact_module_k, {
|
|
31
|
+
children: [
|
|
32
|
+
/*#__PURE__*/ jsxRuntime_module_u(PointerRectOverlay, {
|
|
33
|
+
rootStore: rootStore
|
|
34
|
+
}),
|
|
35
|
+
/*#__PURE__*/ jsxRuntime_module_u(CursorOverlay, {
|
|
36
|
+
rootStore: rootStore
|
|
37
|
+
})
|
|
38
|
+
]
|
|
38
39
|
})
|
|
39
40
|
}),
|
|
40
41
|
/*#__PURE__*/ jsxRuntime_module_u(utils_module_u, {
|
|
@@ -17,11 +17,16 @@ import { styled } from "../../styled-system/jsx/factory.js";
|
|
|
17
17
|
|
|
18
18
|
const Input = (param)=>{
|
|
19
19
|
let { rootStore } = param;
|
|
20
|
-
|
|
20
|
+
var _message_value, _message_value1;
|
|
21
|
+
const { state, isAnchored, message } = rootStore.input;
|
|
21
22
|
const heading = useLocalization(t("main.messageInputHeading"));
|
|
22
23
|
// When a picklist/multi_picklist field request is anchored in the timeline,
|
|
23
|
-
// keep the text input visible so the visitor can still type or speak.
|
|
24
|
-
|
|
24
|
+
// keep the text input visible so the visitor can still type or speak. Do the
|
|
25
|
+
// same after an engaged visitor collapses the messenger so stale picklist
|
|
26
|
+
// options do not replace the compact chat input.
|
|
27
|
+
const isPicklist = ((_message_value = message.value) == null ? void 0 : _message_value.fieldType) === "picklist" || ((_message_value1 = message.value) == null ? void 0 : _message_value1.fieldType) === "multi_picklist";
|
|
28
|
+
const showChatComposerForFieldRequest = state.value === types_InputState.FIELD_REQUEST && (isAnchored.value || isPicklist && rootStore.displayMode.collapsed.value && rootStore.messages.hasVisitorEngaged.value);
|
|
29
|
+
if (showChatComposerForFieldRequest) {
|
|
25
30
|
return /*#__PURE__*/ jsxRuntime_module_u(Container, {
|
|
26
31
|
"data-component-name": "Input.Container",
|
|
27
32
|
children: [
|
|
@@ -2,6 +2,8 @@ import { __webpack_require__ } from "../../../../js/runtime.js";
|
|
|
2
2
|
import { compat_module_P, compat_module_z, hooks_module_y, signals_core_module_d, useComputed, hooks_module_d, jsxRuntime_module_u, signals_core_module_E, hooks_module_A } from "../../../../js/multimodal_v2.js";
|
|
3
3
|
import { CrossIcon, ClockIcon, CheckCircleIcon, GlobeIcon } from "../../images/icons.js";
|
|
4
4
|
import "../../styled-system/css/index.js";
|
|
5
|
+
import { getSentryClient } from "../../../widget/sentry.js";
|
|
6
|
+
import { importWithRetry } from "../../utils/signals.js";
|
|
5
7
|
import { MeetingRepHeader } from "../meetings/MeetingRepHeader.js";
|
|
6
8
|
import { formatTimeShort, formatDate } from "../meetings/utils.js";
|
|
7
9
|
import { CalendarLoadingContent, loadingContentStyle } from "./CalendarLoadingContent.js";
|
|
@@ -22,6 +24,8 @@ import { css as css_css } from "../../styled-system/css/css.js";
|
|
|
22
24
|
|
|
23
25
|
|
|
24
26
|
|
|
27
|
+
|
|
28
|
+
|
|
25
29
|
const LightboxGather = compat_module_z(()=>import("../lightbox_gather/index.js"));
|
|
26
30
|
const LOCAL_TIMEZONE = Intl.DateTimeFormat().resolvedOptions().timeZone.replace(/_/g, " ");
|
|
27
31
|
function maskEmail(visitorEmail) {
|
|
@@ -167,9 +171,19 @@ const MeetingBooker = compat_module_z(()=>import("../meetings/index.js"));
|
|
|
167
171
|
// instantly on repeat calls once the chunk is cached.
|
|
168
172
|
hooks_module_y(()=>{
|
|
169
173
|
if (isLoadingPhase) {
|
|
170
|
-
rootStore.meetingStore.load()
|
|
171
|
-
|
|
174
|
+
rootStore.meetingStore.load().catch((error)=>{
|
|
175
|
+
var _getSentryClient;
|
|
176
|
+
(_getSentryClient = getSentryClient({
|
|
177
|
+
project: "multimodal"
|
|
178
|
+
})) == null ? void 0 : _getSentryClient.captureException(error);
|
|
179
|
+
});
|
|
180
|
+
void importWithRetry(()=>import("../meetings/index.js")).then(()=>{
|
|
172
181
|
meetingChunkReadySignal.value = true;
|
|
182
|
+
}).catch((error)=>{
|
|
183
|
+
var _getSentryClient;
|
|
184
|
+
(_getSentryClient = getSentryClient({
|
|
185
|
+
project: "multimodal"
|
|
186
|
+
})) == null ? void 0 : _getSentryClient.captureException(error);
|
|
173
187
|
});
|
|
174
188
|
}
|
|
175
189
|
// meetingChunkReadySignal is a stable ref (useRef), never changes identity.
|
|
@@ -298,7 +312,7 @@ const MeetingBooker = compat_module_z(()=>import("../meetings/index.js"));
|
|
|
298
312
|
email: (_ref2 = (_rootStore_meetingStore_value1 = rootStore.meetingStore.value) == null ? void 0 : _rootStore_meetingStore_value1.bookedEmail.value) != null ? _ref2 : null,
|
|
299
313
|
timeSlot: (_ref3 = (_rootStore_meetingStore_value2 = rootStore.meetingStore.value) == null ? void 0 : _rootStore_meetingStore_value2.bookedTimeSlot.value) != null ? _ref3 : null,
|
|
300
314
|
timeZone: (_ref4 = (_rootStore_meetingStore_value3 = rootStore.meetingStore.value) == null ? void 0 : _rootStore_meetingStore_value3.timeZone.value) != null ? _ref4 : LOCAL_TIMEZONE,
|
|
301
|
-
locale: rootStore.translationStore.
|
|
315
|
+
locale: rootStore.translationStore.effectiveIntlLocale.value,
|
|
302
316
|
title: (_ref5 = (_elementConversationTextMessage_value3 = elementConversationTextMessage.value) == null ? void 0 : _elementConversationTextMessage_value3.title) != null ? _ref5 : "Your meeting has been scheduled",
|
|
303
317
|
text: (_ref6 = (_elementConversationTextMessage_value4 = elementConversationTextMessage.value) == null ? void 0 : _elementConversationTextMessage_value4.text) != null ? _ref6 : null,
|
|
304
318
|
container: isContainer
|