@pendo/agent 2.342.0 → 2.344.0
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 +34 -48
- package/dist/pendo.module.js +956 -374
- 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
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
function loadAgent(PendoConfig) {
|
|
2
2
|
|
|
3
|
+
let policy;
|
|
4
|
+
const policyOptions = {
|
|
5
|
+
createScriptURL: (str) => str,
|
|
6
|
+
createHTML: (str) => str
|
|
7
|
+
};
|
|
8
|
+
function getPolicy(pendo) {
|
|
9
|
+
if (!policy) {
|
|
10
|
+
if (pendo.trustedTypesPolicy) {
|
|
11
|
+
policy = pendo.trustedTypesPolicy;
|
|
12
|
+
}
|
|
13
|
+
else if (window.trustedTypes && typeof window.trustedTypes.createPolicy === 'function') {
|
|
14
|
+
policy = window.trustedTypes.createPolicy('pendo', policyOptions);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
policy = policyOptions;
|
|
18
|
+
}
|
|
19
|
+
pendo.trustedTypesPolicy = policy;
|
|
20
|
+
}
|
|
21
|
+
return policy;
|
|
22
|
+
}
|
|
23
|
+
|
|
3
24
|
var b64 = (function() {
|
|
4
25
|
|
|
5
26
|
var lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'.split('');
|
|
@@ -409,93 +430,52 @@ var sha1$1 = {exports: {}};
|
|
|
409
430
|
var sha1Exports = sha1$1.exports;
|
|
410
431
|
var sha1 = /*@__PURE__*/getDefaultExportFromCjs(sha1Exports);
|
|
411
432
|
|
|
412
|
-
let policy;
|
|
413
|
-
const policyOptions = {
|
|
414
|
-
createScriptURL: (str) => str,
|
|
415
|
-
createHTML: (str) => str
|
|
416
|
-
};
|
|
417
|
-
function getPolicy(pendo) {
|
|
418
|
-
if (!policy) {
|
|
419
|
-
if (pendo.trustedTypesPolicy) {
|
|
420
|
-
policy = pendo.trustedTypesPolicy;
|
|
421
|
-
}
|
|
422
|
-
else if (window.trustedTypes && typeof window.trustedTypes.createPolicy === 'function') {
|
|
423
|
-
policy = window.trustedTypes.createPolicy('pendo', policyOptions);
|
|
424
|
-
}
|
|
425
|
-
else {
|
|
426
|
-
policy = policyOptions;
|
|
427
|
-
}
|
|
428
|
-
pendo.trustedTypesPolicy = policy;
|
|
429
|
-
}
|
|
430
|
-
return policy;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
433
|
function escapeRegExp(string) {
|
|
434
434
|
if (!string)
|
|
435
435
|
return;
|
|
436
436
|
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
437
437
|
}
|
|
438
438
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
var windowOrMountPoint = loadAsModule(config) ? {} : window;
|
|
448
|
-
const agentScriptTag = document.currentScript || findAgentScriptTag(document.getElementsByTagName('script'), config.apiKey);
|
|
449
|
-
let globalKey = config.pendoGlobalKey || 'pendo';
|
|
450
|
-
if (agentScriptTag) {
|
|
451
|
-
globalKey = agentScriptTag.getAttribute('data-pendo-global-key') || globalKey;
|
|
452
|
-
config.pendoGlobalKey = globalKey;
|
|
453
|
-
}
|
|
454
|
-
pendo$1 = windowOrMountPoint[globalKey] = windowOrMountPoint[globalKey] || {};
|
|
455
|
-
return pendo$1;
|
|
456
|
-
}
|
|
457
|
-
function isStagingEnvironment(config) {
|
|
458
|
-
return config.environmentName === 'staging';
|
|
459
|
-
}
|
|
460
|
-
function isMinifiedAgent(config) {
|
|
461
|
-
return !config.unminified;
|
|
462
|
-
}
|
|
463
|
-
function isExtension(config) {
|
|
464
|
-
return config.installType === 'extension'; // can't use isExtensionAgent() here because of circular dependency and ConfigReader not being ready yet
|
|
465
|
-
}
|
|
466
|
-
function shouldUseStagingAgent(config) {
|
|
467
|
-
return !isExtension(config) && !isStagingEnvironment(config) && isStagingServer(config);
|
|
468
|
-
}
|
|
469
|
-
function shouldUseUnminifiedAgent(config, debuggingEnabled) {
|
|
470
|
-
return !isExtension(config) && isMinifiedAgent(config) && debuggingEnabled;
|
|
471
|
-
}
|
|
472
|
-
function findAgentScriptTag(scripts = [], apiKey) {
|
|
473
|
-
const regex = /^https:\/\/[\w\-.]*cdn[\w\-.]*\.(pendo-dev\.com|pendo\.io)\/agent\/static\/([\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12}|PENDO_API_KEY)\/pendo\.js$/g;
|
|
474
|
-
for (let i = 0; i < scripts.length; i++) {
|
|
475
|
-
const script = scripts[i];
|
|
476
|
-
if (regex.test(script.src) || script.id === `pendo-${apiKey}`) {
|
|
477
|
-
return script;
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
function buildCDNStagingAgentUrl(scripts = []) {
|
|
482
|
-
const agentScript = findAgentScriptTag(scripts);
|
|
483
|
-
if (agentScript) {
|
|
484
|
-
return agentScript.src.replace(/\.js$/, '-staging.js');
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
function getAlternateAgentUrl(config, useStaging, debuggingEnabled) {
|
|
488
|
-
var agentUrl = buildCDNStagingAgentUrl(document.getElementsByTagName('script')) || config.stagingAgentUrl;
|
|
489
|
-
if (debuggingEnabled) {
|
|
490
|
-
var suffix = 'unminified.js';
|
|
491
|
-
if (useStaging) {
|
|
492
|
-
suffix = 'staging-' + suffix;
|
|
493
|
-
}
|
|
494
|
-
agentUrl = agentUrl && agentUrl.replace(/staging\.js$/, suffix);
|
|
495
|
-
}
|
|
496
|
-
return agentUrl;
|
|
439
|
+
// Matching a host or url against configured patterns and hashes. Shared by the
|
|
440
|
+
// preamble and by the agent, so it must stay as light on imports as the preamble
|
|
441
|
+
// is - nothing from the core SDK belongs in here.
|
|
442
|
+
function getHash(str) {
|
|
443
|
+
return b64.uint8ToBase64(sha1
|
|
444
|
+
.create()
|
|
445
|
+
.update(str)
|
|
446
|
+
.digest());
|
|
497
447
|
}
|
|
498
|
-
|
|
448
|
+
// A configured pattern is meant for a whole origin, but test() is a substring
|
|
449
|
+
// search, so on its own it also accepts a look-alike like
|
|
450
|
+
// "https://app.customer.com.attacker.tld". Rebuild it anchored. The group keeps
|
|
451
|
+
// a top-level alternation from escaping the anchors, and the optional port keeps
|
|
452
|
+
// a pattern that doesn't spell one out matching an origin that carries one. A
|
|
453
|
+
// pattern ending in its own '$' opts out of the port, having already said where
|
|
454
|
+
// it ends.
|
|
455
|
+
//
|
|
456
|
+
// Nothing here makes room for a missing scheme. allowedOriginServers is tested
|
|
457
|
+
// against a full origin and only ever comes from the Pendo config, and a string
|
|
458
|
+
// entry is already compiled as ^value$, so a scheme-less entry has never
|
|
459
|
+
// matched anything. A scheme-less glob only did because it wasn't anchored,
|
|
460
|
+
// which is the bug being fixed rather than behaviour to keep.
|
|
461
|
+
//
|
|
462
|
+
// 'm' is dropped rather than carried over, since it would let ^ and $ match at
|
|
463
|
+
// a line break instead of at the ends.
|
|
464
|
+
//
|
|
465
|
+
// Compiled forms are kept by source: isTrustedOrigin runs on every postMessage,
|
|
466
|
+
// and this would otherwise build a RegExp per pattern per call. A plain object
|
|
467
|
+
// rather than a Map or WeakMap - neither exists in IE, and nothing here is
|
|
468
|
+
// polyfilled.
|
|
469
|
+
var anchoredPatterns = {};
|
|
470
|
+
function anchorToWholeOrigin(regexp) {
|
|
471
|
+
var flags = regexp.ignoreCase ? 'i' : '';
|
|
472
|
+
var key = 're:' + flags + ':' + regexp.source;
|
|
473
|
+
if (!anchoredPatterns[key]) {
|
|
474
|
+
anchoredPatterns[key] = new RegExp('^(?:' + regexp.source + ')(?::\\d+)?$', flags);
|
|
475
|
+
}
|
|
476
|
+
return anchoredPatterns[key];
|
|
477
|
+
}
|
|
478
|
+
function urlMatchesPatterns(url, patterns = [], hashes = [], anchorPatterns = true) {
|
|
499
479
|
if (!url)
|
|
500
480
|
return false;
|
|
501
481
|
if (hashes.length > 0) {
|
|
@@ -520,6 +500,9 @@ function urlMatchesPatterns(url, patterns = [], hashes = []) {
|
|
|
520
500
|
pattern = pattern.value;
|
|
521
501
|
}
|
|
522
502
|
}
|
|
503
|
+
if (anchorPatterns && pattern instanceof RegExp) {
|
|
504
|
+
pattern = anchorToWholeOrigin(pattern);
|
|
505
|
+
}
|
|
523
506
|
if (typeof pattern === 'string') {
|
|
524
507
|
// String entries are literal origins/hosts, not patterns. Escape
|
|
525
508
|
// them so a '.' can't act as a wildcard and match a look-alike domain.
|
|
@@ -532,6 +515,15 @@ function urlMatchesPatterns(url, patterns = [], hashes = []) {
|
|
|
532
515
|
}
|
|
533
516
|
return false;
|
|
534
517
|
}
|
|
518
|
+
/**
|
|
519
|
+
* The host part of an origin, or the input unchanged when it carries no scheme.
|
|
520
|
+
* Allowed-origin entries are stored however they were provided - the host picker
|
|
521
|
+
* offers bare hosts, while Pendo's own entries and older hand-entered ones are
|
|
522
|
+
* full origins - so matching tries both forms rather than rewriting either.
|
|
523
|
+
*/
|
|
524
|
+
function stripScheme$1(origin) {
|
|
525
|
+
return String(origin || '').replace(/^[a-z][a-z0-9+.-]*:\/\//i, '');
|
|
526
|
+
}
|
|
535
527
|
function domainSuffixes(host) {
|
|
536
528
|
var hostname = (host || '').split(':')[0].toLowerCase();
|
|
537
529
|
if (!hostname || /^[\d.]+$/.test(hostname) || hostname.indexOf(':') >= 0)
|
|
@@ -543,7 +535,7 @@ function domainSuffixes(host) {
|
|
|
543
535
|
}
|
|
544
536
|
return suffixes;
|
|
545
537
|
}
|
|
546
|
-
function
|
|
538
|
+
function hostMatchesDomains(host, domains = []) {
|
|
547
539
|
if (!domains.length)
|
|
548
540
|
return false;
|
|
549
541
|
var suffixes = domainSuffixes(host);
|
|
@@ -553,6 +545,79 @@ function hostMatchesStagingDomains(host, domains = []) {
|
|
|
553
545
|
}
|
|
554
546
|
return false;
|
|
555
547
|
}
|
|
548
|
+
|
|
549
|
+
var STAGING_SERVER_HASHES = 'stagingServerHashes';
|
|
550
|
+
var STAGING_DOMAINS = 'stagingDomains';
|
|
551
|
+
var pendo$1;
|
|
552
|
+
var _pendoConfig = {};
|
|
553
|
+
function loadAsModule(config) {
|
|
554
|
+
return config.loadAsModule;
|
|
555
|
+
}
|
|
556
|
+
function createPendoObject(config) {
|
|
557
|
+
var windowOrMountPoint = loadAsModule(config) ? {} : window;
|
|
558
|
+
const agentScriptTag = document.currentScript || findAgentScriptTag(document.getElementsByTagName('script'), config.apiKey);
|
|
559
|
+
let globalKey = config.pendoGlobalKey || 'pendo';
|
|
560
|
+
if (agentScriptTag) {
|
|
561
|
+
globalKey = agentScriptTag.getAttribute('data-pendo-global-key') || globalKey;
|
|
562
|
+
config.pendoGlobalKey = globalKey;
|
|
563
|
+
}
|
|
564
|
+
pendo$1 = windowOrMountPoint[globalKey] = windowOrMountPoint[globalKey] || {};
|
|
565
|
+
return pendo$1;
|
|
566
|
+
}
|
|
567
|
+
function isStagingEnvironment(config) {
|
|
568
|
+
return config.environmentName === 'staging';
|
|
569
|
+
}
|
|
570
|
+
function isMinifiedAgent(config) {
|
|
571
|
+
return !config.unminified;
|
|
572
|
+
}
|
|
573
|
+
function isExtension(config) {
|
|
574
|
+
return config.installType === 'extension'; // can't use isExtensionAgent() here because of circular dependency and ConfigReader not being ready yet
|
|
575
|
+
}
|
|
576
|
+
function shouldUseStagingAgent(config) {
|
|
577
|
+
return !isExtension(config) && !isStagingEnvironment(config) && isStagingServer(config);
|
|
578
|
+
}
|
|
579
|
+
function shouldUseUnminifiedAgent(config, debuggingEnabled) {
|
|
580
|
+
return !isExtension(config) && isMinifiedAgent(config) && debuggingEnabled;
|
|
581
|
+
}
|
|
582
|
+
var CDN_AGENT_URL = /^https:\/\/[\w\-.]*cdn[\w\-.]*\.(pendo-dev\.com|pendo\.io)\/agent\/static\/([\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12}|PENDO_API_KEY)\/pendo\.js$/;
|
|
583
|
+
function isCDNAgentScript(script) {
|
|
584
|
+
return CDN_AGENT_URL.test(script.src);
|
|
585
|
+
}
|
|
586
|
+
function findAgentScriptTag(scripts = [], apiKey) {
|
|
587
|
+
for (let i = 0; i < scripts.length; i++) {
|
|
588
|
+
const script = scripts[i];
|
|
589
|
+
if (isCDNAgentScript(script) || script.id === `pendo-${apiKey}`) {
|
|
590
|
+
return script;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
function findCDNAgentScriptTag(scripts = []) {
|
|
595
|
+
for (let i = 0; i < scripts.length; i++) {
|
|
596
|
+
if (isCDNAgentScript(scripts[i])) {
|
|
597
|
+
return scripts[i];
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
// Only a src that passed the CDN allowlist may be rewritten into an alternate
|
|
602
|
+
// agent URL. Matching on id here would let any <script> in the page pick the
|
|
603
|
+
// script that replaces the agent.
|
|
604
|
+
function buildCDNStagingAgentUrl(scripts = []) {
|
|
605
|
+
const agentScript = findCDNAgentScriptTag(scripts);
|
|
606
|
+
if (agentScript) {
|
|
607
|
+
return agentScript.src.replace(/\.js$/, '-staging.js');
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
function getAlternateAgentUrl(config, useStaging, debuggingEnabled) {
|
|
611
|
+
var agentUrl = buildCDNStagingAgentUrl(document.getElementsByTagName('script')) || config.stagingAgentUrl;
|
|
612
|
+
if (debuggingEnabled) {
|
|
613
|
+
var suffix = 'unminified.js';
|
|
614
|
+
if (useStaging) {
|
|
615
|
+
suffix = 'staging-' + suffix;
|
|
616
|
+
}
|
|
617
|
+
agentUrl = agentUrl && agentUrl.replace(/staging\.js$/, suffix);
|
|
618
|
+
}
|
|
619
|
+
return agentUrl;
|
|
620
|
+
}
|
|
556
621
|
function isStagingServer(config, location) {
|
|
557
622
|
if (!config) {
|
|
558
623
|
config = getPendoConfig();
|
|
@@ -566,14 +631,10 @@ function isStagingServer(config, location) {
|
|
|
566
631
|
if (!location) {
|
|
567
632
|
location = window.location;
|
|
568
633
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
return b64.uint8ToBase64(sha1
|
|
574
|
-
.create()
|
|
575
|
-
.update(str)
|
|
576
|
-
.digest());
|
|
634
|
+
// Unanchored: staging globs are matched against location.host and don't
|
|
635
|
+
// account for a port, and picking the staging agent isn't a trust decision.
|
|
636
|
+
return urlMatchesPatterns(location.host, config.stagingServers, config[STAGING_SERVER_HASHES], false) ||
|
|
637
|
+
hostMatchesDomains(location.host, config[STAGING_DOMAINS]);
|
|
577
638
|
}
|
|
578
639
|
var METHODS_TO_CAPTURE = [
|
|
579
640
|
'initialize',
|
|
@@ -2793,6 +2854,15 @@ function deepCopy(obj) {
|
|
|
2793
2854
|
}
|
|
2794
2855
|
}
|
|
2795
2856
|
|
|
2857
|
+
// Markup alone can define window.pendo_options (DOM clobbering via an element's id), so only
|
|
2858
|
+
// accept it when it's a plain object the page actually assigned
|
|
2859
|
+
function getLegacyGlobalSnippetOptions() {
|
|
2860
|
+
var options = window.pendo_options;
|
|
2861
|
+
if (Object.prototype.toString.call(options) !== '[object Object]')
|
|
2862
|
+
return;
|
|
2863
|
+
return options;
|
|
2864
|
+
}
|
|
2865
|
+
|
|
2796
2866
|
var originalOptions;
|
|
2797
2867
|
var ConfigReader = (function () {
|
|
2798
2868
|
// imports
|
|
@@ -2869,6 +2939,7 @@ var ConfigReader = (function () {
|
|
|
2869
2939
|
addOption('additionalPublicAppIds', [PENDO_CONFIG_SRC, SNIPPET_SRC], undefined, undefined, ['additionalApiKeys']);
|
|
2870
2940
|
addOption('allowedOriginServers');
|
|
2871
2941
|
addOption('allowedOriginServerHashes');
|
|
2942
|
+
addOption('enforceAllowedOriginServers', [SNIPPET_SRC, PENDO_CONFIG_SRC], false);
|
|
2872
2943
|
addOption('allowMixedApplicationFrames', [SNIPPET_SRC, PENDO_CONFIG_SRC], true);
|
|
2873
2944
|
addOption('allowMixedApiKeyFrames', [SNIPPET_SRC, PENDO_CONFIG_SRC], true);
|
|
2874
2945
|
/**
|
|
@@ -3120,16 +3191,16 @@ var ConfigReader = (function () {
|
|
|
3120
3191
|
addOption('pendoGlobalKey', [PENDO_CONFIG_SRC]);
|
|
3121
3192
|
/**
|
|
3122
3193
|
* By default, if there are multiple frames on the page that have the web SDK installed, they will communicate
|
|
3123
|
-
* using
|
|
3124
|
-
*
|
|
3194
|
+
* using the browser's Broadcast Channel API. This will only work when all frames use the same domain. If set
|
|
3195
|
+
* to `false` the web SDK will instead use `window.postMessage`, which also works across domains.
|
|
3125
3196
|
*
|
|
3126
3197
|
* @access public
|
|
3127
3198
|
* @category Config/Core
|
|
3128
3199
|
* @name preferBroadcastChannel
|
|
3129
|
-
* @default
|
|
3200
|
+
* @default true
|
|
3130
3201
|
* @type {boolean}
|
|
3131
3202
|
*/
|
|
3132
|
-
addOption('preferBroadcastChannel', [SNIPPET_SRC, PENDO_CONFIG_SRC],
|
|
3203
|
+
addOption('preferBroadcastChannel', [SNIPPET_SRC, PENDO_CONFIG_SRC], true);
|
|
3133
3204
|
/**
|
|
3134
3205
|
* By default, the web SDK will scan the page every 500ms to determine if any new badges or guides are eligible.
|
|
3135
3206
|
* To improve idle performance, this can be set to `true` and then the web SDK will instead use a mutation
|
|
@@ -3144,17 +3215,17 @@ var ConfigReader = (function () {
|
|
|
3144
3215
|
addOption('preferMutationObserver', [SNIPPET_SRC, PENDO_CONFIG_SRC], false);
|
|
3145
3216
|
addOption('preventCookieRefresh');
|
|
3146
3217
|
/**
|
|
3147
|
-
* By default, the web SDK
|
|
3148
|
-
*
|
|
3149
|
-
*
|
|
3218
|
+
* By default, the web SDK uses the `visibilitychange` event listener and sends events when the visibility is
|
|
3219
|
+
* changing to `"hidden"`. If set to `false`, the web SDK will instead add an `unload` event listener to the
|
|
3220
|
+
* page to make sure it sends events upon navigation.
|
|
3150
3221
|
*
|
|
3151
3222
|
* @access public
|
|
3152
3223
|
* @category Config/Core
|
|
3153
3224
|
* @name preventUnloadListener
|
|
3154
|
-
* @default
|
|
3225
|
+
* @default true
|
|
3155
3226
|
* @type {boolean}
|
|
3156
3227
|
*/
|
|
3157
|
-
addOption('preventUnloadListener', [SNIPPET_SRC, PENDO_CONFIG_SRC],
|
|
3228
|
+
addOption('preventUnloadListener', [SNIPPET_SRC, PENDO_CONFIG_SRC], true);
|
|
3158
3229
|
addOption('promotedWebSDKMetadataConfigurations', [PENDO_CONFIG_SRC], undefined, undefined, ['promotedAgentMetadataConfigurations']);
|
|
3159
3230
|
/**
|
|
3160
3231
|
* An array of strings or a function that returns an array of strings. The web SDK will then strip any query
|
|
@@ -3419,6 +3490,18 @@ var ConfigReader = (function () {
|
|
|
3419
3490
|
*/
|
|
3420
3491
|
addOption('disableGuidePseudoStyles', [SNIPPET_SRC, PENDO_CONFIG_SRC], false);
|
|
3421
3492
|
addOption('disableImportantStyleAttributes', [SNIPPET_SRC, PENDO_CONFIG_SRC], false);
|
|
3493
|
+
/**
|
|
3494
|
+
* When a guide has a backdrop enabled, by default elements outside the guide are hidden from assistive
|
|
3495
|
+
* technology using `aria-hidden`. If `true`, the web SDK will instead apply the `inert` attribute,
|
|
3496
|
+
* which also removes those elements from focus and pointer interaction.
|
|
3497
|
+
*
|
|
3498
|
+
* @access public
|
|
3499
|
+
* @category Config/Guides
|
|
3500
|
+
* @name useInertForBackdrop
|
|
3501
|
+
* @default false
|
|
3502
|
+
* @type {boolean}
|
|
3503
|
+
*/
|
|
3504
|
+
addOption('useInertForBackdrop', [SNIPPET_SRC, PENDO_CONFIG_SRC], false);
|
|
3422
3505
|
/**
|
|
3423
3506
|
* Content of badge and target element activated guides will be prefetched on load to improve guide display
|
|
3424
3507
|
* time unless set to `true`.
|
|
@@ -3676,7 +3759,7 @@ var ConfigReader = (function () {
|
|
|
3676
3759
|
initializeOptions();
|
|
3677
3760
|
var sourceGetters = {};
|
|
3678
3761
|
sourceGetters[SNIPPET_SRC] = function () {
|
|
3679
|
-
return { lookup: originalOptions ||
|
|
3762
|
+
return { lookup: originalOptions || getLegacyGlobalSnippetOptions(), name: SNIPPET_SRC };
|
|
3680
3763
|
};
|
|
3681
3764
|
sourceGetters[PENDO_CONFIG_SRC] = function () {
|
|
3682
3765
|
var lookup = getPendoConfig();
|
|
@@ -3926,17 +4009,6 @@ function shouldWrapHashChange() {
|
|
|
3926
4009
|
function isMobileUserAgent() {
|
|
3927
4010
|
return (/Android|webOS|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i).test(getUA());
|
|
3928
4011
|
}
|
|
3929
|
-
// WICG inert polyfills through 3.1.0 install their MutationObserver-driven manager even where
|
|
3930
|
-
// the browser supports inert natively, and inject #inert-style when they do. With one active,
|
|
3931
|
-
// inerting a large subtree costs a full tree walk plus a subtree observer for as long as the
|
|
3932
|
-
// attribute stays, so treat the page as not supporting inert.
|
|
3933
|
-
function supportsInert() {
|
|
3934
|
-
const descriptor = window.HTMLElement && _.isFunction(Object.getOwnPropertyDescriptor) &&
|
|
3935
|
-
Object.getOwnPropertyDescriptor(window.HTMLElement.prototype, 'inert');
|
|
3936
|
-
const isNative = !!(descriptor && descriptor.get && isNativeCode(descriptor.get));
|
|
3937
|
-
const isPolyfilled = !!document.getElementById('inert-style');
|
|
3938
|
-
return isNative && !isPolyfilled;
|
|
3939
|
-
}
|
|
3940
4012
|
function isSafari(userAgent = getUA()) {
|
|
3941
4013
|
return userAgent.indexOf('Safari') >= 0 && !isChromium(userAgent);
|
|
3942
4014
|
}
|
|
@@ -3949,7 +4021,6 @@ function isChromium(userAgent = getUA()) {
|
|
|
3949
4021
|
const sniffer = {
|
|
3950
4022
|
supportsHistoryApi: shouldWrapNativeHistory,
|
|
3951
4023
|
supportsHashChange: shouldWrapHashChange,
|
|
3952
|
-
supportsInert,
|
|
3953
4024
|
hasEvent(event) {
|
|
3954
4025
|
// IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
|
|
3955
4026
|
// it. In particular the event is not fired when backspace or delete key are pressed or
|
|
@@ -4058,8 +4129,8 @@ let SERVER = '';
|
|
|
4058
4129
|
let ASSET_HOST = '';
|
|
4059
4130
|
let ASSET_PATH = '';
|
|
4060
4131
|
let DESIGNER_SERVER = '';
|
|
4061
|
-
let VERSION = '2.
|
|
4062
|
-
let PACKAGE_VERSION = '2.
|
|
4132
|
+
let VERSION = '2.344.0_';
|
|
4133
|
+
let PACKAGE_VERSION = '2.344.0';
|
|
4063
4134
|
let LOADER = 'xhr';
|
|
4064
4135
|
/* eslint-enable web-sdk-eslint-rules/no-gulp-env-references */
|
|
4065
4136
|
/**
|
|
@@ -4476,15 +4547,36 @@ class LogEmitter extends EventTarget {
|
|
|
4476
4547
|
}
|
|
4477
4548
|
const log = new LogEmitter();
|
|
4478
4549
|
|
|
4479
|
-
/** Key used to lookup the consent collection policy in the pendo configuration. */
|
|
4480
4550
|
const CONSENT_COLLECTION = 'cookieConsentCollection';
|
|
4481
|
-
/** Key used to lookup the analytics events in the consent collection policy. */
|
|
4482
4551
|
const ANALYTICS = 'analytics';
|
|
4483
|
-
/** Key used to lookup the guide analytics events in the consent collection policy. */
|
|
4484
4552
|
const GUIDE_ANALYTICS = 'guideAnalytics';
|
|
4485
|
-
/** Key used to lookup the text collection mode in the consent collection policy. */
|
|
4486
4553
|
const TEXT_COLLECTION = 'textCollection';
|
|
4487
4554
|
const TEXT_COLLECTION_LEVELS = ['none', 'whitelist', 'all'];
|
|
4555
|
+
const ANALYTICS_EVENT_TYPES = {
|
|
4556
|
+
agentResponse: 'agent_response',
|
|
4557
|
+
clicks: 'click',
|
|
4558
|
+
fieldChanges: 'change',
|
|
4559
|
+
focus: 'focus',
|
|
4560
|
+
formSubmits: 'submit',
|
|
4561
|
+
identify: 'identify',
|
|
4562
|
+
meta: 'meta',
|
|
4563
|
+
objectMetadata: 'objectMetadata',
|
|
4564
|
+
pageLoads: 'load',
|
|
4565
|
+
prompt: 'prompt',
|
|
4566
|
+
trackEvents: 'track',
|
|
4567
|
+
userReaction: 'user_reaction'
|
|
4568
|
+
};
|
|
4569
|
+
const GUIDE_EVENT_TYPES = [
|
|
4570
|
+
'guideActivity',
|
|
4571
|
+
'guideAdvanced',
|
|
4572
|
+
'guideDismissed',
|
|
4573
|
+
'guideNotSeen',
|
|
4574
|
+
'guideSeen',
|
|
4575
|
+
'guideSnoozeCanceled',
|
|
4576
|
+
'guideSnoozed',
|
|
4577
|
+
'guideTimeout',
|
|
4578
|
+
'pollResponse'
|
|
4579
|
+
];
|
|
4488
4580
|
let enabledProducts = null;
|
|
4489
4581
|
function getClassification() {
|
|
4490
4582
|
return ConfigReader.get(CONSENT_COLLECTION) || {};
|
|
@@ -4494,6 +4586,9 @@ function toTierList(tiers) {
|
|
|
4494
4586
|
return [tiers];
|
|
4495
4587
|
return _.isArray(tiers) ? tiers : [];
|
|
4496
4588
|
}
|
|
4589
|
+
function sameConsentTiers(a, b) {
|
|
4590
|
+
return _.isEqual(toTierList(a).slice().sort(), toTierList(b).slice().sort());
|
|
4591
|
+
}
|
|
4497
4592
|
function warnOnUnconfiguredTiers(grantedTiers, classification) {
|
|
4498
4593
|
_.each(grantedTiers, function (tier) {
|
|
4499
4594
|
if (_.isUndefined(classification[tier])) {
|
|
@@ -4504,30 +4599,62 @@ function warnOnUnconfiguredTiers(grantedTiers, classification) {
|
|
|
4504
4599
|
function moreTextPermissive(a, b) {
|
|
4505
4600
|
return TEXT_COLLECTION_LEVELS.indexOf(b) > TEXT_COLLECTION_LEVELS.indexOf(a) ? b : a;
|
|
4506
4601
|
}
|
|
4507
|
-
function
|
|
4508
|
-
return
|
|
4602
|
+
function toTextCollectionLevel(mode) {
|
|
4603
|
+
return _.contains(TEXT_COLLECTION_LEVELS, mode) ? mode : TEXT_COLLECTION_LEVELS[0];
|
|
4509
4604
|
}
|
|
4510
|
-
function
|
|
4511
|
-
return _.
|
|
4605
|
+
function toAnalyticsEventTypes(eventCollection) {
|
|
4606
|
+
return _.reduce(ANALYTICS_EVENT_TYPES, function (types, eventType, key) {
|
|
4607
|
+
if (eventCollection[key] === true) {
|
|
4608
|
+
types.push(eventType);
|
|
4609
|
+
}
|
|
4610
|
+
return types;
|
|
4611
|
+
}, []);
|
|
4612
|
+
}
|
|
4613
|
+
function normalizeTier(tier) {
|
|
4614
|
+
const { visitorIdentity = {}, analytics = {}, guides = {}, sessionReplay = {} } = tier || {};
|
|
4615
|
+
const eventCollection = analytics.eventCollection || {};
|
|
4616
|
+
const analyticsEnabled = analytics.enabled === true;
|
|
4617
|
+
return {
|
|
4618
|
+
identity: visitorIdentity.trackVisitors === true,
|
|
4619
|
+
guides: guides.enabled === true,
|
|
4620
|
+
replay: sessionReplay.enabled === true,
|
|
4621
|
+
utm: analyticsEnabled && eventCollection.utmParameters === true,
|
|
4622
|
+
analytics: analyticsEnabled ? toAnalyticsEventTypes(eventCollection) : [],
|
|
4623
|
+
guideAnalytics: analyticsEnabled && eventCollection.guideServing === true ? GUIDE_EVENT_TYPES.slice() : [],
|
|
4624
|
+
textCollection: analyticsEnabled ? toTextCollectionLevel(eventCollection.textCollectionMode) : TEXT_COLLECTION_LEVELS[0]
|
|
4625
|
+
};
|
|
4626
|
+
}
|
|
4627
|
+
function deniedProducts() {
|
|
4628
|
+
return {
|
|
4629
|
+
identity: false,
|
|
4630
|
+
guides: false,
|
|
4631
|
+
replay: false,
|
|
4632
|
+
utm: false,
|
|
4633
|
+
[ANALYTICS]: [],
|
|
4634
|
+
[GUIDE_ANALYTICS]: [],
|
|
4635
|
+
[TEXT_COLLECTION]: TEXT_COLLECTION_LEVELS[0]
|
|
4636
|
+
};
|
|
4637
|
+
}
|
|
4638
|
+
function combineTiers(resolved, granted) {
|
|
4639
|
+
return {
|
|
4640
|
+
identity: resolved.identity || granted.identity,
|
|
4641
|
+
guides: resolved.guides || granted.guides,
|
|
4642
|
+
replay: resolved.replay || granted.replay,
|
|
4643
|
+
utm: resolved.utm || granted.utm,
|
|
4644
|
+
[ANALYTICS]: _.union(resolved[ANALYTICS], granted[ANALYTICS]),
|
|
4645
|
+
[GUIDE_ANALYTICS]: _.union(resolved[GUIDE_ANALYTICS], granted[GUIDE_ANALYTICS]),
|
|
4646
|
+
[TEXT_COLLECTION]: moreTextPermissive(resolved[TEXT_COLLECTION], granted[TEXT_COLLECTION])
|
|
4647
|
+
};
|
|
4512
4648
|
}
|
|
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
4649
|
/**
|
|
4523
|
-
* Resolves the
|
|
4650
|
+
* Resolves the tiers a visitor consented to into what each product is allowed to do.
|
|
4524
4651
|
*
|
|
4525
|
-
*
|
|
4526
|
-
*
|
|
4527
|
-
*
|
|
4528
|
-
*
|
|
4529
|
-
*
|
|
4530
|
-
*
|
|
4652
|
+
* Every product starts denied and only granted tiers contribute, so a tier the visitor
|
|
4653
|
+
* hasn't opted into has no say -- it can't deny anything, and it can't allow anything.
|
|
4654
|
+
*
|
|
4655
|
+
* A subscription with no cookie consent configured resolves to an empty map instead, which
|
|
4656
|
+
* every read below treats as unconstrained: consent that was never set up can't deny
|
|
4657
|
+
* anything either.
|
|
4531
4658
|
*
|
|
4532
4659
|
* @access private
|
|
4533
4660
|
* @param {string | string[]} tiers tier or tiers the visitor consented to
|
|
@@ -4536,26 +4663,22 @@ const DEFAULT_RESOLVER = { initial: false, combine: orBoolean };
|
|
|
4536
4663
|
function applyConsentTiers(tiers) {
|
|
4537
4664
|
const cookieConsentCollection = getClassification();
|
|
4538
4665
|
const consentedCookieTiers = toTierList(tiers);
|
|
4539
|
-
const resolved = {};
|
|
4540
4666
|
warnOnUnconfiguredTiers(consentedCookieTiers, cookieConsentCollection);
|
|
4541
|
-
_.
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
});
|
|
4550
|
-
enabledProducts = resolved;
|
|
4667
|
+
if (_.isEmpty(cookieConsentCollection)) {
|
|
4668
|
+
enabledProducts = {};
|
|
4669
|
+
return getEnabledProducts();
|
|
4670
|
+
}
|
|
4671
|
+
enabledProducts = _.reduce(consentedCookieTiers, function (resolved, tier) {
|
|
4672
|
+
const granted = cookieConsentCollection[tier];
|
|
4673
|
+
return granted ? combineTiers(resolved, normalizeTier(granted)) : resolved;
|
|
4674
|
+
}, deniedProducts());
|
|
4551
4675
|
return getEnabledProducts();
|
|
4552
4676
|
}
|
|
4553
4677
|
function getEnabledProducts() {
|
|
4554
4678
|
return enabledProducts ? _.extend({}, enabledProducts) : null;
|
|
4555
4679
|
}
|
|
4556
4680
|
/**
|
|
4557
|
-
* A product is enabled
|
|
4558
|
-
* consent configured at all, or a product no tier ever mentions, there's nothing to deny.
|
|
4681
|
+
* A product is enabled only if some granted tier turned it on or cookie consent has not been set.
|
|
4559
4682
|
*
|
|
4560
4683
|
* @access private
|
|
4561
4684
|
* @param {string} product
|
|
@@ -4569,6 +4692,12 @@ function isProductEnabled(product) {
|
|
|
4569
4692
|
function isTrackingConsentDenied() {
|
|
4570
4693
|
return !isProductEnabled('identity');
|
|
4571
4694
|
}
|
|
4695
|
+
function isGuideConsentDenied() {
|
|
4696
|
+
return !isProductEnabled('guides');
|
|
4697
|
+
}
|
|
4698
|
+
function isUtmConsentDenied() {
|
|
4699
|
+
return !isProductEnabled('utm');
|
|
4700
|
+
}
|
|
4572
4701
|
/**
|
|
4573
4702
|
* Session replay is denied whenever tracking itself is denied (DNT) -- a visitor
|
|
4574
4703
|
* who can't be identified shouldn't have their session recorded either -- or when a
|
|
@@ -4604,6 +4733,9 @@ function hasConsentForAnalyticsEvent(type) {
|
|
|
4604
4733
|
function hasConsentForGuideEvent(type) {
|
|
4605
4734
|
return hasConsentForProduct(type, GUIDE_ANALYTICS);
|
|
4606
4735
|
}
|
|
4736
|
+
function resetConsentPolicy() {
|
|
4737
|
+
enabledProducts = null;
|
|
4738
|
+
}
|
|
4607
4739
|
|
|
4608
4740
|
const allowKeys = function () {
|
|
4609
4741
|
var allowKeys = ConfigReader.get('storage.allowKeys') || '*';
|
|
@@ -5915,7 +6047,6 @@ var Events = (function () {
|
|
|
5915
6047
|
new EventType('recording:unpaused', [LIFECYCLE]),
|
|
5916
6048
|
new EventType('recording:paused', [LIFECYCLE]),
|
|
5917
6049
|
new EventType('consent:changed', [LIFECYCLE]),
|
|
5918
|
-
new EventType('identifyRequested', [DEBUG, LIFECYCLE]),
|
|
5919
6050
|
new EventType('childFrameJoined', [FRAMES]),
|
|
5920
6051
|
new EventType('tabIdChanged:self', [DEBUG, LIFECYCLE])
|
|
5921
6052
|
];
|
|
@@ -11994,6 +12125,30 @@ _.extend(dom, {
|
|
|
11994
12125
|
});
|
|
11995
12126
|
_.extend(dom.prototype, DomEvent.$, DomQuery.$);
|
|
11996
12127
|
|
|
12128
|
+
// location.origin only arrived in IE11, and is missing again under the older
|
|
12129
|
+
// document modes IE11 can emulate
|
|
12130
|
+
function readOrigin(location) {
|
|
12131
|
+
if (location.origin !== undefined)
|
|
12132
|
+
return location.origin;
|
|
12133
|
+
return location.protocol + '//' + location.host;
|
|
12134
|
+
}
|
|
12135
|
+
function getLocationOrigin(window) {
|
|
12136
|
+
const strNull = 'null';
|
|
12137
|
+
const origin = readOrigin(window.location);
|
|
12138
|
+
if (origin === strNull && window.parent && window.parent != window) {
|
|
12139
|
+
try {
|
|
12140
|
+
const parentOrigin = readOrigin(window.parent.location);
|
|
12141
|
+
if (parentOrigin && parentOrigin !== strNull) {
|
|
12142
|
+
return parentOrigin;
|
|
12143
|
+
}
|
|
12144
|
+
}
|
|
12145
|
+
catch (e) {
|
|
12146
|
+
// cross-origin ancestor; cannot read top location
|
|
12147
|
+
}
|
|
12148
|
+
}
|
|
12149
|
+
return origin;
|
|
12150
|
+
}
|
|
12151
|
+
|
|
11997
12152
|
// Domains that Pendo owns, will be swapped in by build patches
|
|
11998
12153
|
const pendoDomainPatterns = [
|
|
11999
12154
|
/^https:\/\/((adopt|oem)\.)?((us1)\.)?(app|via|adopt|cdn|oem|novus-api|novus|portal)(\.(au|eu|gov|hsbc|jpn|tv))?\.pendo\.io$/,
|
|
@@ -12001,11 +12156,34 @@ const pendoDomainPatterns = [
|
|
|
12001
12156
|
/^https:\/\/hotfix-(ops|app)-([0-9]+-dot-)(pendo-au|pendo-eu|pendo-govramp|pendo-hsbc|pendo-io|pendo-jp-prod|pendo-tv|pendo-us1)\.appspot\.com$/,
|
|
12002
12157
|
/^https:\/\/(pendo-au|pendo-eu|pendo-govramp|pendo-hsbc|pendo-io|pendo-jp-prod|pendo-tv|pendo-us1)-static\.storage\.googleapis\.com$/
|
|
12003
12158
|
]; // eslint-disable-line web-sdk-eslint-rules/no-gulp-env-references
|
|
12159
|
+
/**
|
|
12160
|
+
* A leading dot marks a subtree: ".foo.com" covers foo.com and everything below
|
|
12161
|
+
* it, while "foo.com" stays that one host and is matched exactly above.
|
|
12162
|
+
*
|
|
12163
|
+
* Keeping the dot in what we compare is what tells the two apart, so subtree
|
|
12164
|
+
* entries can share allowedOriginServers / allowedOriginServerHashes instead of
|
|
12165
|
+
* needing a list of their own. stagingDomains needs a separate list only because
|
|
12166
|
+
* it strips the dot and hashes unconditionally, which allowed origins don't.
|
|
12167
|
+
*
|
|
12168
|
+
* Both forms are checked because appengine passes a non-glob name through
|
|
12169
|
+
* verbatim: as a quoted string when the subscription doesn't hash its servers,
|
|
12170
|
+
* as sha1 of that same string when it does.
|
|
12171
|
+
*
|
|
12172
|
+
* domainSuffixes drops any port and never yields a single label, so ".com" can
|
|
12173
|
+
* never match.
|
|
12174
|
+
*/
|
|
12175
|
+
function matchesSubtreeEntry(host, servers, hashes) {
|
|
12176
|
+
var suffixes = domainSuffixes(stripScheme$1(host));
|
|
12177
|
+
return _.any(suffixes, function (suffix) {
|
|
12178
|
+
var entry = '.' + suffix;
|
|
12179
|
+
return _.contains(servers, entry) || _.contains(hashes, getHash(entry));
|
|
12180
|
+
});
|
|
12181
|
+
}
|
|
12004
12182
|
function isTrustedOrigin(host) {
|
|
12005
12183
|
if (!host)
|
|
12006
12184
|
return false;
|
|
12007
12185
|
// for CNAME, trust the top-level origin
|
|
12008
|
-
if (host === window.location
|
|
12186
|
+
if (host === readOrigin(window.location))
|
|
12009
12187
|
return true;
|
|
12010
12188
|
// Trust the configured hosts
|
|
12011
12189
|
if (host === getDataHost())
|
|
@@ -12021,7 +12199,21 @@ function isTrustedOrigin(host) {
|
|
|
12021
12199
|
if (host === fullHostname)
|
|
12022
12200
|
return true;
|
|
12023
12201
|
}
|
|
12024
|
-
|
|
12202
|
+
// Entries are matched as both a full origin and a bare host, because that is
|
|
12203
|
+
// how they were stored: the host picker offers names straight off servername,
|
|
12204
|
+
// while Pendo's own entries and older hand-entered ones carry a scheme. A
|
|
12205
|
+
// string entry compiles to ^value$, so a bare host has never matched the full
|
|
12206
|
+
// origin these callers pass, and only a glob did - by accident, its leading
|
|
12207
|
+
// '.*' swallowing the scheme. Testing both forms keeps existing origin
|
|
12208
|
+
// entries pinned to their scheme and lets a bare host match whichever scheme
|
|
12209
|
+
// it arrives on, without rewriting anything already provisioned.
|
|
12210
|
+
var allowedServers = ConfigReader.get('allowedOriginServers', []);
|
|
12211
|
+
var allowedHashes = ConfigReader.get('allowedOriginServerHashes', []);
|
|
12212
|
+
if (urlMatchesPatterns(host, allowedServers, allowedHashes))
|
|
12213
|
+
return true;
|
|
12214
|
+
if (urlMatchesPatterns(stripScheme$1(host), allowedServers, allowedHashes))
|
|
12215
|
+
return true;
|
|
12216
|
+
if (matchesSubtreeEntry(host, allowedServers, allowedHashes))
|
|
12025
12217
|
return true;
|
|
12026
12218
|
return _.any(pendoDomainPatterns, function (pattern) {
|
|
12027
12219
|
return pattern.test(host);
|
|
@@ -14209,15 +14401,13 @@ const internalEvents = {
|
|
|
14209
14401
|
onEventCaptured: 1,
|
|
14210
14402
|
segmentFlagsUpdated: 1,
|
|
14211
14403
|
segmentFlagsReady: 1,
|
|
14212
|
-
segmentFlagsError: 1
|
|
14213
|
-
identifyRequested: 1
|
|
14404
|
+
segmentFlagsError: 1
|
|
14214
14405
|
};
|
|
14215
14406
|
const browserEvents = {
|
|
14216
14407
|
ready: 1,
|
|
14217
14408
|
segmentFlagsUpdated: 1,
|
|
14218
14409
|
segmentFlagsReady: 1,
|
|
14219
|
-
segmentFlagsError: 1
|
|
14220
|
-
identifyRequested: 1
|
|
14410
|
+
segmentFlagsError: 1
|
|
14221
14411
|
};
|
|
14222
14412
|
const supportedPublicEvents = [
|
|
14223
14413
|
'ready',
|
|
@@ -14231,8 +14421,7 @@ const supportedPublicEvents = [
|
|
|
14231
14421
|
'segmentFlagsError',
|
|
14232
14422
|
'validateGuide',
|
|
14233
14423
|
'validateLauncher',
|
|
14234
|
-
'validateGlobalScript'
|
|
14235
|
-
'identifyRequested'
|
|
14424
|
+
'validateGlobalScript'
|
|
14236
14425
|
];
|
|
14237
14426
|
function forwardInternalEvents(Events) {
|
|
14238
14427
|
const unsubscribes = _.reduce(supportedPublicEvents, function (unsubscribes, eventName) {
|
|
@@ -21118,8 +21307,11 @@ function isHidden(document) {
|
|
|
21118
21307
|
const visibilitychange = 'visibilitychange';
|
|
21119
21308
|
const pagehide = 'pagehide';
|
|
21120
21309
|
const unload = 'unload';
|
|
21310
|
+
function supportsPageHide() {
|
|
21311
|
+
return 'onpagehide' in window;
|
|
21312
|
+
}
|
|
21121
21313
|
function onPageHidden(callback, useVisibilityChange) {
|
|
21122
|
-
if (useVisibilityChange) {
|
|
21314
|
+
if (useVisibilityChange && supportsPageHide()) {
|
|
21123
21315
|
const unlisten = [];
|
|
21124
21316
|
const onChange = () => {
|
|
21125
21317
|
if (isHidden(document)) {
|
|
@@ -22161,14 +22353,29 @@ function ClickActivation() {
|
|
|
22161
22353
|
}
|
|
22162
22354
|
stopEvent(evt);
|
|
22163
22355
|
};
|
|
22356
|
+
// Safari does not focus the badge on mousedown, so focus leaves the app instead.
|
|
22357
|
+
// Apps that re-render on blur can remove the badge before mouseup, and the browser
|
|
22358
|
+
// then generates no click at all. Holding focus where it is avoids that (APP-168412)
|
|
22359
|
+
var preserveFocus = function (evt) {
|
|
22360
|
+
if (evt.preventDefault) {
|
|
22361
|
+
evt.preventDefault();
|
|
22362
|
+
}
|
|
22363
|
+
else {
|
|
22364
|
+
evt.returnValue = false;
|
|
22365
|
+
}
|
|
22366
|
+
};
|
|
22164
22367
|
badge.after('show', function () {
|
|
22165
22368
|
if (!attached) {
|
|
22166
22369
|
attachEvent(badgeElem, 'click', click);
|
|
22370
|
+
if (badge.preserveFocusOnClick) {
|
|
22371
|
+
attachEvent(badgeElem, 'mousedown', preserveFocus);
|
|
22372
|
+
}
|
|
22167
22373
|
attached = true;
|
|
22168
22374
|
}
|
|
22169
22375
|
});
|
|
22170
22376
|
badge.after('hide', function () {
|
|
22171
22377
|
detachEvent(badgeElem, 'click', click);
|
|
22378
|
+
detachEvent(badgeElem, 'mousedown', preserveFocus);
|
|
22172
22379
|
attached = false;
|
|
22173
22380
|
});
|
|
22174
22381
|
return badge;
|
|
@@ -22639,7 +22846,7 @@ function stepShowingProc(guide, step) {
|
|
|
22639
22846
|
return;
|
|
22640
22847
|
}
|
|
22641
22848
|
const embedSelector = _.get(guide, 'attributes.embedConfig.selector');
|
|
22642
|
-
if (embedSelector && !dom(embedSelector).length) {
|
|
22849
|
+
if (embedSelector && (!dom(embedSelector).length || (guideElement && !isInDocument(guideElement[0])))) {
|
|
22643
22850
|
step.hide();
|
|
22644
22851
|
return;
|
|
22645
22852
|
}
|
|
@@ -23603,14 +23810,14 @@ var autofocusStep = {
|
|
|
23603
23810
|
script: autofocusRuntimeScript
|
|
23604
23811
|
};
|
|
23605
23812
|
|
|
23606
|
-
function
|
|
23813
|
+
function backdropVisibilityRuntimeScript(step, guide, pendo) {
|
|
23607
23814
|
if (pendo.designer || !step.guideElement)
|
|
23608
23815
|
return;
|
|
23609
23816
|
if (step.hasBackdrop) {
|
|
23610
23817
|
const BuildingBlockGuides = pendo.BuildingBlocks.BuildingBlockGuides;
|
|
23611
23818
|
const guideElement = step.guideElement[0];
|
|
23612
23819
|
const excludeElements = getExcludedElements(step);
|
|
23613
|
-
const disabledElements =
|
|
23820
|
+
const disabledElements = ConfigReader.get('useInertForBackdrop')
|
|
23614
23821
|
? BuildingBlockGuides.addInertOutsideGuide(guideElement, [], excludeElements)
|
|
23615
23822
|
: BuildingBlockGuides.addAriaHiddenOutsideGuide(guideElement, [], excludeElements);
|
|
23616
23823
|
step.hiddenElements = (step.hiddenElements || []).concat(disabledElements);
|
|
@@ -23626,8 +23833,8 @@ function getExcludedElements(step) {
|
|
|
23626
23833
|
}
|
|
23627
23834
|
return excludeElements;
|
|
23628
23835
|
}
|
|
23629
|
-
var
|
|
23630
|
-
script:
|
|
23836
|
+
var backdropVisibilityStep = {
|
|
23837
|
+
script: backdropVisibilityRuntimeScript
|
|
23631
23838
|
};
|
|
23632
23839
|
|
|
23633
23840
|
function isTooltip({ _ }, step) {
|
|
@@ -23787,7 +23994,7 @@ function registerPostrenderScripts(runtime) {
|
|
|
23787
23994
|
advanceOnElement,
|
|
23788
23995
|
announcementMarkSeen,
|
|
23789
23996
|
autofocusStep,
|
|
23790
|
-
|
|
23997
|
+
backdropVisibilityStep,
|
|
23791
23998
|
repositionOnScroll,
|
|
23792
23999
|
setFocusToActivationElement$1,
|
|
23793
24000
|
videoLoadingAttribute
|
|
@@ -25381,8 +25588,17 @@ var initGuides = function (observer) {
|
|
|
25381
25588
|
var { guides } = event.data[0];
|
|
25382
25589
|
if (_.isUndefined(guides))
|
|
25383
25590
|
return;
|
|
25384
|
-
|
|
25591
|
+
if (guides) {
|
|
25592
|
+
enableGuides();
|
|
25593
|
+
return;
|
|
25594
|
+
}
|
|
25595
|
+
disableGuides();
|
|
25596
|
+
clearGuideThrottlingState();
|
|
25385
25597
|
});
|
|
25598
|
+
if (isGuideConsentDenied()) {
|
|
25599
|
+
disableGuides();
|
|
25600
|
+
clearGuideThrottlingState();
|
|
25601
|
+
}
|
|
25386
25602
|
teardownFns.push(onTurbolinksPageLoad(document, function onPageLoad() {
|
|
25387
25603
|
if (pendoDotUrl.get() === reloadGuides.lastUrl) {
|
|
25388
25604
|
// Force a reload if Turbolinks replaces the document body without changing the url
|
|
@@ -25511,6 +25727,11 @@ function disableGuides() {
|
|
|
25511
25727
|
setGuidesDisabled(true);
|
|
25512
25728
|
resetPendoUI();
|
|
25513
25729
|
}
|
|
25730
|
+
function clearGuideThrottlingState() {
|
|
25731
|
+
_.each(THROTTLING_STATE, function (throttlingStorageKey) {
|
|
25732
|
+
agentStorage.clear(throttlingStorageKey);
|
|
25733
|
+
});
|
|
25734
|
+
}
|
|
25514
25735
|
var setGuidesDelayed = function (areDelayed) {
|
|
25515
25736
|
var originalOptions = ConfigReader.getLocalConfig();
|
|
25516
25737
|
delete originalOptions.delayGuides;
|
|
@@ -25549,10 +25770,20 @@ var adjustActiveGuideOnResize = function () {
|
|
|
25549
25770
|
// as the user zooms the browser APP-53281
|
|
25550
25771
|
log.debug('adjustActiveGuideOnResize firing');
|
|
25551
25772
|
var activeGuide = getActiveGuide();
|
|
25552
|
-
if (activeGuide && activeGuide.step && !pendo$1.ignoreResize) {
|
|
25773
|
+
if (activeGuide && activeGuide.step && !pendo$1.ignoreResize && !isEditingGuideTextField(activeGuide.step)) {
|
|
25553
25774
|
activeGuide.step.redraw();
|
|
25554
25775
|
}
|
|
25555
25776
|
};
|
|
25777
|
+
// Skip the resize redraw while the user is focused in a guide text field: the redraw blurs the
|
|
25778
|
+
// field, which on mobile dismisses the soft keyboard (APP-167261).
|
|
25779
|
+
var isEditingGuideTextField = function (step) {
|
|
25780
|
+
var active = document.activeElement;
|
|
25781
|
+
if (!active || (active.tagName !== 'TEXTAREA' && active.tagName !== 'INPUT')) {
|
|
25782
|
+
return false;
|
|
25783
|
+
}
|
|
25784
|
+
var guideEl = step.guideElement && step.guideElement[0];
|
|
25785
|
+
return !!guideEl && guideEl.contains(active);
|
|
25786
|
+
};
|
|
25556
25787
|
var resizeHandler = function () {
|
|
25557
25788
|
// In some browsers, it's possible for activeElement to become undefined or null
|
|
25558
25789
|
var activeElementId = _.get(document, 'activeElement.id', '');
|
|
@@ -25881,6 +26112,46 @@ function stopLivePreviewRelay(onStopped) {
|
|
|
25881
26112
|
}, relay.origin);
|
|
25882
26113
|
}
|
|
25883
26114
|
|
|
26115
|
+
/**
|
|
26116
|
+
* Whether to police who may join the cross-frame channel (APP-165872).
|
|
26117
|
+
*
|
|
26118
|
+
* Off by default, and deliberately named after the setting it depends on: the
|
|
26119
|
+
* check trusts allowedOriginServers, which Pendo provisions per subscription,
|
|
26120
|
+
* so it can only be switched on for an application whose origins are actually
|
|
26121
|
+
* listed. With it off the relay behaves exactly as it always has.
|
|
26122
|
+
*/
|
|
26123
|
+
function enforceAllowedOriginServers() {
|
|
26124
|
+
return ConfigReader.get('enforceAllowedOriginServers') === true;
|
|
26125
|
+
}
|
|
26126
|
+
/**
|
|
26127
|
+
* Top's origin, read straight off the ancestor chain. Every current browser
|
|
26128
|
+
* exposes location.ancestorOrigins; IE does not.
|
|
26129
|
+
*
|
|
26130
|
+
* Returns null whenever top can't be identified - no ancestorOrigins, an empty
|
|
26131
|
+
* chain, an opaque top that reports the literal string "null", or a location
|
|
26132
|
+
* that throws. Callers must treat null as "refuse", never as "trusted": an
|
|
26133
|
+
* application that asked for enforcement gets it everywhere, including on the
|
|
26134
|
+
* browsers where we have no way to look.
|
|
26135
|
+
*/
|
|
26136
|
+
function readTopOrigin(window) {
|
|
26137
|
+
try {
|
|
26138
|
+
if (window === window.top)
|
|
26139
|
+
return readOrigin(window.location);
|
|
26140
|
+
var ancestors = window.location.ancestorOrigins;
|
|
26141
|
+
if (!ancestors || !ancestors.length)
|
|
26142
|
+
return null;
|
|
26143
|
+
// last entry is the top-most ancestor; a sandboxed or data: top reports
|
|
26144
|
+
// the literal string "null", which is not a usable targetOrigin. Only the
|
|
26145
|
+
// top matters here - an opaque ancestor in between still leaves a real
|
|
26146
|
+
// origin at the end of the chain.
|
|
26147
|
+
var top = ancestors[ancestors.length - 1];
|
|
26148
|
+
return (top && top !== 'null') ? top : null;
|
|
26149
|
+
}
|
|
26150
|
+
catch (e) {
|
|
26151
|
+
return null;
|
|
26152
|
+
}
|
|
26153
|
+
}
|
|
26154
|
+
|
|
25884
26155
|
var REMOVE_FRAME_ACTION = 'frames/removeFrame';
|
|
25885
26156
|
function tryParseJson(messageData) {
|
|
25886
26157
|
if (!_.isString(messageData)) {
|
|
@@ -25942,6 +26213,12 @@ _.extend(TopFrameRelay.prototype, {
|
|
|
25942
26213
|
if (message.data.action === 'pendo::connect') {
|
|
25943
26214
|
if (!isSameWindowTree(message, this._window))
|
|
25944
26215
|
return;
|
|
26216
|
+
// A third-party widget embedded on the page is a frame below us, so it
|
|
26217
|
+
// passes the tree check and frameConnect would wire its port straight
|
|
26218
|
+
// into the store. Who the sender is, rather than where it sits, is what
|
|
26219
|
+
// keeps that out (APP-165872).
|
|
26220
|
+
if (enforceAllowedOriginServers() && !isTrustedOrigin(message.origin))
|
|
26221
|
+
return;
|
|
25945
26222
|
this._impl.frameConnect(message);
|
|
25946
26223
|
}
|
|
25947
26224
|
if (message.data.action === REMOVE_FRAME_ACTION) {
|
|
@@ -25964,10 +26241,33 @@ _.extend(TopFrameRelay.prototype, {
|
|
|
25964
26241
|
this._impl.broadcast(message);
|
|
25965
26242
|
},
|
|
25966
26243
|
_connectToTop(timeout, retries, timeoutFn) {
|
|
25967
|
-
|
|
25968
|
-
|
|
25969
|
-
|
|
25970
|
-
|
|
26244
|
+
// The port goes out with the connect, so it can never be taken back once an
|
|
26245
|
+
// untrusted top has it. Check who top is against the trusted origins before
|
|
26246
|
+
// handing it over, and address the message to that origin rather than '*'
|
|
26247
|
+
// (APP-165872).
|
|
26248
|
+
//
|
|
26249
|
+
// An application that turned enforcement on gets it everywhere, so a top we
|
|
26250
|
+
// cannot identify is refused rather than trusted. That includes IE, which
|
|
26251
|
+
// has no ancestorOrigins at all: the alternative is a browser-shaped hole in
|
|
26252
|
+
// a control somebody opted into, which is worse than the frame running
|
|
26253
|
+
// standalone there.
|
|
26254
|
+
var enforcing = enforceAllowedOriginServers();
|
|
26255
|
+
var topOrigin = enforcing ? readTopOrigin(this._window) : null;
|
|
26256
|
+
if (!enforcing || (topOrigin && isTrustedOrigin(topOrigin))) {
|
|
26257
|
+
this._impl.connectToTop({
|
|
26258
|
+
action: 'pendo::connect',
|
|
26259
|
+
frameId: this._store.state.frames.frameId
|
|
26260
|
+
}, topOrigin || this._targetOrigin);
|
|
26261
|
+
}
|
|
26262
|
+
else {
|
|
26263
|
+
// nothing reads frames/connectFailed, so without this the frame just
|
|
26264
|
+
// goes quiet after ~3 minutes of retries with nothing to point at
|
|
26265
|
+
log.warn('cross-frame connect refused: top origin is not allowed', {
|
|
26266
|
+
topOrigin: topOrigin || 'unknown'
|
|
26267
|
+
});
|
|
26268
|
+
}
|
|
26269
|
+
// when refused, fall through to the timeout so the frame still gives up
|
|
26270
|
+
// cleanly via setConnectFailed instead of hanging half-connected
|
|
25971
26271
|
setTimeout$1(_.bind(function () {
|
|
25972
26272
|
if (this._connected)
|
|
25973
26273
|
return;
|
|
@@ -26213,23 +26513,6 @@ _.extend(PostMessageImpl.prototype, ImplCommon, {
|
|
|
26213
26513
|
}
|
|
26214
26514
|
});
|
|
26215
26515
|
|
|
26216
|
-
function getLocationOrigin(window) {
|
|
26217
|
-
const strNull = 'null';
|
|
26218
|
-
const origin = window.location.origin;
|
|
26219
|
-
if (origin === strNull && window.parent && window.parent != window) {
|
|
26220
|
-
try {
|
|
26221
|
-
const parentOrigin = window.parent.location.origin;
|
|
26222
|
-
if (parentOrigin && parentOrigin !== strNull) {
|
|
26223
|
-
return parentOrigin;
|
|
26224
|
-
}
|
|
26225
|
-
}
|
|
26226
|
-
catch (e) {
|
|
26227
|
-
// cross-origin ancestor; cannot read top location
|
|
26228
|
-
}
|
|
26229
|
-
}
|
|
26230
|
-
return origin;
|
|
26231
|
-
}
|
|
26232
|
-
|
|
26233
26516
|
function createSingletonMessageHandler(window, isTrustedOriginFn) {
|
|
26234
26517
|
var listeners = [];
|
|
26235
26518
|
var secure = false;
|
|
@@ -29275,7 +29558,8 @@ const PluginAPI = {
|
|
|
29275
29558
|
collectEvent,
|
|
29276
29559
|
hasConsentForAnalyticsEvent,
|
|
29277
29560
|
getTextCollectionMode,
|
|
29278
|
-
isReplayConsentDenied
|
|
29561
|
+
isReplayConsentDenied,
|
|
29562
|
+
isUtmConsentDenied
|
|
29279
29563
|
},
|
|
29280
29564
|
attachEvent,
|
|
29281
29565
|
attachEventInternal,
|
|
@@ -29325,6 +29609,7 @@ const PluginAPI = {
|
|
|
29325
29609
|
q,
|
|
29326
29610
|
SendQueue,
|
|
29327
29611
|
sessionStorage: agentStorage.getSession(),
|
|
29612
|
+
storageIsDisabled,
|
|
29328
29613
|
sizeElements,
|
|
29329
29614
|
store,
|
|
29330
29615
|
Tombstone,
|
|
@@ -29345,7 +29630,8 @@ const PluginAPI = {
|
|
|
29345
29630
|
escapeRegExp,
|
|
29346
29631
|
getZoneSafeMethod(method, target) {
|
|
29347
29632
|
return getZoneSafeMethod$1(_, method, target);
|
|
29348
|
-
}
|
|
29633
|
+
},
|
|
29634
|
+
isNativeCode
|
|
29349
29635
|
},
|
|
29350
29636
|
constants: {
|
|
29351
29637
|
URL_MAX_LENGTH,
|
|
@@ -29478,16 +29764,18 @@ function initIdentityEvents() {
|
|
|
29478
29764
|
}
|
|
29479
29765
|
}
|
|
29480
29766
|
};
|
|
29481
|
-
const
|
|
29482
|
-
if (!isTrackingConsentDenied()
|
|
29483
|
-
|
|
29767
|
+
const resumeIdentity = function (newIdentity) {
|
|
29768
|
+
if (!newIdentity || isTrackingConsentDenied() || !isVisitorDNT()) {
|
|
29769
|
+
return;
|
|
29484
29770
|
}
|
|
29771
|
+
updateOptions(newIdentity);
|
|
29485
29772
|
};
|
|
29486
29773
|
const handleConsentChange = function (event) {
|
|
29487
29774
|
var { identity } = event.data[0];
|
|
29775
|
+
var newIdentity = event.data[1];
|
|
29488
29776
|
if (_.isUndefined(identity))
|
|
29489
29777
|
return;
|
|
29490
|
-
identity ?
|
|
29778
|
+
identity ? resumeIdentity(newIdentity) : clearSession({ doNotTrack: true });
|
|
29491
29779
|
};
|
|
29492
29780
|
Events.identify.on(collectIdentifyEvent);
|
|
29493
29781
|
Events.metadata.on(collectMetadataEvent);
|
|
@@ -29503,6 +29791,62 @@ function initIdentityEvents() {
|
|
|
29503
29791
|
};
|
|
29504
29792
|
}
|
|
29505
29793
|
|
|
29794
|
+
const CONSENT_TIERS = 'cookieConsentTiers';
|
|
29795
|
+
function applyCookieConsent(tiers, identity) {
|
|
29796
|
+
rememberConsentTiers(tiers);
|
|
29797
|
+
const enabledProducts = applyConsentTiers(tiers);
|
|
29798
|
+
Events['consent:changed'].trigger(enabledProducts, identity);
|
|
29799
|
+
log.debug('Cookie consent updated for ' + JSON.stringify(tiers) + '.');
|
|
29800
|
+
return enabledProducts;
|
|
29801
|
+
}
|
|
29802
|
+
function rememberConsentTiers(tiers) {
|
|
29803
|
+
const options = ConfigReader.getLocalConfig() || {};
|
|
29804
|
+
options[CONSENT_TIERS] = tiers;
|
|
29805
|
+
ConfigReader.setLocalConfig(options);
|
|
29806
|
+
}
|
|
29807
|
+
function getCookieConsentTiers() {
|
|
29808
|
+
return ConfigReader.get(CONSENT_TIERS);
|
|
29809
|
+
}
|
|
29810
|
+
function isCurrentCookieConsent(tiers) {
|
|
29811
|
+
return sameConsentTiers(tiers, getCookieConsentTiers());
|
|
29812
|
+
}
|
|
29813
|
+
/**
|
|
29814
|
+
* Enforces the cookie consent policy a visitor has agreed to. The policy is defined by the subscription's cookie consent configuration,
|
|
29815
|
+
* and is used to determine whether each product the web SDK may run.
|
|
29816
|
+
*
|
|
29817
|
+
* A visitor who was not being tracked has no identity for the web SDK to resume, so pass one in
|
|
29818
|
+
* `identity` when the new tiers start allowing identity. It is ignored otherwise.
|
|
29819
|
+
*
|
|
29820
|
+
* @access public
|
|
29821
|
+
* @category Core
|
|
29822
|
+
* @param {string | string[]} tiers tier or tiers the visitor consented to
|
|
29823
|
+
* @param {object} [identity] visitor and account to identify, in the same shape as
|
|
29824
|
+
* `pendo.updateOptions` -- applied only when consent moves a do-not-track visitor to a tier
|
|
29825
|
+
* that permits identity
|
|
29826
|
+
* @returns {object} map of product to its resolved value -- `guides` and `identity` are
|
|
29827
|
+
* booleans (identity disabled means DNT), `textCollection` is `none`/`whitelist`/`all`,
|
|
29828
|
+
* `analytics` and `guideAnalytics` are each an array of allowed event types
|
|
29829
|
+
* @example
|
|
29830
|
+
* pendo.updateCookieConsent('strictlyNecessary');
|
|
29831
|
+
* @example
|
|
29832
|
+
* pendo.updateCookieConsent(['strictlyNecessary', 'functional']);
|
|
29833
|
+
* @example
|
|
29834
|
+
* pendo.updateCookieConsent(['strictlyNecessary', 'functional'], {
|
|
29835
|
+
* visitor: { id: 'put_visitor_id_here', role: 'admin' },
|
|
29836
|
+
* account: { id: 'put_account_id_here' }
|
|
29837
|
+
* });
|
|
29838
|
+
*/
|
|
29839
|
+
function updateCookieConsent(tiers, identity) {
|
|
29840
|
+
store.dispatch('frames/changeCookieConsent', { tiers });
|
|
29841
|
+
return applyCookieConsent(tiers, identity);
|
|
29842
|
+
}
|
|
29843
|
+
function initializeCookieConsent(tiers) {
|
|
29844
|
+
resetConsentPolicy();
|
|
29845
|
+
if (tiers) {
|
|
29846
|
+
applyCookieConsent(tiers);
|
|
29847
|
+
}
|
|
29848
|
+
}
|
|
29849
|
+
|
|
29506
29850
|
/*
|
|
29507
29851
|
* ResourceCenterActivity Event
|
|
29508
29852
|
*
|
|
@@ -29986,6 +30330,8 @@ function initialize(options) {
|
|
|
29986
30330
|
}
|
|
29987
30331
|
// Save the options somewhere
|
|
29988
30332
|
ConfigReader.setLocalConfig(options);
|
|
30333
|
+
const cookieConsentTiers = _.get(options, 'cookieConsentTiers');
|
|
30334
|
+
initializeCookieConsent(cookieConsentTiers);
|
|
29989
30335
|
initDataHost();
|
|
29990
30336
|
exportDataHost(pendo$1);
|
|
29991
30337
|
const cookieDomain = ConfigReader.get('cookieDomain');
|
|
@@ -30177,8 +30523,9 @@ function autoInitialize() {
|
|
|
30177
30523
|
log.info('already running');
|
|
30178
30524
|
return;
|
|
30179
30525
|
}
|
|
30180
|
-
|
|
30181
|
-
|
|
30526
|
+
const snippetOptions = getLegacyGlobalSnippetOptions();
|
|
30527
|
+
if (snippetOptions) { // Support pre-2.0 initialization
|
|
30528
|
+
initialize(snippetOptions);
|
|
30182
30529
|
}
|
|
30183
30530
|
flushCallQueue();
|
|
30184
30531
|
}
|
|
@@ -31232,28 +31579,6 @@ logPublic.setActiveContexts = setActiveContexts;
|
|
|
31232
31579
|
logPublic.showLogHistory = showLogHistory;
|
|
31233
31580
|
logPublic.getLoggedContexts = getLoggedContexts;
|
|
31234
31581
|
|
|
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
31582
|
/**
|
|
31258
31583
|
* Shared helper for event collection
|
|
31259
31584
|
*/
|
|
@@ -34051,20 +34376,6 @@ function GuideStep(guide) {
|
|
|
34051
34376
|
stepId: step.id,
|
|
34052
34377
|
isShown: step.isShown()
|
|
34053
34378
|
});
|
|
34054
|
-
// Android and some other mobile frameworks (Cordova webviews) send a resize event when the keyboard opens on focusing a text input
|
|
34055
|
-
// due to redrawing the guide on resize this ends up closing the keyboard immediately.
|
|
34056
|
-
// We need to ignore resize events in these cases to not redraw yet
|
|
34057
|
-
// This idea comes from the 2nd solution of: https://stackoverflow.com/questions/38296574/disappearing-soft-keyboard-in-android-app
|
|
34058
|
-
if (step.guideElement && step.guideElement[0]) {
|
|
34059
|
-
step.attachEvent(step.guideElement[0], 'focusin', function (e) {
|
|
34060
|
-
if (e.target.tagName === 'TEXTAREA' || e.target.tagName === 'INPUT') {
|
|
34061
|
-
pendo$1.ignoreResize = true;
|
|
34062
|
-
setTimeout$1(function () {
|
|
34063
|
-
pendo$1.ignoreResize = false;
|
|
34064
|
-
}, 1000);
|
|
34065
|
-
}
|
|
34066
|
-
});
|
|
34067
|
-
}
|
|
34068
34379
|
};
|
|
34069
34380
|
/**
|
|
34070
34381
|
* Hides the Step if shown. Use an argument of an Object containing a
|
|
@@ -34291,7 +34602,7 @@ const ErrorLogModule = (function () {
|
|
|
34291
34602
|
}
|
|
34292
34603
|
const errMsg = `[${logEvent}]`;
|
|
34293
34604
|
if (logEvent.level === LogLevel.Critical) {
|
|
34294
|
-
const options =
|
|
34605
|
+
const options = getLegacyGlobalSnippetOptions() || {};
|
|
34295
34606
|
logError(errMsg);
|
|
34296
34607
|
if (pendo$1.logStackTraces !== false && options.logStackTraces !== false && error.stack) {
|
|
34297
34608
|
writeErrorPOST(`${errMsg}\n${error.stack}`);
|
|
@@ -35029,6 +35340,22 @@ var FramesModule = (function () {
|
|
|
35029
35340
|
return;
|
|
35030
35341
|
identifyVisitor(payload.visitorId, payload.accountId);
|
|
35031
35342
|
},
|
|
35343
|
+
changeCookieConsent(context, payload) {
|
|
35344
|
+
crossFrameChannel.postMessage({
|
|
35345
|
+
action: 'frames/receiveCookieConsent',
|
|
35346
|
+
frameId: context.state.frameId,
|
|
35347
|
+
tiers: payload.tiers
|
|
35348
|
+
});
|
|
35349
|
+
},
|
|
35350
|
+
receiveCookieConsent(context, payload) {
|
|
35351
|
+
if (context.getters.getMyApiKey() !== payload.apiKey)
|
|
35352
|
+
return;
|
|
35353
|
+
if (context.state.frameId === payload.frameId)
|
|
35354
|
+
return;
|
|
35355
|
+
if (isCurrentCookieConsent(payload.tiers))
|
|
35356
|
+
return;
|
|
35357
|
+
applyCookieConsent(payload.tiers);
|
|
35358
|
+
},
|
|
35032
35359
|
setReady(context, payload) {
|
|
35033
35360
|
context.commit('setReady', payload.isReady);
|
|
35034
35361
|
},
|
|
@@ -39010,6 +39337,7 @@ const IFrameMonitor = (function () {
|
|
|
39010
39337
|
let pendoGlobal;
|
|
39011
39338
|
let pluginAPI;
|
|
39012
39339
|
let topDownSync;
|
|
39340
|
+
let identitySync;
|
|
39013
39341
|
return {
|
|
39014
39342
|
name: 'IFrameMonitor',
|
|
39015
39343
|
initialize: init,
|
|
@@ -39025,7 +39353,12 @@ const IFrameMonitor = (function () {
|
|
|
39025
39353
|
removeHandlers();
|
|
39026
39354
|
}
|
|
39027
39355
|
function onChildFrameJoined() {
|
|
39028
|
-
|
|
39356
|
+
// Consent first. A frame applies these in the order they arrive, and one whose policy
|
|
39357
|
+
// still denies tracking will throw away the identity sent below.
|
|
39358
|
+
syncCookieConsent();
|
|
39359
|
+
if (identitySync) {
|
|
39360
|
+
syncIdentity();
|
|
39361
|
+
}
|
|
39029
39362
|
}
|
|
39030
39363
|
function getAgentUrl() {
|
|
39031
39364
|
const selfHostedUrl = pluginAPI.ConfigReader.get('selfHostedAgentUrl');
|
|
@@ -39128,14 +39461,23 @@ const IFrameMonitor = (function () {
|
|
|
39128
39461
|
return;
|
|
39129
39462
|
pluginAPI.store.dispatch('frames/syncIdentity', { visitorId, accountId });
|
|
39130
39463
|
}
|
|
39464
|
+
function syncCookieConsent() {
|
|
39465
|
+
const tiers = getCookieConsentTiers();
|
|
39466
|
+
if (!tiers)
|
|
39467
|
+
return; // nothing to replay for a subscription that never configured consent
|
|
39468
|
+
pluginAPI.store.dispatch('frames/changeCookieConsent', { tiers });
|
|
39469
|
+
}
|
|
39131
39470
|
function monitorFrames() {
|
|
39132
39471
|
if (pluginAPI.ConfigReader.get('autoFrameInstall')) {
|
|
39133
39472
|
pluginAPI.Events.ready.on(readyHandler);
|
|
39134
39473
|
}
|
|
39135
|
-
|
|
39474
|
+
identitySync = pluginAPI.ConfigReader.get('frameIdentitySync');
|
|
39475
|
+
// A frame that installs itself misses consent changes made before it joined, whether or
|
|
39476
|
+
// not identity is being shared with it.
|
|
39477
|
+
pluginAPI.Events.childFrameJoined.on(onChildFrameJoined);
|
|
39478
|
+
if (identitySync) {
|
|
39136
39479
|
pluginAPI.Events.metadata.on(metadataHandler);
|
|
39137
39480
|
pluginAPI.Events.identify.on(identifyHandler);
|
|
39138
|
-
pluginAPI.Events.childFrameJoined.on(onChildFrameJoined);
|
|
39139
39481
|
}
|
|
39140
39482
|
}
|
|
39141
39483
|
function removeHandlers() {
|
|
@@ -42300,7 +42642,8 @@ class WebAnalytics {
|
|
|
42300
42642
|
const { bind } = pendo._;
|
|
42301
42643
|
this.subscriptions = [
|
|
42302
42644
|
PluginAPI.attachEvent(PluginAPI.Events, 'eventCaptured', bind(this.addUtmToEvent, this)),
|
|
42303
|
-
PluginAPI.attachEvent(PluginAPI.Events, 'sessionChanged', bind(this.sessionChanged, this))
|
|
42645
|
+
PluginAPI.attachEvent(PluginAPI.Events, 'sessionChanged', bind(this.sessionChanged, this)),
|
|
42646
|
+
PluginAPI.attachEvent(PluginAPI.Events, 'consent:changed', bind(this.handleConsentChange, this))
|
|
42304
42647
|
];
|
|
42305
42648
|
this.suffix = this.api.ConfigReader.get('identityStorageSuffix');
|
|
42306
42649
|
/**
|
|
@@ -42312,11 +42655,19 @@ class WebAnalytics {
|
|
|
42312
42655
|
* @label WEB_ANALYTICS_STORAGE_KEY
|
|
42313
42656
|
*/
|
|
42314
42657
|
this.api.agentStorage.registry.addLocal(WEB_ANALYTICS_STORAGE_KEY, { cookieSuffix: this.suffix, isSecure: true });
|
|
42315
|
-
|
|
42316
|
-
|
|
42658
|
+
if (PluginAPI.analytics.isUtmConsentDenied()) {
|
|
42659
|
+
return this.clearParameters(PluginAPI.agentStorage);
|
|
42660
|
+
}
|
|
42661
|
+
this.captureParameters();
|
|
42662
|
+
}
|
|
42663
|
+
captureParameters() {
|
|
42664
|
+
if (this.api.analytics.isUtmConsentDenied())
|
|
42665
|
+
return;
|
|
42666
|
+
let params = this.loadParameters(this.api.agentStorage);
|
|
42667
|
+
if (!params || this.pendo._.isEmpty(params)) {
|
|
42317
42668
|
params = this.extractParameters(window.location.href, document.referrer);
|
|
42318
42669
|
}
|
|
42319
|
-
this.storeParameters(params,
|
|
42670
|
+
this.storeParameters(params, this.api.agentStorage);
|
|
42320
42671
|
}
|
|
42321
42672
|
extractParameters(url, referrer) {
|
|
42322
42673
|
const locationUrl = new URL(url);
|
|
@@ -42372,10 +42723,16 @@ class WebAnalytics {
|
|
|
42372
42723
|
}
|
|
42373
42724
|
}
|
|
42374
42725
|
storeParameters(params, storage) {
|
|
42726
|
+
if (this.api.analytics.isUtmConsentDenied())
|
|
42727
|
+
return;
|
|
42375
42728
|
if (this.pendo._.size(params) > 0) {
|
|
42376
42729
|
storage.write(WEB_ANALYTICS_STORAGE_KEY, JSON.stringify(params));
|
|
42377
42730
|
}
|
|
42378
42731
|
}
|
|
42732
|
+
clearParameters(storage) {
|
|
42733
|
+
storage.clear(WEB_ANALYTICS_STORAGE_KEY);
|
|
42734
|
+
storage.clear(WEB_ANALYTICS_STORAGE_KEY, false, '');
|
|
42735
|
+
}
|
|
42379
42736
|
loadParameters(storage) {
|
|
42380
42737
|
const storedParamsJson = storage.read(WEB_ANALYTICS_STORAGE_KEY) ||
|
|
42381
42738
|
storage.read(WEB_ANALYTICS_STORAGE_KEY, false, '');
|
|
@@ -42390,6 +42747,8 @@ class WebAnalytics {
|
|
|
42390
42747
|
this.subscriptions.length = 0;
|
|
42391
42748
|
}
|
|
42392
42749
|
addUtmToEvent(event) {
|
|
42750
|
+
if (this.api.analytics.isUtmConsentDenied())
|
|
42751
|
+
return;
|
|
42393
42752
|
let utm;
|
|
42394
42753
|
try {
|
|
42395
42754
|
utm = this.loadParameters(this.api.agentStorage);
|
|
@@ -42403,11 +42762,19 @@ class WebAnalytics {
|
|
|
42403
42762
|
}
|
|
42404
42763
|
sessionChanged() {
|
|
42405
42764
|
const agentStorage = this.api.agentStorage;
|
|
42406
|
-
|
|
42407
|
-
agentStorage.clear(WEB_ANALYTICS_STORAGE_KEY, false, '');
|
|
42765
|
+
this.clearParameters(agentStorage);
|
|
42408
42766
|
const params = this.extractParameters(window.location.href, document.referrer);
|
|
42409
42767
|
this.storeParameters(params, agentStorage);
|
|
42410
42768
|
}
|
|
42769
|
+
handleConsentChange(event) {
|
|
42770
|
+
const { utm } = event.data[0];
|
|
42771
|
+
if (this.pendo._.isUndefined(utm))
|
|
42772
|
+
return;
|
|
42773
|
+
if (this.api.analytics.isUtmConsentDenied()) {
|
|
42774
|
+
return this.clearParameters(this.api.agentStorage);
|
|
42775
|
+
}
|
|
42776
|
+
this.captureParameters();
|
|
42777
|
+
}
|
|
42411
42778
|
}
|
|
42412
42779
|
var WebAnalytics$1 = new WebAnalytics();
|
|
42413
42780
|
|
|
@@ -42735,6 +43102,310 @@ var guideMarkdownUtil = {
|
|
|
42735
43102
|
removeMarkdownSyntax
|
|
42736
43103
|
};
|
|
42737
43104
|
|
|
43105
|
+
const AUTO_ADVANCE_DELAY_MS = 250;
|
|
43106
|
+
function isAutoAdvanceRequested(step, _) {
|
|
43107
|
+
return _.get(step, 'attributes.autoAdvance') === true;
|
|
43108
|
+
}
|
|
43109
|
+
// Survey-authored DOM carries no question-type classes, so eligibility keys off
|
|
43110
|
+
// answer-control shape: every required poll id must be radio-backed, which also
|
|
43111
|
+
// refuses a future non-radio type by default.
|
|
43112
|
+
function isStepEligibleInDom(step, _, requiredPollIds) {
|
|
43113
|
+
const guideElement = step && step.guideElement;
|
|
43114
|
+
if (!guideElement)
|
|
43115
|
+
return false;
|
|
43116
|
+
if (guideElement.find('textarea, input:text, input:checkbox, select').length)
|
|
43117
|
+
return false;
|
|
43118
|
+
const pollIds = _.uniq(_.map(guideElement.find('[data-pendo-poll-id]'), (element) => element.getAttribute('data-pendo-poll-id')));
|
|
43119
|
+
if (!pollIds.length)
|
|
43120
|
+
return false;
|
|
43121
|
+
return _.all(pollIds, (pollId) => _.contains(requiredPollIds, pollId) &&
|
|
43122
|
+
guideElement.find(`input:radio[data-pendo-poll-id="${pollId}"]`).length > 0);
|
|
43123
|
+
}
|
|
43124
|
+
function hideSubmitButtons(step, _, submitButtons) {
|
|
43125
|
+
const hiddenButtons = [];
|
|
43126
|
+
_.each(submitButtons, (button) => {
|
|
43127
|
+
const element = step.guideElement.find(`#${button.props.id}`)[0];
|
|
43128
|
+
if (!element)
|
|
43129
|
+
return;
|
|
43130
|
+
// display, not visibility: a reserved-but-invisible box leaves an empty gap
|
|
43131
|
+
// at the foot of the step. Authored styles reach the button inline
|
|
43132
|
+
// (build.js:151), so the original value is kept rather than blanked.
|
|
43133
|
+
hiddenButtons.push({ element, display: element.style.display });
|
|
43134
|
+
element.style.display = 'none';
|
|
43135
|
+
});
|
|
43136
|
+
return function restoreSubmitButtons() {
|
|
43137
|
+
_.each(hiddenButtons, (hiddenButton) => {
|
|
43138
|
+
hiddenButton.element.style.display = hiddenButton.display;
|
|
43139
|
+
});
|
|
43140
|
+
};
|
|
43141
|
+
}
|
|
43142
|
+
// The container's pixel height is measured during render (layout.js) before custom
|
|
43143
|
+
// scripts run, so collapsing a row afterwards leaves the box stale and the step keeps
|
|
43144
|
+
// the empty space. Same reflow pair metadata-substitution uses, scoped to this step so
|
|
43145
|
+
// a second guide on screen is left alone.
|
|
43146
|
+
function reflowGuide(step, _, pendo) {
|
|
43147
|
+
const recalculateGuideHeight = _.get(pendo, 'BuildingBlocks.BuildingBlockGuides.recalculateGuideHeight');
|
|
43148
|
+
try {
|
|
43149
|
+
if (_.isFunction(pendo.flexElement)) {
|
|
43150
|
+
pendo.flexElement(step.guideElement.find(guideMarkdownUtil.containerSelector));
|
|
43151
|
+
}
|
|
43152
|
+
if (_.isFunction(recalculateGuideHeight)) {
|
|
43153
|
+
recalculateGuideHeight(`pendo-g-${step.id}`, step.guideElement);
|
|
43154
|
+
}
|
|
43155
|
+
}
|
|
43156
|
+
catch (error) {
|
|
43157
|
+
pendo.log('[auto-advance] guide reflow failed, the step may keep an empty row', error && error.message);
|
|
43158
|
+
}
|
|
43159
|
+
}
|
|
43160
|
+
function createInertController() {
|
|
43161
|
+
return { isActive: false, notifyAnswered() { }, cancel() { } };
|
|
43162
|
+
}
|
|
43163
|
+
function createAutoAdvanceController(step, pendo, submitButtons, requiredPollIds) {
|
|
43164
|
+
const _ = pendo._;
|
|
43165
|
+
const inert = createInertController();
|
|
43166
|
+
if (!isAutoAdvanceRequested(step, _))
|
|
43167
|
+
return inert;
|
|
43168
|
+
// Only reachable for API-authored guides; choosing between two
|
|
43169
|
+
// submitPollAndGoToStep targets would be a guess.
|
|
43170
|
+
if (_.size(submitButtons) !== 1) {
|
|
43171
|
+
pendo.log(`[auto-advance] not arming: step ${step.id} needs exactly one submit button, found ${_.size(submitButtons)}`);
|
|
43172
|
+
return inert;
|
|
43173
|
+
}
|
|
43174
|
+
if (!isStepEligibleInDom(step, _, requiredPollIds)) {
|
|
43175
|
+
pendo.log(`[auto-advance] not arming: rendered step ${step.id} failed the eligibility re-check`);
|
|
43176
|
+
return inert;
|
|
43177
|
+
}
|
|
43178
|
+
const submitButton = submitButtons[0];
|
|
43179
|
+
// A click binds every action on the node and dispatches one event each
|
|
43180
|
+
// (build.js:205-207); we dispatch one, so a second action would be dropped.
|
|
43181
|
+
if (_.size(submitButton.actions) !== 1) {
|
|
43182
|
+
pendo.log(`[auto-advance] not arming: step ${step.id} submit button carries ${_.size(submitButton.actions)} actions, expected one`);
|
|
43183
|
+
return inert;
|
|
43184
|
+
}
|
|
43185
|
+
const submitAction = _.find(submitButton.actions, (action) => action.action === 'submitPoll' || action.action === 'submitPollAndGoToStep');
|
|
43186
|
+
if (!submitAction)
|
|
43187
|
+
return inert;
|
|
43188
|
+
let hasFired = false;
|
|
43189
|
+
let restoreSubmitButtons = _.noop;
|
|
43190
|
+
function fire() {
|
|
43191
|
+
if (hasFired)
|
|
43192
|
+
return;
|
|
43193
|
+
hasFired = true;
|
|
43194
|
+
try {
|
|
43195
|
+
// Shape-identical to what bindActionToNode emits for a click, so
|
|
43196
|
+
// beforeAdvance consumers intercept an auto-advance the same way.
|
|
43197
|
+
step.eventRouter.eventable.trigger('pendoEvent', {
|
|
43198
|
+
action: submitAction.action,
|
|
43199
|
+
params: submitAction.parameters,
|
|
43200
|
+
step,
|
|
43201
|
+
ignore: !!submitAction.ignore,
|
|
43202
|
+
srcElement: step.guideElement.find(`#${submitButton.props.id}`)[0]
|
|
43203
|
+
});
|
|
43204
|
+
}
|
|
43205
|
+
catch (error) {
|
|
43206
|
+
restoreSubmitButtons();
|
|
43207
|
+
reflowGuide(step, _, pendo);
|
|
43208
|
+
pendo.log('[auto-advance] submit dispatch failed, restored the submit button', error && error.message);
|
|
43209
|
+
}
|
|
43210
|
+
}
|
|
43211
|
+
const debouncedFire = _.debounce(fire, AUTO_ADVANCE_DELAY_MS);
|
|
43212
|
+
// Last, so nothing between here and the return can throw and leave the step
|
|
43213
|
+
// with a hidden button and no controller to un-hide it.
|
|
43214
|
+
restoreSubmitButtons = hideSubmitButtons(step, _, submitButtons);
|
|
43215
|
+
reflowGuide(step, _, pendo);
|
|
43216
|
+
return {
|
|
43217
|
+
isActive: true,
|
|
43218
|
+
notifyAnswered: debouncedFire,
|
|
43219
|
+
cancel: debouncedFire.cancel
|
|
43220
|
+
};
|
|
43221
|
+
}
|
|
43222
|
+
|
|
43223
|
+
const SURVEY_ANSWERS_KEY = 'pendo_survey_answers';
|
|
43224
|
+
const ANSWER_CONTROLS = 'input:radio, input:text, textarea, select';
|
|
43225
|
+
const TEXT_CONTROLS = 'input:text, textarea';
|
|
43226
|
+
// Matches MULTISTEP_CONTINUATION_TIME_LIMIT (walkthrough-guide.js:67), past which a guide is not continued.
|
|
43227
|
+
const ANSWER_TTL_MS = 12 * 60 * 60 * 1000;
|
|
43228
|
+
let pluginApi = null;
|
|
43229
|
+
let inMemoryAnswers = {};
|
|
43230
|
+
let persistedAnswersDropped = false;
|
|
43231
|
+
// A restricted storage.allowKeys silently rejects the session write, so mirror the visit here.
|
|
43232
|
+
const visitedSteps = {};
|
|
43233
|
+
function setPluginApi(api) {
|
|
43234
|
+
pluginApi = api;
|
|
43235
|
+
if (!api || !api.agentStorage)
|
|
43236
|
+
return;
|
|
43237
|
+
/**
|
|
43238
|
+
* Answers the visitor has given on each step of a survey, so a step re-shown
|
|
43239
|
+
* after Previous comes back filled in rather than blank.
|
|
43240
|
+
*
|
|
43241
|
+
* @name pendo_survey_answers
|
|
43242
|
+
* @category Cookies/sessionStorage
|
|
43243
|
+
* @access public
|
|
43244
|
+
* @label SURVEY_ANSWERS_KEY
|
|
43245
|
+
*/
|
|
43246
|
+
api.agentStorage.registry.addSession(SURVEY_ANSWERS_KEY);
|
|
43247
|
+
}
|
|
43248
|
+
// getSession() checks the allow-list but not storageIsDisabled, which the cookie path applies.
|
|
43249
|
+
function persistenceAllowed() {
|
|
43250
|
+
const allowed = !!pluginApi &&
|
|
43251
|
+
!!pluginApi.sessionStorage &&
|
|
43252
|
+
typeof pluginApi.storageIsDisabled === 'function' &&
|
|
43253
|
+
!pluginApi.storageIsDisabled();
|
|
43254
|
+
if (allowed) {
|
|
43255
|
+
persistedAnswersDropped = false;
|
|
43256
|
+
}
|
|
43257
|
+
else {
|
|
43258
|
+
dropPersistedAnswers();
|
|
43259
|
+
}
|
|
43260
|
+
return allowed;
|
|
43261
|
+
}
|
|
43262
|
+
// agentStorage.clearAll only walks local keys, so a session key has to be dropped here.
|
|
43263
|
+
function dropPersistedAnswers() {
|
|
43264
|
+
if (persistedAnswersDropped || !pluginApi || !pluginApi.sessionStorage)
|
|
43265
|
+
return;
|
|
43266
|
+
persistedAnswersDropped = true;
|
|
43267
|
+
pluginApi.sessionStorage.removeItem(SURVEY_ANSWERS_KEY);
|
|
43268
|
+
}
|
|
43269
|
+
function readAll() {
|
|
43270
|
+
if (!persistenceAllowed())
|
|
43271
|
+
return inMemoryAnswers;
|
|
43272
|
+
try {
|
|
43273
|
+
return JSON.parse(pluginApi.sessionStorage.getItem(SURVEY_ANSWERS_KEY)) || {};
|
|
43274
|
+
}
|
|
43275
|
+
catch (error) {
|
|
43276
|
+
return {};
|
|
43277
|
+
}
|
|
43278
|
+
}
|
|
43279
|
+
function writeAll(answers) {
|
|
43280
|
+
if (!persistenceAllowed()) {
|
|
43281
|
+
inMemoryAnswers = answers;
|
|
43282
|
+
return;
|
|
43283
|
+
}
|
|
43284
|
+
pluginApi.sessionStorage.setItem(SURVEY_ANSWERS_KEY, JSON.stringify(answers));
|
|
43285
|
+
}
|
|
43286
|
+
// identify() sets the new id before the reload tears the step down, so resolve this once per render.
|
|
43287
|
+
function visitorKey(pendo) {
|
|
43288
|
+
return (pendo._.isFunction(pendo.get_visitor_id) && pendo.get_visitor_id()) || 'unknown';
|
|
43289
|
+
}
|
|
43290
|
+
function stateKey(step, guide, visitor) {
|
|
43291
|
+
return `${visitor}:${guide.id}:${step.id}`;
|
|
43292
|
+
}
|
|
43293
|
+
// Host pages reassign Date.now to return a Date, which JSON writes out as a string.
|
|
43294
|
+
function getNow() {
|
|
43295
|
+
return new Date().getTime();
|
|
43296
|
+
}
|
|
43297
|
+
function isFresh(writtenAt) {
|
|
43298
|
+
return !!writtenAt && (getNow() - writtenAt) <= ANSWER_TTL_MS;
|
|
43299
|
+
}
|
|
43300
|
+
function readRecord(step, guide, visitor) {
|
|
43301
|
+
const forVisitor = readAll()[visitor];
|
|
43302
|
+
const forGuide = forVisitor && forVisitor[guide.id];
|
|
43303
|
+
const record = (forGuide && forGuide[step.id]) || null;
|
|
43304
|
+
return isFresh(record && record.at) ? record : null;
|
|
43305
|
+
}
|
|
43306
|
+
function updateRecord(step, guide, visitor, changes) {
|
|
43307
|
+
const all = readAll();
|
|
43308
|
+
const current = readRecord(step, guide, visitor) || {};
|
|
43309
|
+
if (!all[visitor])
|
|
43310
|
+
all[visitor] = {};
|
|
43311
|
+
if (!all[visitor][guide.id])
|
|
43312
|
+
all[visitor][guide.id] = {};
|
|
43313
|
+
all[visitor][guide.id][step.id] = {
|
|
43314
|
+
at: getNow(),
|
|
43315
|
+
visited: changes.visited || current.visited || 0,
|
|
43316
|
+
answers: changes.answers || current.answers || {}
|
|
43317
|
+
};
|
|
43318
|
+
writeAll(all);
|
|
43319
|
+
}
|
|
43320
|
+
// Previous only renders after an auto-advance step, so any other guide needs none of this.
|
|
43321
|
+
function guideAutoAdvances(guide, _) {
|
|
43322
|
+
return _.any(_.get(guide, 'steps', []), (step) => isAutoAdvanceRequested(step, _));
|
|
43323
|
+
}
|
|
43324
|
+
function pollIdFor(control) {
|
|
43325
|
+
let node = control;
|
|
43326
|
+
while (node && node.getAttribute) {
|
|
43327
|
+
const pollId = node.getAttribute('data-pendo-poll-id');
|
|
43328
|
+
if (pollId)
|
|
43329
|
+
return pollId;
|
|
43330
|
+
node = node.parentNode;
|
|
43331
|
+
}
|
|
43332
|
+
return null;
|
|
43333
|
+
}
|
|
43334
|
+
// submitPoll never reads a checkbox back (event-router.js:264), so restoring one shows nothing sent.
|
|
43335
|
+
function isRadio(control) {
|
|
43336
|
+
return control.type === 'radio';
|
|
43337
|
+
}
|
|
43338
|
+
function collectAnswers(step, pendo) {
|
|
43339
|
+
const answers = {};
|
|
43340
|
+
pendo._.each(step.guideElement.find(ANSWER_CONTROLS), (control) => {
|
|
43341
|
+
const pollId = pollIdFor(control);
|
|
43342
|
+
// One value per poll id, first in document order, as submitPoll reads it.
|
|
43343
|
+
if (!pollId || answers[pollId])
|
|
43344
|
+
return;
|
|
43345
|
+
if (isRadio(control) && !control.checked)
|
|
43346
|
+
return;
|
|
43347
|
+
if (!control.value)
|
|
43348
|
+
return;
|
|
43349
|
+
answers[pollId] = [control.value];
|
|
43350
|
+
});
|
|
43351
|
+
return answers;
|
|
43352
|
+
}
|
|
43353
|
+
// No event dispatched: a restored answer must not reach the gate listeners or re-arm auto-advance.
|
|
43354
|
+
function restoreAnswers(step, pendo, answers) {
|
|
43355
|
+
pendo._.each(step.guideElement.find(ANSWER_CONTROLS), (control) => {
|
|
43356
|
+
const pollId = pollIdFor(control);
|
|
43357
|
+
const values = pollId && answers[pollId];
|
|
43358
|
+
if (!values)
|
|
43359
|
+
return;
|
|
43360
|
+
if (isRadio(control)) {
|
|
43361
|
+
if (pendo._.contains(values, control.value))
|
|
43362
|
+
control.checked = true;
|
|
43363
|
+
return;
|
|
43364
|
+
}
|
|
43365
|
+
control.value = values[0];
|
|
43366
|
+
});
|
|
43367
|
+
}
|
|
43368
|
+
function isRevisit(step, guide, pendo) {
|
|
43369
|
+
if (!guideAutoAdvances(guide, pendo._))
|
|
43370
|
+
return false;
|
|
43371
|
+
const visitor = visitorKey(pendo);
|
|
43372
|
+
if (isFresh(visitedSteps[stateKey(step, guide, visitor)]))
|
|
43373
|
+
return true;
|
|
43374
|
+
const record = readRecord(step, guide, visitor);
|
|
43375
|
+
return !!record && (!!record.visited || !pendo._.isEmpty(record.answers));
|
|
43376
|
+
}
|
|
43377
|
+
const AnswerCache = {
|
|
43378
|
+
name: 'AnswerCache',
|
|
43379
|
+
script(step, guide, pendo) {
|
|
43380
|
+
const visitor = visitorKey(pendo);
|
|
43381
|
+
const record = readRecord(step, guide, visitor);
|
|
43382
|
+
restoreAnswers(step, pendo, (record && record.answers) || {});
|
|
43383
|
+
// Marked on leaving, not on render: sessionStorage survives a reload, which is not a revisit.
|
|
43384
|
+
this.on('beforeUnmount', (event) => {
|
|
43385
|
+
if (event && event.reason === 'hidden')
|
|
43386
|
+
return;
|
|
43387
|
+
visitedSteps[stateKey(step, guide, visitor)] = getNow();
|
|
43388
|
+
updateRecord(step, guide, visitor, { visited: 1 });
|
|
43389
|
+
});
|
|
43390
|
+
const stepRoot = step.guideElement[0];
|
|
43391
|
+
// change does not bubble on legacy browsers; it is here only for keyboard selection.
|
|
43392
|
+
step.attachEvent(stepRoot, 'click', capture);
|
|
43393
|
+
step.attachEvent(stepRoot, 'change', capture);
|
|
43394
|
+
pendo._.each(step.guideElement.find(TEXT_CONTROLS), (control) => {
|
|
43395
|
+
step.attachEvent(control, 'input', capture);
|
|
43396
|
+
});
|
|
43397
|
+
function capture() {
|
|
43398
|
+
updateRecord(step, guide, visitor, { answers: collectAnswers(step, pendo) });
|
|
43399
|
+
}
|
|
43400
|
+
},
|
|
43401
|
+
test(step, guide, pendo) {
|
|
43402
|
+
const guideElement = step && step.guideElement;
|
|
43403
|
+
return !!guideElement &&
|
|
43404
|
+
guideAutoAdvances(guide, pendo._) &&
|
|
43405
|
+
guideElement.find(ANSWER_CONTROLS).length > 0;
|
|
43406
|
+
}
|
|
43407
|
+
};
|
|
43408
|
+
|
|
42738
43409
|
function getZoneSafeMethod(_, method, target = window) {
|
|
42739
43410
|
var zoneSymbol = '__symbol__';
|
|
42740
43411
|
/* global Zone */
|
|
@@ -43211,124 +43882,6 @@ function substitutionIcon(color, size) {
|
|
|
43211
43882
|
13.7239 15.5 14V17Z" fill="${color}"></path> </g></svg></div>`);
|
|
43212
43883
|
}
|
|
43213
43884
|
|
|
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
43885
|
const requiredElement = '<span class="_pendo-required-indicator" style="color:red; font-style:italic;" title="Question is required"> *</span>';
|
|
43333
43886
|
const requiredSyntax = '{required/}';
|
|
43334
43887
|
const RequiredQuestions = {
|
|
@@ -43354,9 +43907,14 @@ const RequiredQuestions = {
|
|
|
43354
43907
|
}
|
|
43355
43908
|
});
|
|
43356
43909
|
}
|
|
43910
|
+
// Restored answers reach no listener, and the controller is still inert here.
|
|
43911
|
+
const revisited = isRevisit(step, guide, pendo);
|
|
43912
|
+
if (revisited) {
|
|
43913
|
+
evaluateRequiredQuestions(requiredQuestions);
|
|
43914
|
+
}
|
|
43357
43915
|
// Armed last, after the listeners above: runCustomScripts abandons the rest of
|
|
43358
43916
|
// this script on a throw, and the submit button is already disabled by now.
|
|
43359
|
-
if (isAutoAdvanceRequested(step, pendo._)) {
|
|
43917
|
+
if (isAutoAdvanceRequested(step, pendo._) && !revisited) {
|
|
43360
43918
|
this.on('beforeUnmount', () => autoAdvance.cancel());
|
|
43361
43919
|
try {
|
|
43362
43920
|
autoAdvance = createAutoAdvanceController(step, pendo, submitButtons, requiredPollIds);
|
|
@@ -43707,7 +44265,9 @@ function GuideMarkdown() {
|
|
|
43707
44265
|
const markdownScriptsEnabled = configReader.get(GUIDEMARKDOWN_CONFIG);
|
|
43708
44266
|
if (!markdownScriptsEnabled)
|
|
43709
44267
|
return;
|
|
43710
|
-
|
|
44268
|
+
setPluginApi(PluginAPI);
|
|
44269
|
+
// AnswerCache first: it restores the answers RequiredQuestions reads the gate against.
|
|
44270
|
+
guideMarkdown = [AnswerCache, PollBranching, MetadataSubstitution, RequiredQuestions, SkipToEligibleStep];
|
|
43711
44271
|
if (!pendo.designer) {
|
|
43712
44272
|
pendo._.each(guideMarkdown, function (script) {
|
|
43713
44273
|
PluginAPI.GlobalRuntime.addGlobalScript(script);
|
|
@@ -51498,12 +52058,15 @@ class Transport {
|
|
|
51498
52058
|
}
|
|
51499
52059
|
}
|
|
51500
52060
|
|
|
51501
|
-
const CATCHALL_TYPES = ['load', 'click', 'track'];
|
|
51502
52061
|
const EVENT_KINDS = {
|
|
51503
|
-
|
|
51504
|
-
|
|
51505
|
-
|
|
51506
|
-
|
|
52062
|
+
AGENTIC: 'agentic',
|
|
52063
|
+
CATCHALL: 'catchall',
|
|
52064
|
+
DEVLOG: 'devlog',
|
|
52065
|
+
RECORDING: 'recording'
|
|
52066
|
+
};
|
|
52067
|
+
const CATCHALL_TYPES = ['load', 'click', 'track', 'guideSeen'];
|
|
52068
|
+
const AGENTIC_TYPES = ["prompt", "agent_response", "user_reaction"];
|
|
52069
|
+
const CONSENT_EXEMPT_TYPES = ['guideSeen', EVENT_KINDS.DEVLOG];
|
|
51507
52070
|
class LiveReplayClient {
|
|
51508
52071
|
constructor(pendo, api) {
|
|
51509
52072
|
this.pendo = pendo;
|
|
@@ -51511,6 +52074,7 @@ class LiveReplayClient {
|
|
|
51511
52074
|
this.ws = null;
|
|
51512
52075
|
this.sequence = 0;
|
|
51513
52076
|
this.unsubscribeEventCaptured = null;
|
|
52077
|
+
this.disabled = false;
|
|
51514
52078
|
}
|
|
51515
52079
|
buildWsUrl() {
|
|
51516
52080
|
const url = new URL$1(`${this.pendo.HOST}/data/live-replay/${this.pendo.apiKey}`);
|
|
@@ -51523,11 +52087,18 @@ class LiveReplayClient {
|
|
|
51523
52087
|
return url.href;
|
|
51524
52088
|
}
|
|
51525
52089
|
start(visitorId, onOpen) {
|
|
51526
|
-
|
|
52090
|
+
const wsIsOpen = this.ws && this.ws.readyState !== WebSocket.CLOSED;
|
|
52091
|
+
if (wsIsOpen || this.disabled)
|
|
51527
52092
|
return;
|
|
51528
52093
|
this.sequence = 0;
|
|
51529
52094
|
this.unsubscribeEventCaptured = this.api.attachEvent(this.api.Events, 'eventCaptured', this.pendo._.bind(this.forwardEvent, this));
|
|
51530
52095
|
this.ws = new WebSocket(this.buildWsUrl());
|
|
52096
|
+
if (!this.api.util.isNativeCode(this.ws.send)) {
|
|
52097
|
+
this.disabled = true;
|
|
52098
|
+
this.api.log.warn('Live replay disabled: WebSocket send is not native.');
|
|
52099
|
+
this.stop();
|
|
52100
|
+
return;
|
|
52101
|
+
}
|
|
51531
52102
|
this.ws.onopen = this.pendo._.bind(this.handleOpen, this, visitorId, onOpen);
|
|
51532
52103
|
const boundHandleClose = this.pendo._.bind(this.handleClose, this);
|
|
51533
52104
|
this.ws.onclose = boundHandleClose;
|
|
@@ -51583,21 +52154,31 @@ class LiveReplayClient {
|
|
|
51583
52154
|
catch (_a) { }
|
|
51584
52155
|
}
|
|
51585
52156
|
sendRecordingEvent(fields) {
|
|
51586
|
-
this.send(Object.assign(Object.assign({}, fields), { kind: EVENT_KINDS.
|
|
52157
|
+
this.send(Object.assign(Object.assign({}, fields), { kind: EVENT_KINDS.RECORDING, sequence: this.sequence++ }));
|
|
52158
|
+
}
|
|
52159
|
+
hasConsentToForward(type) {
|
|
52160
|
+
if (this.pendo._.contains(CONSENT_EXEMPT_TYPES, type))
|
|
52161
|
+
return true;
|
|
52162
|
+
return this.api.analytics.hasConsentForAnalyticsEvent(type);
|
|
51587
52163
|
}
|
|
51588
52164
|
forwardEvent(event) {
|
|
51589
52165
|
if (!event || !event.data || !event.data.length)
|
|
51590
52166
|
return;
|
|
51591
52167
|
const capturedEvent = event.data[0];
|
|
51592
52168
|
const config = { event: capturedEvent };
|
|
52169
|
+
if (this.pendo._.contains(AGENTIC_TYPES, capturedEvent.type)) {
|
|
52170
|
+
config.kind = EVENT_KINDS.AGENTIC;
|
|
52171
|
+
}
|
|
51593
52172
|
if (this.pendo._.contains(CATCHALL_TYPES, capturedEvent.type)) {
|
|
51594
|
-
config.kind = EVENT_KINDS.
|
|
52173
|
+
config.kind = EVENT_KINDS.CATCHALL;
|
|
51595
52174
|
}
|
|
51596
|
-
if (capturedEvent.type === EVENT_KINDS.
|
|
51597
|
-
config.kind = EVENT_KINDS.
|
|
52175
|
+
if (capturedEvent.type === EVENT_KINDS.DEVLOG) {
|
|
52176
|
+
config.kind = EVENT_KINDS.DEVLOG;
|
|
51598
52177
|
}
|
|
51599
52178
|
if (!config.kind)
|
|
51600
52179
|
return;
|
|
52180
|
+
if (!this.hasConsentToForward(capturedEvent.type))
|
|
52181
|
+
return;
|
|
51601
52182
|
// other eventCaptured listeners still mutate capturedEvent in place; defer until they're done
|
|
51602
52183
|
this.pendo._.defer(() => {
|
|
51603
52184
|
this.send(config);
|
|
@@ -52017,6 +52598,7 @@ class SessionRecorder {
|
|
|
52017
52598
|
this.logStopReason('CONSENT_DENIED');
|
|
52018
52599
|
this.stop();
|
|
52019
52600
|
}
|
|
52601
|
+
this.clearSessionInfo();
|
|
52020
52602
|
}
|
|
52021
52603
|
else if (!this.isRecording()) {
|
|
52022
52604
|
this.start();
|
|
@@ -52181,7 +52763,6 @@ class SessionRecorder {
|
|
|
52181
52763
|
var bind = this.pendo._.bind;
|
|
52182
52764
|
this.visitorConfig = visitorConfig;
|
|
52183
52765
|
this.clearOldSessionId();
|
|
52184
|
-
this.liveReplayClient.onLiveFlag(visitorConfig.live, this.visitorId);
|
|
52185
52766
|
this.sendQueue.start();
|
|
52186
52767
|
this.interval = setInterval(() => {
|
|
52187
52768
|
if (!this.sendQueue.failed()) {
|
|
@@ -52194,6 +52775,7 @@ class SessionRecorder {
|
|
|
52194
52775
|
errorHandler: this.errorHandler
|
|
52195
52776
|
}, config));
|
|
52196
52777
|
this._refreshIds();
|
|
52778
|
+
this.liveReplayClient.onLiveFlag(visitorConfig.live, this.visitorId, () => this.snapshot());
|
|
52197
52779
|
this.onRecordingStart();
|
|
52198
52780
|
}
|
|
52199
52781
|
catch (e) {
|