@pendo/agent 2.343.1 → 2.344.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dom.esm.js +33 -24
- package/dist/pendo.module.js +498 -192
- package/dist/pendo.module.min.js +8 -8
- 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.1_';
|
|
4133
|
+
let PACKAGE_VERSION = '2.344.1';
|
|
4063
4134
|
let LOADER = 'xhr';
|
|
4064
4135
|
/* eslint-enable web-sdk-eslint-rules/no-gulp-env-references */
|
|
4065
4136
|
/**
|
|
@@ -4489,6 +4560,7 @@ const ANALYTICS_EVENT_TYPES = {
|
|
|
4489
4560
|
formSubmits: 'submit',
|
|
4490
4561
|
identify: 'identify',
|
|
4491
4562
|
meta: 'meta',
|
|
4563
|
+
objectMetadata: 'objectMetadata',
|
|
4492
4564
|
pageLoads: 'load',
|
|
4493
4565
|
prompt: 'prompt',
|
|
4494
4566
|
trackEvents: 'track',
|
|
@@ -4514,6 +4586,9 @@ function toTierList(tiers) {
|
|
|
4514
4586
|
return [tiers];
|
|
4515
4587
|
return _.isArray(tiers) ? tiers : [];
|
|
4516
4588
|
}
|
|
4589
|
+
function sameConsentTiers(a, b) {
|
|
4590
|
+
return _.isEqual(toTierList(a).slice().sort(), toTierList(b).slice().sort());
|
|
4591
|
+
}
|
|
4517
4592
|
function warnOnUnconfiguredTiers(grantedTiers, classification) {
|
|
4518
4593
|
_.each(grantedTiers, function (tier) {
|
|
4519
4594
|
if (_.isUndefined(classification[tier])) {
|
|
@@ -4543,6 +4618,7 @@ function normalizeTier(tier) {
|
|
|
4543
4618
|
identity: visitorIdentity.trackVisitors === true,
|
|
4544
4619
|
guides: guides.enabled === true,
|
|
4545
4620
|
replay: sessionReplay.enabled === true,
|
|
4621
|
+
utm: analyticsEnabled && eventCollection.utmParameters === true,
|
|
4546
4622
|
analytics: analyticsEnabled ? toAnalyticsEventTypes(eventCollection) : [],
|
|
4547
4623
|
guideAnalytics: analyticsEnabled && eventCollection.guideServing === true ? GUIDE_EVENT_TYPES.slice() : [],
|
|
4548
4624
|
textCollection: analyticsEnabled ? toTextCollectionLevel(eventCollection.textCollectionMode) : TEXT_COLLECTION_LEVELS[0]
|
|
@@ -4553,6 +4629,7 @@ function deniedProducts() {
|
|
|
4553
4629
|
identity: false,
|
|
4554
4630
|
guides: false,
|
|
4555
4631
|
replay: false,
|
|
4632
|
+
utm: false,
|
|
4556
4633
|
[ANALYTICS]: [],
|
|
4557
4634
|
[GUIDE_ANALYTICS]: [],
|
|
4558
4635
|
[TEXT_COLLECTION]: TEXT_COLLECTION_LEVELS[0]
|
|
@@ -4563,6 +4640,7 @@ function combineTiers(resolved, granted) {
|
|
|
4563
4640
|
identity: resolved.identity || granted.identity,
|
|
4564
4641
|
guides: resolved.guides || granted.guides,
|
|
4565
4642
|
replay: resolved.replay || granted.replay,
|
|
4643
|
+
utm: resolved.utm || granted.utm,
|
|
4566
4644
|
[ANALYTICS]: _.union(resolved[ANALYTICS], granted[ANALYTICS]),
|
|
4567
4645
|
[GUIDE_ANALYTICS]: _.union(resolved[GUIDE_ANALYTICS], granted[GUIDE_ANALYTICS]),
|
|
4568
4646
|
[TEXT_COLLECTION]: moreTextPermissive(resolved[TEXT_COLLECTION], granted[TEXT_COLLECTION])
|
|
@@ -4617,6 +4695,9 @@ function isTrackingConsentDenied() {
|
|
|
4617
4695
|
function isGuideConsentDenied() {
|
|
4618
4696
|
return !isProductEnabled('guides');
|
|
4619
4697
|
}
|
|
4698
|
+
function isUtmConsentDenied() {
|
|
4699
|
+
return !isProductEnabled('utm');
|
|
4700
|
+
}
|
|
4620
4701
|
/**
|
|
4621
4702
|
* Session replay is denied whenever tracking itself is denied (DNT) -- a visitor
|
|
4622
4703
|
* who can't be identified shouldn't have their session recorded either -- or when a
|
|
@@ -5966,7 +6047,6 @@ var Events = (function () {
|
|
|
5966
6047
|
new EventType('recording:unpaused', [LIFECYCLE]),
|
|
5967
6048
|
new EventType('recording:paused', [LIFECYCLE]),
|
|
5968
6049
|
new EventType('consent:changed', [LIFECYCLE]),
|
|
5969
|
-
new EventType('identifyRequested', [DEBUG, LIFECYCLE]),
|
|
5970
6050
|
new EventType('childFrameJoined', [FRAMES]),
|
|
5971
6051
|
new EventType('tabIdChanged:self', [DEBUG, LIFECYCLE])
|
|
5972
6052
|
];
|
|
@@ -12045,6 +12125,30 @@ _.extend(dom, {
|
|
|
12045
12125
|
});
|
|
12046
12126
|
_.extend(dom.prototype, DomEvent.$, DomQuery.$);
|
|
12047
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
|
+
|
|
12048
12152
|
// Domains that Pendo owns, will be swapped in by build patches
|
|
12049
12153
|
const pendoDomainPatterns = [
|
|
12050
12154
|
/^https:\/\/((adopt|oem)\.)?((us1)\.)?(app|via|adopt|cdn|oem|novus-api|novus|portal)(\.(au|eu|gov|hsbc|jpn|tv))?\.pendo\.io$/,
|
|
@@ -12052,11 +12156,34 @@ const pendoDomainPatterns = [
|
|
|
12052
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$/,
|
|
12053
12157
|
/^https:\/\/(pendo-au|pendo-eu|pendo-govramp|pendo-hsbc|pendo-io|pendo-jp-prod|pendo-tv|pendo-us1)-static\.storage\.googleapis\.com$/
|
|
12054
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
|
+
}
|
|
12055
12182
|
function isTrustedOrigin(host) {
|
|
12056
12183
|
if (!host)
|
|
12057
12184
|
return false;
|
|
12058
12185
|
// for CNAME, trust the top-level origin
|
|
12059
|
-
if (host === window.location
|
|
12186
|
+
if (host === readOrigin(window.location))
|
|
12060
12187
|
return true;
|
|
12061
12188
|
// Trust the configured hosts
|
|
12062
12189
|
if (host === getDataHost())
|
|
@@ -12072,7 +12199,21 @@ function isTrustedOrigin(host) {
|
|
|
12072
12199
|
if (host === fullHostname)
|
|
12073
12200
|
return true;
|
|
12074
12201
|
}
|
|
12075
|
-
|
|
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))
|
|
12076
12217
|
return true;
|
|
12077
12218
|
return _.any(pendoDomainPatterns, function (pattern) {
|
|
12078
12219
|
return pattern.test(host);
|
|
@@ -14260,15 +14401,13 @@ const internalEvents = {
|
|
|
14260
14401
|
onEventCaptured: 1,
|
|
14261
14402
|
segmentFlagsUpdated: 1,
|
|
14262
14403
|
segmentFlagsReady: 1,
|
|
14263
|
-
segmentFlagsError: 1
|
|
14264
|
-
identifyRequested: 1
|
|
14404
|
+
segmentFlagsError: 1
|
|
14265
14405
|
};
|
|
14266
14406
|
const browserEvents = {
|
|
14267
14407
|
ready: 1,
|
|
14268
14408
|
segmentFlagsUpdated: 1,
|
|
14269
14409
|
segmentFlagsReady: 1,
|
|
14270
|
-
segmentFlagsError: 1
|
|
14271
|
-
identifyRequested: 1
|
|
14410
|
+
segmentFlagsError: 1
|
|
14272
14411
|
};
|
|
14273
14412
|
const supportedPublicEvents = [
|
|
14274
14413
|
'ready',
|
|
@@ -14282,8 +14421,7 @@ const supportedPublicEvents = [
|
|
|
14282
14421
|
'segmentFlagsError',
|
|
14283
14422
|
'validateGuide',
|
|
14284
14423
|
'validateLauncher',
|
|
14285
|
-
'validateGlobalScript'
|
|
14286
|
-
'identifyRequested'
|
|
14424
|
+
'validateGlobalScript'
|
|
14287
14425
|
];
|
|
14288
14426
|
function forwardInternalEvents(Events) {
|
|
14289
14427
|
const unsubscribes = _.reduce(supportedPublicEvents, function (unsubscribes, eventName) {
|
|
@@ -21169,8 +21307,11 @@ function isHidden(document) {
|
|
|
21169
21307
|
const visibilitychange = 'visibilitychange';
|
|
21170
21308
|
const pagehide = 'pagehide';
|
|
21171
21309
|
const unload = 'unload';
|
|
21310
|
+
function supportsPageHide() {
|
|
21311
|
+
return 'onpagehide' in window;
|
|
21312
|
+
}
|
|
21172
21313
|
function onPageHidden(callback, useVisibilityChange) {
|
|
21173
|
-
if (useVisibilityChange) {
|
|
21314
|
+
if (useVisibilityChange && supportsPageHide()) {
|
|
21174
21315
|
const unlisten = [];
|
|
21175
21316
|
const onChange = () => {
|
|
21176
21317
|
if (isHidden(document)) {
|
|
@@ -23669,14 +23810,14 @@ var autofocusStep = {
|
|
|
23669
23810
|
script: autofocusRuntimeScript
|
|
23670
23811
|
};
|
|
23671
23812
|
|
|
23672
|
-
function
|
|
23813
|
+
function backdropVisibilityRuntimeScript(step, guide, pendo) {
|
|
23673
23814
|
if (pendo.designer || !step.guideElement)
|
|
23674
23815
|
return;
|
|
23675
23816
|
if (step.hasBackdrop) {
|
|
23676
23817
|
const BuildingBlockGuides = pendo.BuildingBlocks.BuildingBlockGuides;
|
|
23677
23818
|
const guideElement = step.guideElement[0];
|
|
23678
23819
|
const excludeElements = getExcludedElements(step);
|
|
23679
|
-
const disabledElements =
|
|
23820
|
+
const disabledElements = ConfigReader.get('useInertForBackdrop')
|
|
23680
23821
|
? BuildingBlockGuides.addInertOutsideGuide(guideElement, [], excludeElements)
|
|
23681
23822
|
: BuildingBlockGuides.addAriaHiddenOutsideGuide(guideElement, [], excludeElements);
|
|
23682
23823
|
step.hiddenElements = (step.hiddenElements || []).concat(disabledElements);
|
|
@@ -23692,8 +23833,8 @@ function getExcludedElements(step) {
|
|
|
23692
23833
|
}
|
|
23693
23834
|
return excludeElements;
|
|
23694
23835
|
}
|
|
23695
|
-
var
|
|
23696
|
-
script:
|
|
23836
|
+
var backdropVisibilityStep = {
|
|
23837
|
+
script: backdropVisibilityRuntimeScript
|
|
23697
23838
|
};
|
|
23698
23839
|
|
|
23699
23840
|
function isTooltip({ _ }, step) {
|
|
@@ -23853,7 +23994,7 @@ function registerPostrenderScripts(runtime) {
|
|
|
23853
23994
|
advanceOnElement,
|
|
23854
23995
|
announcementMarkSeen,
|
|
23855
23996
|
autofocusStep,
|
|
23856
|
-
|
|
23997
|
+
backdropVisibilityStep,
|
|
23857
23998
|
repositionOnScroll,
|
|
23858
23999
|
setFocusToActivationElement$1,
|
|
23859
24000
|
videoLoadingAttribute
|
|
@@ -25447,10 +25588,16 @@ var initGuides = function (observer) {
|
|
|
25447
25588
|
var { guides } = event.data[0];
|
|
25448
25589
|
if (_.isUndefined(guides))
|
|
25449
25590
|
return;
|
|
25450
|
-
|
|
25591
|
+
if (guides) {
|
|
25592
|
+
enableGuides();
|
|
25593
|
+
return;
|
|
25594
|
+
}
|
|
25595
|
+
disableGuides();
|
|
25596
|
+
clearGuideThrottlingState();
|
|
25451
25597
|
});
|
|
25452
25598
|
if (isGuideConsentDenied()) {
|
|
25453
25599
|
disableGuides();
|
|
25600
|
+
clearGuideThrottlingState();
|
|
25454
25601
|
}
|
|
25455
25602
|
teardownFns.push(onTurbolinksPageLoad(document, function onPageLoad() {
|
|
25456
25603
|
if (pendoDotUrl.get() === reloadGuides.lastUrl) {
|
|
@@ -25580,6 +25727,11 @@ function disableGuides() {
|
|
|
25580
25727
|
setGuidesDisabled(true);
|
|
25581
25728
|
resetPendoUI();
|
|
25582
25729
|
}
|
|
25730
|
+
function clearGuideThrottlingState() {
|
|
25731
|
+
_.each(THROTTLING_STATE, function (throttlingStorageKey) {
|
|
25732
|
+
agentStorage.clear(throttlingStorageKey);
|
|
25733
|
+
});
|
|
25734
|
+
}
|
|
25583
25735
|
var setGuidesDelayed = function (areDelayed) {
|
|
25584
25736
|
var originalOptions = ConfigReader.getLocalConfig();
|
|
25585
25737
|
delete originalOptions.delayGuides;
|
|
@@ -25618,10 +25770,20 @@ var adjustActiveGuideOnResize = function () {
|
|
|
25618
25770
|
// as the user zooms the browser APP-53281
|
|
25619
25771
|
log.debug('adjustActiveGuideOnResize firing');
|
|
25620
25772
|
var activeGuide = getActiveGuide();
|
|
25621
|
-
if (activeGuide && activeGuide.step && !pendo$1.ignoreResize) {
|
|
25773
|
+
if (activeGuide && activeGuide.step && !pendo$1.ignoreResize && !isEditingGuideTextField(activeGuide.step)) {
|
|
25622
25774
|
activeGuide.step.redraw();
|
|
25623
25775
|
}
|
|
25624
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
|
+
};
|
|
25625
25787
|
var resizeHandler = function () {
|
|
25626
25788
|
// In some browsers, it's possible for activeElement to become undefined or null
|
|
25627
25789
|
var activeElementId = _.get(document, 'activeElement.id', '');
|
|
@@ -25950,6 +26112,46 @@ function stopLivePreviewRelay(onStopped) {
|
|
|
25950
26112
|
}, relay.origin);
|
|
25951
26113
|
}
|
|
25952
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
|
+
|
|
25953
26155
|
var REMOVE_FRAME_ACTION = 'frames/removeFrame';
|
|
25954
26156
|
function tryParseJson(messageData) {
|
|
25955
26157
|
if (!_.isString(messageData)) {
|
|
@@ -26011,6 +26213,12 @@ _.extend(TopFrameRelay.prototype, {
|
|
|
26011
26213
|
if (message.data.action === 'pendo::connect') {
|
|
26012
26214
|
if (!isSameWindowTree(message, this._window))
|
|
26013
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;
|
|
26014
26222
|
this._impl.frameConnect(message);
|
|
26015
26223
|
}
|
|
26016
26224
|
if (message.data.action === REMOVE_FRAME_ACTION) {
|
|
@@ -26033,10 +26241,33 @@ _.extend(TopFrameRelay.prototype, {
|
|
|
26033
26241
|
this._impl.broadcast(message);
|
|
26034
26242
|
},
|
|
26035
26243
|
_connectToTop(timeout, retries, timeoutFn) {
|
|
26036
|
-
|
|
26037
|
-
|
|
26038
|
-
|
|
26039
|
-
|
|
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
|
|
26040
26271
|
setTimeout$1(_.bind(function () {
|
|
26041
26272
|
if (this._connected)
|
|
26042
26273
|
return;
|
|
@@ -26286,23 +26517,6 @@ _.extend(PostMessageImpl.prototype, ImplCommon, {
|
|
|
26286
26517
|
}
|
|
26287
26518
|
});
|
|
26288
26519
|
|
|
26289
|
-
function getLocationOrigin(window) {
|
|
26290
|
-
const strNull = 'null';
|
|
26291
|
-
const origin = window.location.origin;
|
|
26292
|
-
if (origin === strNull && window.parent && window.parent != window) {
|
|
26293
|
-
try {
|
|
26294
|
-
const parentOrigin = window.parent.location.origin;
|
|
26295
|
-
if (parentOrigin && parentOrigin !== strNull) {
|
|
26296
|
-
return parentOrigin;
|
|
26297
|
-
}
|
|
26298
|
-
}
|
|
26299
|
-
catch (e) {
|
|
26300
|
-
// cross-origin ancestor; cannot read top location
|
|
26301
|
-
}
|
|
26302
|
-
}
|
|
26303
|
-
return origin;
|
|
26304
|
-
}
|
|
26305
|
-
|
|
26306
26520
|
function createSingletonMessageHandler(window, isTrustedOriginFn) {
|
|
26307
26521
|
var listeners = [];
|
|
26308
26522
|
var secure = false;
|
|
@@ -29348,7 +29562,8 @@ const PluginAPI = {
|
|
|
29348
29562
|
collectEvent,
|
|
29349
29563
|
hasConsentForAnalyticsEvent,
|
|
29350
29564
|
getTextCollectionMode,
|
|
29351
|
-
isReplayConsentDenied
|
|
29565
|
+
isReplayConsentDenied,
|
|
29566
|
+
isUtmConsentDenied
|
|
29352
29567
|
},
|
|
29353
29568
|
attachEvent,
|
|
29354
29569
|
attachEventInternal,
|
|
@@ -29419,7 +29634,8 @@ const PluginAPI = {
|
|
|
29419
29634
|
escapeRegExp,
|
|
29420
29635
|
getZoneSafeMethod(method, target) {
|
|
29421
29636
|
return getZoneSafeMethod$1(_, method, target);
|
|
29422
|
-
}
|
|
29637
|
+
},
|
|
29638
|
+
isNativeCode
|
|
29423
29639
|
},
|
|
29424
29640
|
constants: {
|
|
29425
29641
|
URL_MAX_LENGTH,
|
|
@@ -29552,16 +29768,18 @@ function initIdentityEvents() {
|
|
|
29552
29768
|
}
|
|
29553
29769
|
}
|
|
29554
29770
|
};
|
|
29555
|
-
const
|
|
29556
|
-
if (!isTrackingConsentDenied()
|
|
29557
|
-
|
|
29771
|
+
const resumeIdentity = function (newIdentity) {
|
|
29772
|
+
if (!newIdentity || isTrackingConsentDenied() || !isVisitorDNT()) {
|
|
29773
|
+
return;
|
|
29558
29774
|
}
|
|
29775
|
+
updateOptions(newIdentity);
|
|
29559
29776
|
};
|
|
29560
29777
|
const handleConsentChange = function (event) {
|
|
29561
29778
|
var { identity } = event.data[0];
|
|
29779
|
+
var newIdentity = event.data[1];
|
|
29562
29780
|
if (_.isUndefined(identity))
|
|
29563
29781
|
return;
|
|
29564
|
-
identity ?
|
|
29782
|
+
identity ? resumeIdentity(newIdentity) : clearSession({ doNotTrack: true });
|
|
29565
29783
|
};
|
|
29566
29784
|
Events.identify.on(collectIdentifyEvent);
|
|
29567
29785
|
Events.metadata.on(collectMetadataEvent);
|
|
@@ -29577,13 +29795,38 @@ function initIdentityEvents() {
|
|
|
29577
29795
|
};
|
|
29578
29796
|
}
|
|
29579
29797
|
|
|
29798
|
+
const CONSENT_TIERS = 'cookieConsentTiers';
|
|
29799
|
+
function applyCookieConsent(tiers, identity) {
|
|
29800
|
+
rememberConsentTiers(tiers);
|
|
29801
|
+
const enabledProducts = applyConsentTiers(tiers);
|
|
29802
|
+
Events['consent:changed'].trigger(enabledProducts, identity);
|
|
29803
|
+
log.debug('Cookie consent updated for ' + JSON.stringify(tiers) + '.');
|
|
29804
|
+
return enabledProducts;
|
|
29805
|
+
}
|
|
29806
|
+
function rememberConsentTiers(tiers) {
|
|
29807
|
+
const options = ConfigReader.getLocalConfig() || {};
|
|
29808
|
+
options[CONSENT_TIERS] = tiers;
|
|
29809
|
+
ConfigReader.setLocalConfig(options);
|
|
29810
|
+
}
|
|
29811
|
+
function getCookieConsentTiers() {
|
|
29812
|
+
return ConfigReader.get(CONSENT_TIERS);
|
|
29813
|
+
}
|
|
29814
|
+
function isCurrentCookieConsent(tiers) {
|
|
29815
|
+
return sameConsentTiers(tiers, getCookieConsentTiers());
|
|
29816
|
+
}
|
|
29580
29817
|
/**
|
|
29581
29818
|
* Enforces the cookie consent policy a visitor has agreed to. The policy is defined by the subscription's cookie consent configuration,
|
|
29582
29819
|
* and is used to determine whether each product the web SDK may run.
|
|
29583
29820
|
*
|
|
29821
|
+
* A visitor who was not being tracked has no identity for the web SDK to resume, so pass one in
|
|
29822
|
+
* `identity` when the new tiers start allowing identity. It is ignored otherwise.
|
|
29823
|
+
*
|
|
29584
29824
|
* @access public
|
|
29585
29825
|
* @category Core
|
|
29586
29826
|
* @param {string | string[]} tiers tier or tiers the visitor consented to
|
|
29827
|
+
* @param {object} [identity] visitor and account to identify, in the same shape as
|
|
29828
|
+
* `pendo.updateOptions` -- applied only when consent moves a do-not-track visitor to a tier
|
|
29829
|
+
* that permits identity
|
|
29587
29830
|
* @returns {object} map of product to its resolved value -- `guides` and `identity` are
|
|
29588
29831
|
* booleans (identity disabled means DNT), `textCollection` is `none`/`whitelist`/`all`,
|
|
29589
29832
|
* `analytics` and `guideAnalytics` are each an array of allowed event types
|
|
@@ -29591,17 +29834,20 @@ function initIdentityEvents() {
|
|
|
29591
29834
|
* pendo.updateCookieConsent('strictlyNecessary');
|
|
29592
29835
|
* @example
|
|
29593
29836
|
* pendo.updateCookieConsent(['strictlyNecessary', 'functional']);
|
|
29837
|
+
* @example
|
|
29838
|
+
* pendo.updateCookieConsent(['strictlyNecessary', 'functional'], {
|
|
29839
|
+
* visitor: { id: 'put_visitor_id_here', role: 'admin' },
|
|
29840
|
+
* account: { id: 'put_account_id_here' }
|
|
29841
|
+
* });
|
|
29594
29842
|
*/
|
|
29595
|
-
function updateCookieConsent(tiers) {
|
|
29596
|
-
|
|
29597
|
-
|
|
29598
|
-
log.debug('Cookie consent updated for ' + JSON.stringify(tiers) + '.');
|
|
29599
|
-
return enabledProducts;
|
|
29843
|
+
function updateCookieConsent(tiers, identity) {
|
|
29844
|
+
store.dispatch('frames/changeCookieConsent', { tiers });
|
|
29845
|
+
return applyCookieConsent(tiers, identity);
|
|
29600
29846
|
}
|
|
29601
29847
|
function initializeCookieConsent(tiers) {
|
|
29602
29848
|
resetConsentPolicy();
|
|
29603
29849
|
if (tiers) {
|
|
29604
|
-
|
|
29850
|
+
applyCookieConsent(tiers);
|
|
29605
29851
|
}
|
|
29606
29852
|
}
|
|
29607
29853
|
|
|
@@ -30281,8 +30527,9 @@ function autoInitialize() {
|
|
|
30281
30527
|
log.info('already running');
|
|
30282
30528
|
return;
|
|
30283
30529
|
}
|
|
30284
|
-
|
|
30285
|
-
|
|
30530
|
+
const snippetOptions = getLegacyGlobalSnippetOptions();
|
|
30531
|
+
if (snippetOptions) { // Support pre-2.0 initialization
|
|
30532
|
+
initialize(snippetOptions);
|
|
30286
30533
|
}
|
|
30287
30534
|
flushCallQueue();
|
|
30288
30535
|
}
|
|
@@ -34133,20 +34380,6 @@ function GuideStep(guide) {
|
|
|
34133
34380
|
stepId: step.id,
|
|
34134
34381
|
isShown: step.isShown()
|
|
34135
34382
|
});
|
|
34136
|
-
// Android and some other mobile frameworks (Cordova webviews) send a resize event when the keyboard opens on focusing a text input
|
|
34137
|
-
// due to redrawing the guide on resize this ends up closing the keyboard immediately.
|
|
34138
|
-
// We need to ignore resize events in these cases to not redraw yet
|
|
34139
|
-
// This idea comes from the 2nd solution of: https://stackoverflow.com/questions/38296574/disappearing-soft-keyboard-in-android-app
|
|
34140
|
-
if (step.guideElement && step.guideElement[0]) {
|
|
34141
|
-
step.attachEvent(step.guideElement[0], 'focusin', function (e) {
|
|
34142
|
-
if (e.target.tagName === 'TEXTAREA' || e.target.tagName === 'INPUT') {
|
|
34143
|
-
pendo$1.ignoreResize = true;
|
|
34144
|
-
setTimeout$1(function () {
|
|
34145
|
-
pendo$1.ignoreResize = false;
|
|
34146
|
-
}, 1000);
|
|
34147
|
-
}
|
|
34148
|
-
});
|
|
34149
|
-
}
|
|
34150
34383
|
};
|
|
34151
34384
|
/**
|
|
34152
34385
|
* Hides the Step if shown. Use an argument of an Object containing a
|
|
@@ -34373,7 +34606,7 @@ const ErrorLogModule = (function () {
|
|
|
34373
34606
|
}
|
|
34374
34607
|
const errMsg = `[${logEvent}]`;
|
|
34375
34608
|
if (logEvent.level === LogLevel.Critical) {
|
|
34376
|
-
const options =
|
|
34609
|
+
const options = getLegacyGlobalSnippetOptions() || {};
|
|
34377
34610
|
logError(errMsg);
|
|
34378
34611
|
if (pendo$1.logStackTraces !== false && options.logStackTraces !== false && error.stack) {
|
|
34379
34612
|
writeErrorPOST(`${errMsg}\n${error.stack}`);
|
|
@@ -35111,6 +35344,22 @@ var FramesModule = (function () {
|
|
|
35111
35344
|
return;
|
|
35112
35345
|
identifyVisitor(payload.visitorId, payload.accountId);
|
|
35113
35346
|
},
|
|
35347
|
+
changeCookieConsent(context, payload) {
|
|
35348
|
+
crossFrameChannel.postMessage({
|
|
35349
|
+
action: 'frames/receiveCookieConsent',
|
|
35350
|
+
frameId: context.state.frameId,
|
|
35351
|
+
tiers: payload.tiers
|
|
35352
|
+
});
|
|
35353
|
+
},
|
|
35354
|
+
receiveCookieConsent(context, payload) {
|
|
35355
|
+
if (context.getters.getMyApiKey() !== payload.apiKey)
|
|
35356
|
+
return;
|
|
35357
|
+
if (context.state.frameId === payload.frameId)
|
|
35358
|
+
return;
|
|
35359
|
+
if (isCurrentCookieConsent(payload.tiers))
|
|
35360
|
+
return;
|
|
35361
|
+
applyCookieConsent(payload.tiers);
|
|
35362
|
+
},
|
|
35114
35363
|
setReady(context, payload) {
|
|
35115
35364
|
context.commit('setReady', payload.isReady);
|
|
35116
35365
|
},
|
|
@@ -39092,6 +39341,7 @@ const IFrameMonitor = (function () {
|
|
|
39092
39341
|
let pendoGlobal;
|
|
39093
39342
|
let pluginAPI;
|
|
39094
39343
|
let topDownSync;
|
|
39344
|
+
let identitySync;
|
|
39095
39345
|
return {
|
|
39096
39346
|
name: 'IFrameMonitor',
|
|
39097
39347
|
initialize: init,
|
|
@@ -39107,7 +39357,12 @@ const IFrameMonitor = (function () {
|
|
|
39107
39357
|
removeHandlers();
|
|
39108
39358
|
}
|
|
39109
39359
|
function onChildFrameJoined() {
|
|
39110
|
-
|
|
39360
|
+
// Consent first. A frame applies these in the order they arrive, and one whose policy
|
|
39361
|
+
// still denies tracking will throw away the identity sent below.
|
|
39362
|
+
syncCookieConsent();
|
|
39363
|
+
if (identitySync) {
|
|
39364
|
+
syncIdentity();
|
|
39365
|
+
}
|
|
39111
39366
|
}
|
|
39112
39367
|
function getAgentUrl() {
|
|
39113
39368
|
const selfHostedUrl = pluginAPI.ConfigReader.get('selfHostedAgentUrl');
|
|
@@ -39210,14 +39465,23 @@ const IFrameMonitor = (function () {
|
|
|
39210
39465
|
return;
|
|
39211
39466
|
pluginAPI.store.dispatch('frames/syncIdentity', { visitorId, accountId });
|
|
39212
39467
|
}
|
|
39468
|
+
function syncCookieConsent() {
|
|
39469
|
+
const tiers = getCookieConsentTiers();
|
|
39470
|
+
if (!tiers)
|
|
39471
|
+
return; // nothing to replay for a subscription that never configured consent
|
|
39472
|
+
pluginAPI.store.dispatch('frames/changeCookieConsent', { tiers });
|
|
39473
|
+
}
|
|
39213
39474
|
function monitorFrames() {
|
|
39214
39475
|
if (pluginAPI.ConfigReader.get('autoFrameInstall')) {
|
|
39215
39476
|
pluginAPI.Events.ready.on(readyHandler);
|
|
39216
39477
|
}
|
|
39217
|
-
|
|
39478
|
+
identitySync = pluginAPI.ConfigReader.get('frameIdentitySync');
|
|
39479
|
+
// A frame that installs itself misses consent changes made before it joined, whether or
|
|
39480
|
+
// not identity is being shared with it.
|
|
39481
|
+
pluginAPI.Events.childFrameJoined.on(onChildFrameJoined);
|
|
39482
|
+
if (identitySync) {
|
|
39218
39483
|
pluginAPI.Events.metadata.on(metadataHandler);
|
|
39219
39484
|
pluginAPI.Events.identify.on(identifyHandler);
|
|
39220
|
-
pluginAPI.Events.childFrameJoined.on(onChildFrameJoined);
|
|
39221
39485
|
}
|
|
39222
39486
|
}
|
|
39223
39487
|
function removeHandlers() {
|
|
@@ -42382,7 +42646,8 @@ class WebAnalytics {
|
|
|
42382
42646
|
const { bind } = pendo._;
|
|
42383
42647
|
this.subscriptions = [
|
|
42384
42648
|
PluginAPI.attachEvent(PluginAPI.Events, 'eventCaptured', bind(this.addUtmToEvent, this)),
|
|
42385
|
-
PluginAPI.attachEvent(PluginAPI.Events, 'sessionChanged', bind(this.sessionChanged, this))
|
|
42649
|
+
PluginAPI.attachEvent(PluginAPI.Events, 'sessionChanged', bind(this.sessionChanged, this)),
|
|
42650
|
+
PluginAPI.attachEvent(PluginAPI.Events, 'consent:changed', bind(this.handleConsentChange, this))
|
|
42386
42651
|
];
|
|
42387
42652
|
this.suffix = this.api.ConfigReader.get('identityStorageSuffix');
|
|
42388
42653
|
/**
|
|
@@ -42394,11 +42659,19 @@ class WebAnalytics {
|
|
|
42394
42659
|
* @label WEB_ANALYTICS_STORAGE_KEY
|
|
42395
42660
|
*/
|
|
42396
42661
|
this.api.agentStorage.registry.addLocal(WEB_ANALYTICS_STORAGE_KEY, { cookieSuffix: this.suffix, isSecure: true });
|
|
42397
|
-
|
|
42398
|
-
|
|
42662
|
+
if (PluginAPI.analytics.isUtmConsentDenied()) {
|
|
42663
|
+
return this.clearParameters(PluginAPI.agentStorage);
|
|
42664
|
+
}
|
|
42665
|
+
this.captureParameters();
|
|
42666
|
+
}
|
|
42667
|
+
captureParameters() {
|
|
42668
|
+
if (this.api.analytics.isUtmConsentDenied())
|
|
42669
|
+
return;
|
|
42670
|
+
let params = this.loadParameters(this.api.agentStorage);
|
|
42671
|
+
if (!params || this.pendo._.isEmpty(params)) {
|
|
42399
42672
|
params = this.extractParameters(window.location.href, document.referrer);
|
|
42400
42673
|
}
|
|
42401
|
-
this.storeParameters(params,
|
|
42674
|
+
this.storeParameters(params, this.api.agentStorage);
|
|
42402
42675
|
}
|
|
42403
42676
|
extractParameters(url, referrer) {
|
|
42404
42677
|
const locationUrl = new URL(url);
|
|
@@ -42454,10 +42727,16 @@ class WebAnalytics {
|
|
|
42454
42727
|
}
|
|
42455
42728
|
}
|
|
42456
42729
|
storeParameters(params, storage) {
|
|
42730
|
+
if (this.api.analytics.isUtmConsentDenied())
|
|
42731
|
+
return;
|
|
42457
42732
|
if (this.pendo._.size(params) > 0) {
|
|
42458
42733
|
storage.write(WEB_ANALYTICS_STORAGE_KEY, JSON.stringify(params));
|
|
42459
42734
|
}
|
|
42460
42735
|
}
|
|
42736
|
+
clearParameters(storage) {
|
|
42737
|
+
storage.clear(WEB_ANALYTICS_STORAGE_KEY);
|
|
42738
|
+
storage.clear(WEB_ANALYTICS_STORAGE_KEY, false, '');
|
|
42739
|
+
}
|
|
42461
42740
|
loadParameters(storage) {
|
|
42462
42741
|
const storedParamsJson = storage.read(WEB_ANALYTICS_STORAGE_KEY) ||
|
|
42463
42742
|
storage.read(WEB_ANALYTICS_STORAGE_KEY, false, '');
|
|
@@ -42472,6 +42751,8 @@ class WebAnalytics {
|
|
|
42472
42751
|
this.subscriptions.length = 0;
|
|
42473
42752
|
}
|
|
42474
42753
|
addUtmToEvent(event) {
|
|
42754
|
+
if (this.api.analytics.isUtmConsentDenied())
|
|
42755
|
+
return;
|
|
42475
42756
|
let utm;
|
|
42476
42757
|
try {
|
|
42477
42758
|
utm = this.loadParameters(this.api.agentStorage);
|
|
@@ -42485,11 +42766,19 @@ class WebAnalytics {
|
|
|
42485
42766
|
}
|
|
42486
42767
|
sessionChanged() {
|
|
42487
42768
|
const agentStorage = this.api.agentStorage;
|
|
42488
|
-
|
|
42489
|
-
agentStorage.clear(WEB_ANALYTICS_STORAGE_KEY, false, '');
|
|
42769
|
+
this.clearParameters(agentStorage);
|
|
42490
42770
|
const params = this.extractParameters(window.location.href, document.referrer);
|
|
42491
42771
|
this.storeParameters(params, agentStorage);
|
|
42492
42772
|
}
|
|
42773
|
+
handleConsentChange(event) {
|
|
42774
|
+
const { utm } = event.data[0];
|
|
42775
|
+
if (this.pendo._.isUndefined(utm))
|
|
42776
|
+
return;
|
|
42777
|
+
if (this.api.analytics.isUtmConsentDenied()) {
|
|
42778
|
+
return this.clearParameters(this.api.agentStorage);
|
|
42779
|
+
}
|
|
42780
|
+
this.captureParameters();
|
|
42781
|
+
}
|
|
42493
42782
|
}
|
|
42494
42783
|
var WebAnalytics$1 = new WebAnalytics();
|
|
42495
42784
|
|
|
@@ -51773,14 +52062,15 @@ class Transport {
|
|
|
51773
52062
|
}
|
|
51774
52063
|
}
|
|
51775
52064
|
|
|
51776
|
-
const CATCHALL_TYPES = ['load', 'click', 'track', 'guideSeen'];
|
|
51777
|
-
const AGENTIC_TYPES = ["prompt", "agent_response", "user_reaction"];
|
|
51778
52065
|
const EVENT_KINDS = {
|
|
51779
52066
|
AGENTIC: 'agentic',
|
|
51780
52067
|
CATCHALL: 'catchall',
|
|
51781
52068
|
DEVLOG: 'devlog',
|
|
51782
52069
|
RECORDING: 'recording'
|
|
51783
52070
|
};
|
|
52071
|
+
const CATCHALL_TYPES = ['load', 'click', 'track', 'guideSeen'];
|
|
52072
|
+
const AGENTIC_TYPES = ["prompt", "agent_response", "user_reaction"];
|
|
52073
|
+
const CONSENT_EXEMPT_TYPES = ['guideSeen', EVENT_KINDS.DEVLOG];
|
|
51784
52074
|
class LiveReplayClient {
|
|
51785
52075
|
constructor(pendo, api) {
|
|
51786
52076
|
this.pendo = pendo;
|
|
@@ -51788,6 +52078,7 @@ class LiveReplayClient {
|
|
|
51788
52078
|
this.ws = null;
|
|
51789
52079
|
this.sequence = 0;
|
|
51790
52080
|
this.unsubscribeEventCaptured = null;
|
|
52081
|
+
this.disabled = false;
|
|
51791
52082
|
}
|
|
51792
52083
|
buildWsUrl() {
|
|
51793
52084
|
const url = new URL$1(`${this.pendo.HOST}/data/live-replay/${this.pendo.apiKey}`);
|
|
@@ -51800,11 +52091,18 @@ class LiveReplayClient {
|
|
|
51800
52091
|
return url.href;
|
|
51801
52092
|
}
|
|
51802
52093
|
start(visitorId, onOpen) {
|
|
51803
|
-
|
|
52094
|
+
const wsIsOpen = this.ws && this.ws.readyState !== WebSocket.CLOSED;
|
|
52095
|
+
if (wsIsOpen || this.disabled)
|
|
51804
52096
|
return;
|
|
51805
52097
|
this.sequence = 0;
|
|
51806
52098
|
this.unsubscribeEventCaptured = this.api.attachEvent(this.api.Events, 'eventCaptured', this.pendo._.bind(this.forwardEvent, this));
|
|
51807
52099
|
this.ws = new WebSocket(this.buildWsUrl());
|
|
52100
|
+
if (!this.api.util.isNativeCode(this.ws.send)) {
|
|
52101
|
+
this.disabled = true;
|
|
52102
|
+
this.api.log.warn('Live replay disabled: WebSocket send is not native.');
|
|
52103
|
+
this.stop();
|
|
52104
|
+
return;
|
|
52105
|
+
}
|
|
51808
52106
|
this.ws.onopen = this.pendo._.bind(this.handleOpen, this, visitorId, onOpen);
|
|
51809
52107
|
const boundHandleClose = this.pendo._.bind(this.handleClose, this);
|
|
51810
52108
|
this.ws.onclose = boundHandleClose;
|
|
@@ -51862,6 +52160,11 @@ class LiveReplayClient {
|
|
|
51862
52160
|
sendRecordingEvent(fields) {
|
|
51863
52161
|
this.send(Object.assign(Object.assign({}, fields), { kind: EVENT_KINDS.RECORDING, sequence: this.sequence++ }));
|
|
51864
52162
|
}
|
|
52163
|
+
hasConsentToForward(type) {
|
|
52164
|
+
if (this.pendo._.contains(CONSENT_EXEMPT_TYPES, type))
|
|
52165
|
+
return true;
|
|
52166
|
+
return this.api.analytics.hasConsentForAnalyticsEvent(type);
|
|
52167
|
+
}
|
|
51865
52168
|
forwardEvent(event) {
|
|
51866
52169
|
if (!event || !event.data || !event.data.length)
|
|
51867
52170
|
return;
|
|
@@ -51878,6 +52181,8 @@ class LiveReplayClient {
|
|
|
51878
52181
|
}
|
|
51879
52182
|
if (!config.kind)
|
|
51880
52183
|
return;
|
|
52184
|
+
if (!this.hasConsentToForward(capturedEvent.type))
|
|
52185
|
+
return;
|
|
51881
52186
|
// other eventCaptured listeners still mutate capturedEvent in place; defer until they're done
|
|
51882
52187
|
this.pendo._.defer(() => {
|
|
51883
52188
|
this.send(config);
|
|
@@ -52297,6 +52602,7 @@ class SessionRecorder {
|
|
|
52297
52602
|
this.logStopReason('CONSENT_DENIED');
|
|
52298
52603
|
this.stop();
|
|
52299
52604
|
}
|
|
52605
|
+
this.clearSessionInfo();
|
|
52300
52606
|
}
|
|
52301
52607
|
else if (!this.isRecording()) {
|
|
52302
52608
|
this.start();
|