@pendo/agent 2.342.0 → 2.343.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/dist/dom.esm.js +2 -25
- package/dist/pendo.module.js +472 -192
- package/dist/pendo.module.min.js +12 -12
- package/dist/servers.json +8 -8
- package/package.json +1 -1
package/dist/pendo.module.js
CHANGED
|
@@ -4058,8 +4058,8 @@ let SERVER = '';
|
|
|
4058
4058
|
let ASSET_HOST = '';
|
|
4059
4059
|
let ASSET_PATH = '';
|
|
4060
4060
|
let DESIGNER_SERVER = '';
|
|
4061
|
-
let VERSION = '2.
|
|
4062
|
-
let PACKAGE_VERSION = '2.
|
|
4061
|
+
let VERSION = '2.343.1_';
|
|
4062
|
+
let PACKAGE_VERSION = '2.343.1';
|
|
4063
4063
|
let LOADER = 'xhr';
|
|
4064
4064
|
/* eslint-enable web-sdk-eslint-rules/no-gulp-env-references */
|
|
4065
4065
|
/**
|
|
@@ -4476,15 +4476,35 @@ class LogEmitter extends EventTarget {
|
|
|
4476
4476
|
}
|
|
4477
4477
|
const log = new LogEmitter();
|
|
4478
4478
|
|
|
4479
|
-
/** Key used to lookup the consent collection policy in the pendo configuration. */
|
|
4480
4479
|
const CONSENT_COLLECTION = 'cookieConsentCollection';
|
|
4481
|
-
/** Key used to lookup the analytics events in the consent collection policy. */
|
|
4482
4480
|
const ANALYTICS = 'analytics';
|
|
4483
|
-
/** Key used to lookup the guide analytics events in the consent collection policy. */
|
|
4484
4481
|
const GUIDE_ANALYTICS = 'guideAnalytics';
|
|
4485
|
-
/** Key used to lookup the text collection mode in the consent collection policy. */
|
|
4486
4482
|
const TEXT_COLLECTION = 'textCollection';
|
|
4487
4483
|
const TEXT_COLLECTION_LEVELS = ['none', 'whitelist', 'all'];
|
|
4484
|
+
const ANALYTICS_EVENT_TYPES = {
|
|
4485
|
+
agentResponse: 'agent_response',
|
|
4486
|
+
clicks: 'click',
|
|
4487
|
+
fieldChanges: 'change',
|
|
4488
|
+
focus: 'focus',
|
|
4489
|
+
formSubmits: 'submit',
|
|
4490
|
+
identify: 'identify',
|
|
4491
|
+
meta: 'meta',
|
|
4492
|
+
pageLoads: 'load',
|
|
4493
|
+
prompt: 'prompt',
|
|
4494
|
+
trackEvents: 'track',
|
|
4495
|
+
userReaction: 'user_reaction'
|
|
4496
|
+
};
|
|
4497
|
+
const GUIDE_EVENT_TYPES = [
|
|
4498
|
+
'guideActivity',
|
|
4499
|
+
'guideAdvanced',
|
|
4500
|
+
'guideDismissed',
|
|
4501
|
+
'guideNotSeen',
|
|
4502
|
+
'guideSeen',
|
|
4503
|
+
'guideSnoozeCanceled',
|
|
4504
|
+
'guideSnoozed',
|
|
4505
|
+
'guideTimeout',
|
|
4506
|
+
'pollResponse'
|
|
4507
|
+
];
|
|
4488
4508
|
let enabledProducts = null;
|
|
4489
4509
|
function getClassification() {
|
|
4490
4510
|
return ConfigReader.get(CONSENT_COLLECTION) || {};
|
|
@@ -4504,30 +4524,59 @@ function warnOnUnconfiguredTiers(grantedTiers, classification) {
|
|
|
4504
4524
|
function moreTextPermissive(a, b) {
|
|
4505
4525
|
return TEXT_COLLECTION_LEVELS.indexOf(b) > TEXT_COLLECTION_LEVELS.indexOf(a) ? b : a;
|
|
4506
4526
|
}
|
|
4507
|
-
function
|
|
4508
|
-
return
|
|
4527
|
+
function toTextCollectionLevel(mode) {
|
|
4528
|
+
return _.contains(TEXT_COLLECTION_LEVELS, mode) ? mode : TEXT_COLLECTION_LEVELS[0];
|
|
4509
4529
|
}
|
|
4510
|
-
function
|
|
4511
|
-
return _.
|
|
4530
|
+
function toAnalyticsEventTypes(eventCollection) {
|
|
4531
|
+
return _.reduce(ANALYTICS_EVENT_TYPES, function (types, eventType, key) {
|
|
4532
|
+
if (eventCollection[key] === true) {
|
|
4533
|
+
types.push(eventType);
|
|
4534
|
+
}
|
|
4535
|
+
return types;
|
|
4536
|
+
}, []);
|
|
4537
|
+
}
|
|
4538
|
+
function normalizeTier(tier) {
|
|
4539
|
+
const { visitorIdentity = {}, analytics = {}, guides = {}, sessionReplay = {} } = tier || {};
|
|
4540
|
+
const eventCollection = analytics.eventCollection || {};
|
|
4541
|
+
const analyticsEnabled = analytics.enabled === true;
|
|
4542
|
+
return {
|
|
4543
|
+
identity: visitorIdentity.trackVisitors === true,
|
|
4544
|
+
guides: guides.enabled === true,
|
|
4545
|
+
replay: sessionReplay.enabled === true,
|
|
4546
|
+
analytics: analyticsEnabled ? toAnalyticsEventTypes(eventCollection) : [],
|
|
4547
|
+
guideAnalytics: analyticsEnabled && eventCollection.guideServing === true ? GUIDE_EVENT_TYPES.slice() : [],
|
|
4548
|
+
textCollection: analyticsEnabled ? toTextCollectionLevel(eventCollection.textCollectionMode) : TEXT_COLLECTION_LEVELS[0]
|
|
4549
|
+
};
|
|
4550
|
+
}
|
|
4551
|
+
function deniedProducts() {
|
|
4552
|
+
return {
|
|
4553
|
+
identity: false,
|
|
4554
|
+
guides: false,
|
|
4555
|
+
replay: false,
|
|
4556
|
+
[ANALYTICS]: [],
|
|
4557
|
+
[GUIDE_ANALYTICS]: [],
|
|
4558
|
+
[TEXT_COLLECTION]: TEXT_COLLECTION_LEVELS[0]
|
|
4559
|
+
};
|
|
4560
|
+
}
|
|
4561
|
+
function combineTiers(resolved, granted) {
|
|
4562
|
+
return {
|
|
4563
|
+
identity: resolved.identity || granted.identity,
|
|
4564
|
+
guides: resolved.guides || granted.guides,
|
|
4565
|
+
replay: resolved.replay || granted.replay,
|
|
4566
|
+
[ANALYTICS]: _.union(resolved[ANALYTICS], granted[ANALYTICS]),
|
|
4567
|
+
[GUIDE_ANALYTICS]: _.union(resolved[GUIDE_ANALYTICS], granted[GUIDE_ANALYTICS]),
|
|
4568
|
+
[TEXT_COLLECTION]: moreTextPermissive(resolved[TEXT_COLLECTION], granted[TEXT_COLLECTION])
|
|
4569
|
+
};
|
|
4512
4570
|
}
|
|
4513
|
-
// How a granted tier's value for a product combines with what's been resolved so far.
|
|
4514
|
-
// `initial` doubles as what an ungranted tier contributes -- an unlisted product falls
|
|
4515
|
-
// back to DEFAULT_RESOLVER, the plain "any granted tier turns it on" boolean rule.
|
|
4516
|
-
const RESOLVERS = {
|
|
4517
|
-
[TEXT_COLLECTION]: { initial: TEXT_COLLECTION_LEVELS[0], combine: moreTextPermissive },
|
|
4518
|
-
[ANALYTICS]: { initial: [], combine: unionArrays },
|
|
4519
|
-
[GUIDE_ANALYTICS]: { initial: [], combine: unionArrays }
|
|
4520
|
-
};
|
|
4521
|
-
const DEFAULT_RESOLVER = { initial: false, combine: orBoolean };
|
|
4522
4571
|
/**
|
|
4523
|
-
* Resolves the
|
|
4572
|
+
* Resolves the tiers a visitor consented to into what each product is allowed to do.
|
|
4573
|
+
*
|
|
4574
|
+
* Every product starts denied and only granted tiers contribute, so a tier the visitor
|
|
4575
|
+
* hasn't opted into has no say -- it can't deny anything, and it can't allow anything.
|
|
4524
4576
|
*
|
|
4525
|
-
* A
|
|
4526
|
-
*
|
|
4527
|
-
*
|
|
4528
|
-
* value is combined in, so opting into an additional tier only ever adds capability. Most
|
|
4529
|
-
* products are plain booleans (DEFAULT_RESOLVER); `textCollection`, `analytics`, and
|
|
4530
|
-
* `guideAnalytics` have their own entries in RESOLVERS since none of them is a boolean.
|
|
4577
|
+
* A subscription with no cookie consent configured resolves to an empty map instead, which
|
|
4578
|
+
* every read below treats as unconstrained: consent that was never set up can't deny
|
|
4579
|
+
* anything either.
|
|
4531
4580
|
*
|
|
4532
4581
|
* @access private
|
|
4533
4582
|
* @param {string | string[]} tiers tier or tiers the visitor consented to
|
|
@@ -4536,26 +4585,22 @@ const DEFAULT_RESOLVER = { initial: false, combine: orBoolean };
|
|
|
4536
4585
|
function applyConsentTiers(tiers) {
|
|
4537
4586
|
const cookieConsentCollection = getClassification();
|
|
4538
4587
|
const consentedCookieTiers = toTierList(tiers);
|
|
4539
|
-
const resolved = {};
|
|
4540
4588
|
warnOnUnconfiguredTiers(consentedCookieTiers, cookieConsentCollection);
|
|
4541
|
-
_.
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
});
|
|
4550
|
-
enabledProducts = resolved;
|
|
4589
|
+
if (_.isEmpty(cookieConsentCollection)) {
|
|
4590
|
+
enabledProducts = {};
|
|
4591
|
+
return getEnabledProducts();
|
|
4592
|
+
}
|
|
4593
|
+
enabledProducts = _.reduce(consentedCookieTiers, function (resolved, tier) {
|
|
4594
|
+
const granted = cookieConsentCollection[tier];
|
|
4595
|
+
return granted ? combineTiers(resolved, normalizeTier(granted)) : resolved;
|
|
4596
|
+
}, deniedProducts());
|
|
4551
4597
|
return getEnabledProducts();
|
|
4552
4598
|
}
|
|
4553
4599
|
function getEnabledProducts() {
|
|
4554
4600
|
return enabledProducts ? _.extend({}, enabledProducts) : null;
|
|
4555
4601
|
}
|
|
4556
4602
|
/**
|
|
4557
|
-
* A product is enabled
|
|
4558
|
-
* consent configured at all, or a product no tier ever mentions, there's nothing to deny.
|
|
4603
|
+
* A product is enabled only if some granted tier turned it on or cookie consent has not been set.
|
|
4559
4604
|
*
|
|
4560
4605
|
* @access private
|
|
4561
4606
|
* @param {string} product
|
|
@@ -4569,6 +4614,9 @@ function isProductEnabled(product) {
|
|
|
4569
4614
|
function isTrackingConsentDenied() {
|
|
4570
4615
|
return !isProductEnabled('identity');
|
|
4571
4616
|
}
|
|
4617
|
+
function isGuideConsentDenied() {
|
|
4618
|
+
return !isProductEnabled('guides');
|
|
4619
|
+
}
|
|
4572
4620
|
/**
|
|
4573
4621
|
* Session replay is denied whenever tracking itself is denied (DNT) -- a visitor
|
|
4574
4622
|
* who can't be identified shouldn't have their session recorded either -- or when a
|
|
@@ -4604,6 +4652,9 @@ function hasConsentForAnalyticsEvent(type) {
|
|
|
4604
4652
|
function hasConsentForGuideEvent(type) {
|
|
4605
4653
|
return hasConsentForProduct(type, GUIDE_ANALYTICS);
|
|
4606
4654
|
}
|
|
4655
|
+
function resetConsentPolicy() {
|
|
4656
|
+
enabledProducts = null;
|
|
4657
|
+
}
|
|
4607
4658
|
|
|
4608
4659
|
const allowKeys = function () {
|
|
4609
4660
|
var allowKeys = ConfigReader.get('storage.allowKeys') || '*';
|
|
@@ -22161,14 +22212,29 @@ function ClickActivation() {
|
|
|
22161
22212
|
}
|
|
22162
22213
|
stopEvent(evt);
|
|
22163
22214
|
};
|
|
22215
|
+
// Safari does not focus the badge on mousedown, so focus leaves the app instead.
|
|
22216
|
+
// Apps that re-render on blur can remove the badge before mouseup, and the browser
|
|
22217
|
+
// then generates no click at all. Holding focus where it is avoids that (APP-168412)
|
|
22218
|
+
var preserveFocus = function (evt) {
|
|
22219
|
+
if (evt.preventDefault) {
|
|
22220
|
+
evt.preventDefault();
|
|
22221
|
+
}
|
|
22222
|
+
else {
|
|
22223
|
+
evt.returnValue = false;
|
|
22224
|
+
}
|
|
22225
|
+
};
|
|
22164
22226
|
badge.after('show', function () {
|
|
22165
22227
|
if (!attached) {
|
|
22166
22228
|
attachEvent(badgeElem, 'click', click);
|
|
22229
|
+
if (badge.preserveFocusOnClick) {
|
|
22230
|
+
attachEvent(badgeElem, 'mousedown', preserveFocus);
|
|
22231
|
+
}
|
|
22167
22232
|
attached = true;
|
|
22168
22233
|
}
|
|
22169
22234
|
});
|
|
22170
22235
|
badge.after('hide', function () {
|
|
22171
22236
|
detachEvent(badgeElem, 'click', click);
|
|
22237
|
+
detachEvent(badgeElem, 'mousedown', preserveFocus);
|
|
22172
22238
|
attached = false;
|
|
22173
22239
|
});
|
|
22174
22240
|
return badge;
|
|
@@ -22639,7 +22705,7 @@ function stepShowingProc(guide, step) {
|
|
|
22639
22705
|
return;
|
|
22640
22706
|
}
|
|
22641
22707
|
const embedSelector = _.get(guide, 'attributes.embedConfig.selector');
|
|
22642
|
-
if (embedSelector && !dom(embedSelector).length) {
|
|
22708
|
+
if (embedSelector && (!dom(embedSelector).length || (guideElement && !isInDocument(guideElement[0])))) {
|
|
22643
22709
|
step.hide();
|
|
22644
22710
|
return;
|
|
22645
22711
|
}
|
|
@@ -25383,6 +25449,9 @@ var initGuides = function (observer) {
|
|
|
25383
25449
|
return;
|
|
25384
25450
|
guides ? enableGuides() : disableGuides();
|
|
25385
25451
|
});
|
|
25452
|
+
if (isGuideConsentDenied()) {
|
|
25453
|
+
disableGuides();
|
|
25454
|
+
}
|
|
25386
25455
|
teardownFns.push(onTurbolinksPageLoad(document, function onPageLoad() {
|
|
25387
25456
|
if (pendoDotUrl.get() === reloadGuides.lastUrl) {
|
|
25388
25457
|
// Force a reload if Turbolinks replaces the document body without changing the url
|
|
@@ -26044,10 +26113,14 @@ function isPortValid(port) {
|
|
|
26044
26113
|
// (APP-168708). frames and its indexed children are readable across origins, so
|
|
26045
26114
|
// this holds where an origin check can't: no ancestorOrigins needed, and sandboxed
|
|
26046
26115
|
// frames (origin "null") are still found.
|
|
26116
|
+
//
|
|
26117
|
+
// frames skips iframes whose element lives in a shadow root, so a web-component
|
|
26118
|
+
// modal's frame never turns up in the walk. top is also readable across origins
|
|
26119
|
+
// and can't be spoofed by a popup, so it covers those frames (APP-171528).
|
|
26047
26120
|
function isFrameBelowUs(source, window) {
|
|
26048
26121
|
if (!source)
|
|
26049
26122
|
return false;
|
|
26050
|
-
if (source === window)
|
|
26123
|
+
if (source === window || source.top === window)
|
|
26051
26124
|
return true;
|
|
26052
26125
|
var pending = [window];
|
|
26053
26126
|
while (pending.length) {
|
|
@@ -29325,6 +29398,7 @@ const PluginAPI = {
|
|
|
29325
29398
|
q,
|
|
29326
29399
|
SendQueue,
|
|
29327
29400
|
sessionStorage: agentStorage.getSession(),
|
|
29401
|
+
storageIsDisabled,
|
|
29328
29402
|
sizeElements,
|
|
29329
29403
|
store,
|
|
29330
29404
|
Tombstone,
|
|
@@ -29503,6 +29577,34 @@ function initIdentityEvents() {
|
|
|
29503
29577
|
};
|
|
29504
29578
|
}
|
|
29505
29579
|
|
|
29580
|
+
/**
|
|
29581
|
+
* Enforces the cookie consent policy a visitor has agreed to. The policy is defined by the subscription's cookie consent configuration,
|
|
29582
|
+
* and is used to determine whether each product the web SDK may run.
|
|
29583
|
+
*
|
|
29584
|
+
* @access public
|
|
29585
|
+
* @category Core
|
|
29586
|
+
* @param {string | string[]} tiers tier or tiers the visitor consented to
|
|
29587
|
+
* @returns {object} map of product to its resolved value -- `guides` and `identity` are
|
|
29588
|
+
* booleans (identity disabled means DNT), `textCollection` is `none`/`whitelist`/`all`,
|
|
29589
|
+
* `analytics` and `guideAnalytics` are each an array of allowed event types
|
|
29590
|
+
* @example
|
|
29591
|
+
* pendo.updateCookieConsent('strictlyNecessary');
|
|
29592
|
+
* @example
|
|
29593
|
+
* pendo.updateCookieConsent(['strictlyNecessary', 'functional']);
|
|
29594
|
+
*/
|
|
29595
|
+
function updateCookieConsent(tiers) {
|
|
29596
|
+
const enabledProducts = applyConsentTiers(tiers);
|
|
29597
|
+
Events['consent:changed'].trigger(enabledProducts);
|
|
29598
|
+
log.debug('Cookie consent updated for ' + JSON.stringify(tiers) + '.');
|
|
29599
|
+
return enabledProducts;
|
|
29600
|
+
}
|
|
29601
|
+
function initializeCookieConsent(tiers) {
|
|
29602
|
+
resetConsentPolicy();
|
|
29603
|
+
if (tiers) {
|
|
29604
|
+
updateCookieConsent(tiers);
|
|
29605
|
+
}
|
|
29606
|
+
}
|
|
29607
|
+
|
|
29506
29608
|
/*
|
|
29507
29609
|
* ResourceCenterActivity Event
|
|
29508
29610
|
*
|
|
@@ -29986,6 +30088,8 @@ function initialize(options) {
|
|
|
29986
30088
|
}
|
|
29987
30089
|
// Save the options somewhere
|
|
29988
30090
|
ConfigReader.setLocalConfig(options);
|
|
30091
|
+
const cookieConsentTiers = _.get(options, 'cookieConsentTiers');
|
|
30092
|
+
initializeCookieConsent(cookieConsentTiers);
|
|
29989
30093
|
initDataHost();
|
|
29990
30094
|
exportDataHost(pendo$1);
|
|
29991
30095
|
const cookieDomain = ConfigReader.get('cookieDomain');
|
|
@@ -31232,28 +31336,6 @@ logPublic.setActiveContexts = setActiveContexts;
|
|
|
31232
31336
|
logPublic.showLogHistory = showLogHistory;
|
|
31233
31337
|
logPublic.getLoggedContexts = getLoggedContexts;
|
|
31234
31338
|
|
|
31235
|
-
/**
|
|
31236
|
-
* Enforces the cookie consent policy a visitor has agreed to. The policy is defined by the subscription's cookie consent configuration,
|
|
31237
|
-
* and is used to determine whether each product the web SDK may run.
|
|
31238
|
-
*
|
|
31239
|
-
* @access public
|
|
31240
|
-
* @category Core
|
|
31241
|
-
* @param {string | string[]} tiers tier or tiers the visitor consented to
|
|
31242
|
-
* @returns {object} map of product to its resolved value -- `guides` and `identity` are
|
|
31243
|
-
* booleans (identity disabled means DNT), `textCollection` is `none`/`whitelist`/`all`,
|
|
31244
|
-
* `analytics` and `guideAnalytics` are each an array of allowed event types
|
|
31245
|
-
* @example
|
|
31246
|
-
* pendo.updateCookieConsent('strictlyNecessary');
|
|
31247
|
-
* @example
|
|
31248
|
-
* pendo.updateCookieConsent(['strictlyNecessary', 'functional']);
|
|
31249
|
-
*/
|
|
31250
|
-
function updateCookieConsent(tiers) {
|
|
31251
|
-
const enabledProducts = applyConsentTiers(tiers);
|
|
31252
|
-
Events['consent:changed'].trigger(enabledProducts);
|
|
31253
|
-
log.debug('Cookie consent updated for ' + JSON.stringify(tiers) + '.');
|
|
31254
|
-
return enabledProducts;
|
|
31255
|
-
}
|
|
31256
|
-
|
|
31257
31339
|
/**
|
|
31258
31340
|
* Shared helper for event collection
|
|
31259
31341
|
*/
|
|
@@ -42735,6 +42817,310 @@ var guideMarkdownUtil = {
|
|
|
42735
42817
|
removeMarkdownSyntax
|
|
42736
42818
|
};
|
|
42737
42819
|
|
|
42820
|
+
const AUTO_ADVANCE_DELAY_MS = 250;
|
|
42821
|
+
function isAutoAdvanceRequested(step, _) {
|
|
42822
|
+
return _.get(step, 'attributes.autoAdvance') === true;
|
|
42823
|
+
}
|
|
42824
|
+
// Survey-authored DOM carries no question-type classes, so eligibility keys off
|
|
42825
|
+
// answer-control shape: every required poll id must be radio-backed, which also
|
|
42826
|
+
// refuses a future non-radio type by default.
|
|
42827
|
+
function isStepEligibleInDom(step, _, requiredPollIds) {
|
|
42828
|
+
const guideElement = step && step.guideElement;
|
|
42829
|
+
if (!guideElement)
|
|
42830
|
+
return false;
|
|
42831
|
+
if (guideElement.find('textarea, input:text, input:checkbox, select').length)
|
|
42832
|
+
return false;
|
|
42833
|
+
const pollIds = _.uniq(_.map(guideElement.find('[data-pendo-poll-id]'), (element) => element.getAttribute('data-pendo-poll-id')));
|
|
42834
|
+
if (!pollIds.length)
|
|
42835
|
+
return false;
|
|
42836
|
+
return _.all(pollIds, (pollId) => _.contains(requiredPollIds, pollId) &&
|
|
42837
|
+
guideElement.find(`input:radio[data-pendo-poll-id="${pollId}"]`).length > 0);
|
|
42838
|
+
}
|
|
42839
|
+
function hideSubmitButtons(step, _, submitButtons) {
|
|
42840
|
+
const hiddenButtons = [];
|
|
42841
|
+
_.each(submitButtons, (button) => {
|
|
42842
|
+
const element = step.guideElement.find(`#${button.props.id}`)[0];
|
|
42843
|
+
if (!element)
|
|
42844
|
+
return;
|
|
42845
|
+
// display, not visibility: a reserved-but-invisible box leaves an empty gap
|
|
42846
|
+
// at the foot of the step. Authored styles reach the button inline
|
|
42847
|
+
// (build.js:151), so the original value is kept rather than blanked.
|
|
42848
|
+
hiddenButtons.push({ element, display: element.style.display });
|
|
42849
|
+
element.style.display = 'none';
|
|
42850
|
+
});
|
|
42851
|
+
return function restoreSubmitButtons() {
|
|
42852
|
+
_.each(hiddenButtons, (hiddenButton) => {
|
|
42853
|
+
hiddenButton.element.style.display = hiddenButton.display;
|
|
42854
|
+
});
|
|
42855
|
+
};
|
|
42856
|
+
}
|
|
42857
|
+
// The container's pixel height is measured during render (layout.js) before custom
|
|
42858
|
+
// scripts run, so collapsing a row afterwards leaves the box stale and the step keeps
|
|
42859
|
+
// the empty space. Same reflow pair metadata-substitution uses, scoped to this step so
|
|
42860
|
+
// a second guide on screen is left alone.
|
|
42861
|
+
function reflowGuide(step, _, pendo) {
|
|
42862
|
+
const recalculateGuideHeight = _.get(pendo, 'BuildingBlocks.BuildingBlockGuides.recalculateGuideHeight');
|
|
42863
|
+
try {
|
|
42864
|
+
if (_.isFunction(pendo.flexElement)) {
|
|
42865
|
+
pendo.flexElement(step.guideElement.find(guideMarkdownUtil.containerSelector));
|
|
42866
|
+
}
|
|
42867
|
+
if (_.isFunction(recalculateGuideHeight)) {
|
|
42868
|
+
recalculateGuideHeight(`pendo-g-${step.id}`, step.guideElement);
|
|
42869
|
+
}
|
|
42870
|
+
}
|
|
42871
|
+
catch (error) {
|
|
42872
|
+
pendo.log('[auto-advance] guide reflow failed, the step may keep an empty row', error && error.message);
|
|
42873
|
+
}
|
|
42874
|
+
}
|
|
42875
|
+
function createInertController() {
|
|
42876
|
+
return { isActive: false, notifyAnswered() { }, cancel() { } };
|
|
42877
|
+
}
|
|
42878
|
+
function createAutoAdvanceController(step, pendo, submitButtons, requiredPollIds) {
|
|
42879
|
+
const _ = pendo._;
|
|
42880
|
+
const inert = createInertController();
|
|
42881
|
+
if (!isAutoAdvanceRequested(step, _))
|
|
42882
|
+
return inert;
|
|
42883
|
+
// Only reachable for API-authored guides; choosing between two
|
|
42884
|
+
// submitPollAndGoToStep targets would be a guess.
|
|
42885
|
+
if (_.size(submitButtons) !== 1) {
|
|
42886
|
+
pendo.log(`[auto-advance] not arming: step ${step.id} needs exactly one submit button, found ${_.size(submitButtons)}`);
|
|
42887
|
+
return inert;
|
|
42888
|
+
}
|
|
42889
|
+
if (!isStepEligibleInDom(step, _, requiredPollIds)) {
|
|
42890
|
+
pendo.log(`[auto-advance] not arming: rendered step ${step.id} failed the eligibility re-check`);
|
|
42891
|
+
return inert;
|
|
42892
|
+
}
|
|
42893
|
+
const submitButton = submitButtons[0];
|
|
42894
|
+
// A click binds every action on the node and dispatches one event each
|
|
42895
|
+
// (build.js:205-207); we dispatch one, so a second action would be dropped.
|
|
42896
|
+
if (_.size(submitButton.actions) !== 1) {
|
|
42897
|
+
pendo.log(`[auto-advance] not arming: step ${step.id} submit button carries ${_.size(submitButton.actions)} actions, expected one`);
|
|
42898
|
+
return inert;
|
|
42899
|
+
}
|
|
42900
|
+
const submitAction = _.find(submitButton.actions, (action) => action.action === 'submitPoll' || action.action === 'submitPollAndGoToStep');
|
|
42901
|
+
if (!submitAction)
|
|
42902
|
+
return inert;
|
|
42903
|
+
let hasFired = false;
|
|
42904
|
+
let restoreSubmitButtons = _.noop;
|
|
42905
|
+
function fire() {
|
|
42906
|
+
if (hasFired)
|
|
42907
|
+
return;
|
|
42908
|
+
hasFired = true;
|
|
42909
|
+
try {
|
|
42910
|
+
// Shape-identical to what bindActionToNode emits for a click, so
|
|
42911
|
+
// beforeAdvance consumers intercept an auto-advance the same way.
|
|
42912
|
+
step.eventRouter.eventable.trigger('pendoEvent', {
|
|
42913
|
+
action: submitAction.action,
|
|
42914
|
+
params: submitAction.parameters,
|
|
42915
|
+
step,
|
|
42916
|
+
ignore: !!submitAction.ignore,
|
|
42917
|
+
srcElement: step.guideElement.find(`#${submitButton.props.id}`)[0]
|
|
42918
|
+
});
|
|
42919
|
+
}
|
|
42920
|
+
catch (error) {
|
|
42921
|
+
restoreSubmitButtons();
|
|
42922
|
+
reflowGuide(step, _, pendo);
|
|
42923
|
+
pendo.log('[auto-advance] submit dispatch failed, restored the submit button', error && error.message);
|
|
42924
|
+
}
|
|
42925
|
+
}
|
|
42926
|
+
const debouncedFire = _.debounce(fire, AUTO_ADVANCE_DELAY_MS);
|
|
42927
|
+
// Last, so nothing between here and the return can throw and leave the step
|
|
42928
|
+
// with a hidden button and no controller to un-hide it.
|
|
42929
|
+
restoreSubmitButtons = hideSubmitButtons(step, _, submitButtons);
|
|
42930
|
+
reflowGuide(step, _, pendo);
|
|
42931
|
+
return {
|
|
42932
|
+
isActive: true,
|
|
42933
|
+
notifyAnswered: debouncedFire,
|
|
42934
|
+
cancel: debouncedFire.cancel
|
|
42935
|
+
};
|
|
42936
|
+
}
|
|
42937
|
+
|
|
42938
|
+
const SURVEY_ANSWERS_KEY = 'pendo_survey_answers';
|
|
42939
|
+
const ANSWER_CONTROLS = 'input:radio, input:text, textarea, select';
|
|
42940
|
+
const TEXT_CONTROLS = 'input:text, textarea';
|
|
42941
|
+
// Matches MULTISTEP_CONTINUATION_TIME_LIMIT (walkthrough-guide.js:67), past which a guide is not continued.
|
|
42942
|
+
const ANSWER_TTL_MS = 12 * 60 * 60 * 1000;
|
|
42943
|
+
let pluginApi = null;
|
|
42944
|
+
let inMemoryAnswers = {};
|
|
42945
|
+
let persistedAnswersDropped = false;
|
|
42946
|
+
// A restricted storage.allowKeys silently rejects the session write, so mirror the visit here.
|
|
42947
|
+
const visitedSteps = {};
|
|
42948
|
+
function setPluginApi(api) {
|
|
42949
|
+
pluginApi = api;
|
|
42950
|
+
if (!api || !api.agentStorage)
|
|
42951
|
+
return;
|
|
42952
|
+
/**
|
|
42953
|
+
* Answers the visitor has given on each step of a survey, so a step re-shown
|
|
42954
|
+
* after Previous comes back filled in rather than blank.
|
|
42955
|
+
*
|
|
42956
|
+
* @name pendo_survey_answers
|
|
42957
|
+
* @category Cookies/sessionStorage
|
|
42958
|
+
* @access public
|
|
42959
|
+
* @label SURVEY_ANSWERS_KEY
|
|
42960
|
+
*/
|
|
42961
|
+
api.agentStorage.registry.addSession(SURVEY_ANSWERS_KEY);
|
|
42962
|
+
}
|
|
42963
|
+
// getSession() checks the allow-list but not storageIsDisabled, which the cookie path applies.
|
|
42964
|
+
function persistenceAllowed() {
|
|
42965
|
+
const allowed = !!pluginApi &&
|
|
42966
|
+
!!pluginApi.sessionStorage &&
|
|
42967
|
+
typeof pluginApi.storageIsDisabled === 'function' &&
|
|
42968
|
+
!pluginApi.storageIsDisabled();
|
|
42969
|
+
if (allowed) {
|
|
42970
|
+
persistedAnswersDropped = false;
|
|
42971
|
+
}
|
|
42972
|
+
else {
|
|
42973
|
+
dropPersistedAnswers();
|
|
42974
|
+
}
|
|
42975
|
+
return allowed;
|
|
42976
|
+
}
|
|
42977
|
+
// agentStorage.clearAll only walks local keys, so a session key has to be dropped here.
|
|
42978
|
+
function dropPersistedAnswers() {
|
|
42979
|
+
if (persistedAnswersDropped || !pluginApi || !pluginApi.sessionStorage)
|
|
42980
|
+
return;
|
|
42981
|
+
persistedAnswersDropped = true;
|
|
42982
|
+
pluginApi.sessionStorage.removeItem(SURVEY_ANSWERS_KEY);
|
|
42983
|
+
}
|
|
42984
|
+
function readAll() {
|
|
42985
|
+
if (!persistenceAllowed())
|
|
42986
|
+
return inMemoryAnswers;
|
|
42987
|
+
try {
|
|
42988
|
+
return JSON.parse(pluginApi.sessionStorage.getItem(SURVEY_ANSWERS_KEY)) || {};
|
|
42989
|
+
}
|
|
42990
|
+
catch (error) {
|
|
42991
|
+
return {};
|
|
42992
|
+
}
|
|
42993
|
+
}
|
|
42994
|
+
function writeAll(answers) {
|
|
42995
|
+
if (!persistenceAllowed()) {
|
|
42996
|
+
inMemoryAnswers = answers;
|
|
42997
|
+
return;
|
|
42998
|
+
}
|
|
42999
|
+
pluginApi.sessionStorage.setItem(SURVEY_ANSWERS_KEY, JSON.stringify(answers));
|
|
43000
|
+
}
|
|
43001
|
+
// identify() sets the new id before the reload tears the step down, so resolve this once per render.
|
|
43002
|
+
function visitorKey(pendo) {
|
|
43003
|
+
return (pendo._.isFunction(pendo.get_visitor_id) && pendo.get_visitor_id()) || 'unknown';
|
|
43004
|
+
}
|
|
43005
|
+
function stateKey(step, guide, visitor) {
|
|
43006
|
+
return `${visitor}:${guide.id}:${step.id}`;
|
|
43007
|
+
}
|
|
43008
|
+
// Host pages reassign Date.now to return a Date, which JSON writes out as a string.
|
|
43009
|
+
function getNow() {
|
|
43010
|
+
return new Date().getTime();
|
|
43011
|
+
}
|
|
43012
|
+
function isFresh(writtenAt) {
|
|
43013
|
+
return !!writtenAt && (getNow() - writtenAt) <= ANSWER_TTL_MS;
|
|
43014
|
+
}
|
|
43015
|
+
function readRecord(step, guide, visitor) {
|
|
43016
|
+
const forVisitor = readAll()[visitor];
|
|
43017
|
+
const forGuide = forVisitor && forVisitor[guide.id];
|
|
43018
|
+
const record = (forGuide && forGuide[step.id]) || null;
|
|
43019
|
+
return isFresh(record && record.at) ? record : null;
|
|
43020
|
+
}
|
|
43021
|
+
function updateRecord(step, guide, visitor, changes) {
|
|
43022
|
+
const all = readAll();
|
|
43023
|
+
const current = readRecord(step, guide, visitor) || {};
|
|
43024
|
+
if (!all[visitor])
|
|
43025
|
+
all[visitor] = {};
|
|
43026
|
+
if (!all[visitor][guide.id])
|
|
43027
|
+
all[visitor][guide.id] = {};
|
|
43028
|
+
all[visitor][guide.id][step.id] = {
|
|
43029
|
+
at: getNow(),
|
|
43030
|
+
visited: changes.visited || current.visited || 0,
|
|
43031
|
+
answers: changes.answers || current.answers || {}
|
|
43032
|
+
};
|
|
43033
|
+
writeAll(all);
|
|
43034
|
+
}
|
|
43035
|
+
// Previous only renders after an auto-advance step, so any other guide needs none of this.
|
|
43036
|
+
function guideAutoAdvances(guide, _) {
|
|
43037
|
+
return _.any(_.get(guide, 'steps', []), (step) => isAutoAdvanceRequested(step, _));
|
|
43038
|
+
}
|
|
43039
|
+
function pollIdFor(control) {
|
|
43040
|
+
let node = control;
|
|
43041
|
+
while (node && node.getAttribute) {
|
|
43042
|
+
const pollId = node.getAttribute('data-pendo-poll-id');
|
|
43043
|
+
if (pollId)
|
|
43044
|
+
return pollId;
|
|
43045
|
+
node = node.parentNode;
|
|
43046
|
+
}
|
|
43047
|
+
return null;
|
|
43048
|
+
}
|
|
43049
|
+
// submitPoll never reads a checkbox back (event-router.js:264), so restoring one shows nothing sent.
|
|
43050
|
+
function isRadio(control) {
|
|
43051
|
+
return control.type === 'radio';
|
|
43052
|
+
}
|
|
43053
|
+
function collectAnswers(step, pendo) {
|
|
43054
|
+
const answers = {};
|
|
43055
|
+
pendo._.each(step.guideElement.find(ANSWER_CONTROLS), (control) => {
|
|
43056
|
+
const pollId = pollIdFor(control);
|
|
43057
|
+
// One value per poll id, first in document order, as submitPoll reads it.
|
|
43058
|
+
if (!pollId || answers[pollId])
|
|
43059
|
+
return;
|
|
43060
|
+
if (isRadio(control) && !control.checked)
|
|
43061
|
+
return;
|
|
43062
|
+
if (!control.value)
|
|
43063
|
+
return;
|
|
43064
|
+
answers[pollId] = [control.value];
|
|
43065
|
+
});
|
|
43066
|
+
return answers;
|
|
43067
|
+
}
|
|
43068
|
+
// No event dispatched: a restored answer must not reach the gate listeners or re-arm auto-advance.
|
|
43069
|
+
function restoreAnswers(step, pendo, answers) {
|
|
43070
|
+
pendo._.each(step.guideElement.find(ANSWER_CONTROLS), (control) => {
|
|
43071
|
+
const pollId = pollIdFor(control);
|
|
43072
|
+
const values = pollId && answers[pollId];
|
|
43073
|
+
if (!values)
|
|
43074
|
+
return;
|
|
43075
|
+
if (isRadio(control)) {
|
|
43076
|
+
if (pendo._.contains(values, control.value))
|
|
43077
|
+
control.checked = true;
|
|
43078
|
+
return;
|
|
43079
|
+
}
|
|
43080
|
+
control.value = values[0];
|
|
43081
|
+
});
|
|
43082
|
+
}
|
|
43083
|
+
function isRevisit(step, guide, pendo) {
|
|
43084
|
+
if (!guideAutoAdvances(guide, pendo._))
|
|
43085
|
+
return false;
|
|
43086
|
+
const visitor = visitorKey(pendo);
|
|
43087
|
+
if (isFresh(visitedSteps[stateKey(step, guide, visitor)]))
|
|
43088
|
+
return true;
|
|
43089
|
+
const record = readRecord(step, guide, visitor);
|
|
43090
|
+
return !!record && (!!record.visited || !pendo._.isEmpty(record.answers));
|
|
43091
|
+
}
|
|
43092
|
+
const AnswerCache = {
|
|
43093
|
+
name: 'AnswerCache',
|
|
43094
|
+
script(step, guide, pendo) {
|
|
43095
|
+
const visitor = visitorKey(pendo);
|
|
43096
|
+
const record = readRecord(step, guide, visitor);
|
|
43097
|
+
restoreAnswers(step, pendo, (record && record.answers) || {});
|
|
43098
|
+
// Marked on leaving, not on render: sessionStorage survives a reload, which is not a revisit.
|
|
43099
|
+
this.on('beforeUnmount', (event) => {
|
|
43100
|
+
if (event && event.reason === 'hidden')
|
|
43101
|
+
return;
|
|
43102
|
+
visitedSteps[stateKey(step, guide, visitor)] = getNow();
|
|
43103
|
+
updateRecord(step, guide, visitor, { visited: 1 });
|
|
43104
|
+
});
|
|
43105
|
+
const stepRoot = step.guideElement[0];
|
|
43106
|
+
// change does not bubble on legacy browsers; it is here only for keyboard selection.
|
|
43107
|
+
step.attachEvent(stepRoot, 'click', capture);
|
|
43108
|
+
step.attachEvent(stepRoot, 'change', capture);
|
|
43109
|
+
pendo._.each(step.guideElement.find(TEXT_CONTROLS), (control) => {
|
|
43110
|
+
step.attachEvent(control, 'input', capture);
|
|
43111
|
+
});
|
|
43112
|
+
function capture() {
|
|
43113
|
+
updateRecord(step, guide, visitor, { answers: collectAnswers(step, pendo) });
|
|
43114
|
+
}
|
|
43115
|
+
},
|
|
43116
|
+
test(step, guide, pendo) {
|
|
43117
|
+
const guideElement = step && step.guideElement;
|
|
43118
|
+
return !!guideElement &&
|
|
43119
|
+
guideAutoAdvances(guide, pendo._) &&
|
|
43120
|
+
guideElement.find(ANSWER_CONTROLS).length > 0;
|
|
43121
|
+
}
|
|
43122
|
+
};
|
|
43123
|
+
|
|
42738
43124
|
function getZoneSafeMethod(_, method, target = window) {
|
|
42739
43125
|
var zoneSymbol = '__symbol__';
|
|
42740
43126
|
/* global Zone */
|
|
@@ -43211,124 +43597,6 @@ function substitutionIcon(color, size) {
|
|
|
43211
43597
|
13.7239 15.5 14V17Z" fill="${color}"></path> </g></svg></div>`);
|
|
43212
43598
|
}
|
|
43213
43599
|
|
|
43214
|
-
const AUTO_ADVANCE_DELAY_MS = 250;
|
|
43215
|
-
function isAutoAdvanceRequested(step, _) {
|
|
43216
|
-
return _.get(step, 'attributes.autoAdvance') === true;
|
|
43217
|
-
}
|
|
43218
|
-
// Survey-authored DOM carries no question-type classes, so eligibility keys off
|
|
43219
|
-
// answer-control shape: every required poll id must be radio-backed, which also
|
|
43220
|
-
// refuses a future non-radio type by default.
|
|
43221
|
-
function isStepEligibleInDom(step, _, requiredPollIds) {
|
|
43222
|
-
const guideElement = step && step.guideElement;
|
|
43223
|
-
if (!guideElement)
|
|
43224
|
-
return false;
|
|
43225
|
-
if (guideElement.find('textarea, input:text, input:checkbox, select').length)
|
|
43226
|
-
return false;
|
|
43227
|
-
const pollIds = _.uniq(_.map(guideElement.find('[data-pendo-poll-id]'), (element) => element.getAttribute('data-pendo-poll-id')));
|
|
43228
|
-
if (!pollIds.length)
|
|
43229
|
-
return false;
|
|
43230
|
-
return _.all(pollIds, (pollId) => _.contains(requiredPollIds, pollId) &&
|
|
43231
|
-
guideElement.find(`input:radio[data-pendo-poll-id="${pollId}"]`).length > 0);
|
|
43232
|
-
}
|
|
43233
|
-
function hideSubmitButtons(step, _, submitButtons) {
|
|
43234
|
-
const hiddenButtons = [];
|
|
43235
|
-
_.each(submitButtons, (button) => {
|
|
43236
|
-
const element = step.guideElement.find(`#${button.props.id}`)[0];
|
|
43237
|
-
if (!element)
|
|
43238
|
-
return;
|
|
43239
|
-
// display, not visibility: a reserved-but-invisible box leaves an empty gap
|
|
43240
|
-
// at the foot of the step. Authored styles reach the button inline
|
|
43241
|
-
// (build.js:151), so the original value is kept rather than blanked.
|
|
43242
|
-
hiddenButtons.push({ element, display: element.style.display });
|
|
43243
|
-
element.style.display = 'none';
|
|
43244
|
-
});
|
|
43245
|
-
return function restoreSubmitButtons() {
|
|
43246
|
-
_.each(hiddenButtons, (hiddenButton) => {
|
|
43247
|
-
hiddenButton.element.style.display = hiddenButton.display;
|
|
43248
|
-
});
|
|
43249
|
-
};
|
|
43250
|
-
}
|
|
43251
|
-
// The container's pixel height is measured during render (layout.js) before custom
|
|
43252
|
-
// scripts run, so collapsing a row afterwards leaves the box stale and the step keeps
|
|
43253
|
-
// the empty space. Same reflow pair metadata-substitution uses, scoped to this step so
|
|
43254
|
-
// a second guide on screen is left alone.
|
|
43255
|
-
function reflowGuide(step, _, pendo) {
|
|
43256
|
-
const recalculateGuideHeight = _.get(pendo, 'BuildingBlocks.BuildingBlockGuides.recalculateGuideHeight');
|
|
43257
|
-
try {
|
|
43258
|
-
if (_.isFunction(pendo.flexElement)) {
|
|
43259
|
-
pendo.flexElement(step.guideElement.find(guideMarkdownUtil.containerSelector));
|
|
43260
|
-
}
|
|
43261
|
-
if (_.isFunction(recalculateGuideHeight)) {
|
|
43262
|
-
recalculateGuideHeight(`pendo-g-${step.id}`, step.guideElement);
|
|
43263
|
-
}
|
|
43264
|
-
}
|
|
43265
|
-
catch (error) {
|
|
43266
|
-
pendo.log('[auto-advance] guide reflow failed, the step may keep an empty row', error && error.message);
|
|
43267
|
-
}
|
|
43268
|
-
}
|
|
43269
|
-
function createInertController() {
|
|
43270
|
-
return { isActive: false, notifyAnswered() { }, cancel() { } };
|
|
43271
|
-
}
|
|
43272
|
-
function createAutoAdvanceController(step, pendo, submitButtons, requiredPollIds) {
|
|
43273
|
-
const _ = pendo._;
|
|
43274
|
-
const inert = createInertController();
|
|
43275
|
-
if (!isAutoAdvanceRequested(step, _))
|
|
43276
|
-
return inert;
|
|
43277
|
-
// Only reachable for API-authored guides; choosing between two
|
|
43278
|
-
// submitPollAndGoToStep targets would be a guess.
|
|
43279
|
-
if (_.size(submitButtons) !== 1) {
|
|
43280
|
-
pendo.log(`[auto-advance] not arming: step ${step.id} needs exactly one submit button, found ${_.size(submitButtons)}`);
|
|
43281
|
-
return inert;
|
|
43282
|
-
}
|
|
43283
|
-
if (!isStepEligibleInDom(step, _, requiredPollIds)) {
|
|
43284
|
-
pendo.log(`[auto-advance] not arming: rendered step ${step.id} failed the eligibility re-check`);
|
|
43285
|
-
return inert;
|
|
43286
|
-
}
|
|
43287
|
-
const submitButton = submitButtons[0];
|
|
43288
|
-
// A click binds every action on the node and dispatches one event each
|
|
43289
|
-
// (build.js:205-207); we dispatch one, so a second action would be dropped.
|
|
43290
|
-
if (_.size(submitButton.actions) !== 1) {
|
|
43291
|
-
pendo.log(`[auto-advance] not arming: step ${step.id} submit button carries ${_.size(submitButton.actions)} actions, expected one`);
|
|
43292
|
-
return inert;
|
|
43293
|
-
}
|
|
43294
|
-
const submitAction = _.find(submitButton.actions, (action) => action.action === 'submitPoll' || action.action === 'submitPollAndGoToStep');
|
|
43295
|
-
if (!submitAction)
|
|
43296
|
-
return inert;
|
|
43297
|
-
let hasFired = false;
|
|
43298
|
-
let restoreSubmitButtons = _.noop;
|
|
43299
|
-
function fire() {
|
|
43300
|
-
if (hasFired)
|
|
43301
|
-
return;
|
|
43302
|
-
hasFired = true;
|
|
43303
|
-
try {
|
|
43304
|
-
// Shape-identical to what bindActionToNode emits for a click, so
|
|
43305
|
-
// beforeAdvance consumers intercept an auto-advance the same way.
|
|
43306
|
-
step.eventRouter.eventable.trigger('pendoEvent', {
|
|
43307
|
-
action: submitAction.action,
|
|
43308
|
-
params: submitAction.parameters,
|
|
43309
|
-
step,
|
|
43310
|
-
ignore: !!submitAction.ignore,
|
|
43311
|
-
srcElement: step.guideElement.find(`#${submitButton.props.id}`)[0]
|
|
43312
|
-
});
|
|
43313
|
-
}
|
|
43314
|
-
catch (error) {
|
|
43315
|
-
restoreSubmitButtons();
|
|
43316
|
-
reflowGuide(step, _, pendo);
|
|
43317
|
-
pendo.log('[auto-advance] submit dispatch failed, restored the submit button', error && error.message);
|
|
43318
|
-
}
|
|
43319
|
-
}
|
|
43320
|
-
const debouncedFire = _.debounce(fire, AUTO_ADVANCE_DELAY_MS);
|
|
43321
|
-
// Last, so nothing between here and the return can throw and leave the step
|
|
43322
|
-
// with a hidden button and no controller to un-hide it.
|
|
43323
|
-
restoreSubmitButtons = hideSubmitButtons(step, _, submitButtons);
|
|
43324
|
-
reflowGuide(step, _, pendo);
|
|
43325
|
-
return {
|
|
43326
|
-
isActive: true,
|
|
43327
|
-
notifyAnswered: debouncedFire,
|
|
43328
|
-
cancel: debouncedFire.cancel
|
|
43329
|
-
};
|
|
43330
|
-
}
|
|
43331
|
-
|
|
43332
43600
|
const requiredElement = '<span class="_pendo-required-indicator" style="color:red; font-style:italic;" title="Question is required"> *</span>';
|
|
43333
43601
|
const requiredSyntax = '{required/}';
|
|
43334
43602
|
const RequiredQuestions = {
|
|
@@ -43354,9 +43622,14 @@ const RequiredQuestions = {
|
|
|
43354
43622
|
}
|
|
43355
43623
|
});
|
|
43356
43624
|
}
|
|
43625
|
+
// Restored answers reach no listener, and the controller is still inert here.
|
|
43626
|
+
const revisited = isRevisit(step, guide, pendo);
|
|
43627
|
+
if (revisited) {
|
|
43628
|
+
evaluateRequiredQuestions(requiredQuestions);
|
|
43629
|
+
}
|
|
43357
43630
|
// Armed last, after the listeners above: runCustomScripts abandons the rest of
|
|
43358
43631
|
// this script on a throw, and the submit button is already disabled by now.
|
|
43359
|
-
if (isAutoAdvanceRequested(step, pendo._)) {
|
|
43632
|
+
if (isAutoAdvanceRequested(step, pendo._) && !revisited) {
|
|
43360
43633
|
this.on('beforeUnmount', () => autoAdvance.cancel());
|
|
43361
43634
|
try {
|
|
43362
43635
|
autoAdvance = createAutoAdvanceController(step, pendo, submitButtons, requiredPollIds);
|
|
@@ -43707,7 +43980,9 @@ function GuideMarkdown() {
|
|
|
43707
43980
|
const markdownScriptsEnabled = configReader.get(GUIDEMARKDOWN_CONFIG);
|
|
43708
43981
|
if (!markdownScriptsEnabled)
|
|
43709
43982
|
return;
|
|
43710
|
-
|
|
43983
|
+
setPluginApi(PluginAPI);
|
|
43984
|
+
// AnswerCache first: it restores the answers RequiredQuestions reads the gate against.
|
|
43985
|
+
guideMarkdown = [AnswerCache, PollBranching, MetadataSubstitution, RequiredQuestions, SkipToEligibleStep];
|
|
43711
43986
|
if (!pendo.designer) {
|
|
43712
43987
|
pendo._.each(guideMarkdown, function (script) {
|
|
43713
43988
|
PluginAPI.GlobalRuntime.addGlobalScript(script);
|
|
@@ -51498,11 +51773,13 @@ class Transport {
|
|
|
51498
51773
|
}
|
|
51499
51774
|
}
|
|
51500
51775
|
|
|
51501
|
-
const CATCHALL_TYPES = ['load', 'click', 'track'];
|
|
51776
|
+
const CATCHALL_TYPES = ['load', 'click', 'track', 'guideSeen'];
|
|
51777
|
+
const AGENTIC_TYPES = ["prompt", "agent_response", "user_reaction"];
|
|
51502
51778
|
const EVENT_KINDS = {
|
|
51503
|
-
|
|
51504
|
-
|
|
51505
|
-
|
|
51779
|
+
AGENTIC: 'agentic',
|
|
51780
|
+
CATCHALL: 'catchall',
|
|
51781
|
+
DEVLOG: 'devlog',
|
|
51782
|
+
RECORDING: 'recording'
|
|
51506
51783
|
};
|
|
51507
51784
|
class LiveReplayClient {
|
|
51508
51785
|
constructor(pendo, api) {
|
|
@@ -51583,18 +51860,21 @@ class LiveReplayClient {
|
|
|
51583
51860
|
catch (_a) { }
|
|
51584
51861
|
}
|
|
51585
51862
|
sendRecordingEvent(fields) {
|
|
51586
|
-
this.send(Object.assign(Object.assign({}, fields), { kind: EVENT_KINDS.
|
|
51863
|
+
this.send(Object.assign(Object.assign({}, fields), { kind: EVENT_KINDS.RECORDING, sequence: this.sequence++ }));
|
|
51587
51864
|
}
|
|
51588
51865
|
forwardEvent(event) {
|
|
51589
51866
|
if (!event || !event.data || !event.data.length)
|
|
51590
51867
|
return;
|
|
51591
51868
|
const capturedEvent = event.data[0];
|
|
51592
51869
|
const config = { event: capturedEvent };
|
|
51870
|
+
if (this.pendo._.contains(AGENTIC_TYPES, capturedEvent.type)) {
|
|
51871
|
+
config.kind = EVENT_KINDS.AGENTIC;
|
|
51872
|
+
}
|
|
51593
51873
|
if (this.pendo._.contains(CATCHALL_TYPES, capturedEvent.type)) {
|
|
51594
|
-
config.kind = EVENT_KINDS.
|
|
51874
|
+
config.kind = EVENT_KINDS.CATCHALL;
|
|
51595
51875
|
}
|
|
51596
|
-
if (capturedEvent.type === EVENT_KINDS.
|
|
51597
|
-
config.kind = EVENT_KINDS.
|
|
51876
|
+
if (capturedEvent.type === EVENT_KINDS.DEVLOG) {
|
|
51877
|
+
config.kind = EVENT_KINDS.DEVLOG;
|
|
51598
51878
|
}
|
|
51599
51879
|
if (!config.kind)
|
|
51600
51880
|
return;
|
|
@@ -52181,7 +52461,6 @@ class SessionRecorder {
|
|
|
52181
52461
|
var bind = this.pendo._.bind;
|
|
52182
52462
|
this.visitorConfig = visitorConfig;
|
|
52183
52463
|
this.clearOldSessionId();
|
|
52184
|
-
this.liveReplayClient.onLiveFlag(visitorConfig.live, this.visitorId);
|
|
52185
52464
|
this.sendQueue.start();
|
|
52186
52465
|
this.interval = setInterval(() => {
|
|
52187
52466
|
if (!this.sendQueue.failed()) {
|
|
@@ -52194,6 +52473,7 @@ class SessionRecorder {
|
|
|
52194
52473
|
errorHandler: this.errorHandler
|
|
52195
52474
|
}, config));
|
|
52196
52475
|
this._refreshIds();
|
|
52476
|
+
this.liveReplayClient.onLiveFlag(visitorConfig.live, this.visitorId, () => this.snapshot());
|
|
52197
52477
|
this.onRecordingStart();
|
|
52198
52478
|
}
|
|
52199
52479
|
catch (e) {
|