@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.
@@ -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
- var STAGING_SERVER_HASHES = 'stagingServerHashes';
440
- var STAGING_DOMAINS = 'stagingDomains';
441
- var pendo$1;
442
- var _pendoConfig = {};
443
- function loadAsModule(config) {
444
- return config.loadAsModule;
445
- }
446
- function createPendoObject(config) {
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
- function urlMatchesPatterns(url, patterns = [], hashes = []) {
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 hostMatchesStagingDomains(host, domains = []) {
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
- return urlMatchesPatterns(location.host, config.stagingServers, config[STAGING_SERVER_HASHES]) ||
570
- hostMatchesStagingDomains(location.host, config[STAGING_DOMAINS]);
571
- }
572
- function getHash(str) {
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 `window.postMessage`. If set to `true` the web SDK will instead use the browser's Broadcast Channel
3124
- * API to communicate. This will only work when all frames use the same domain.
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 false
3200
+ * @default true
3130
3201
  * @type {boolean}
3131
3202
  */
3132
- addOption('preferBroadcastChannel', [SNIPPET_SRC, PENDO_CONFIG_SRC], false);
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 adds an `unload` event listener to the page to make sure it sends events upon
3148
- * navigation. If set to `true`, the web SDK will instead use the `visibilitychange` event listener and send
3149
- * events when the visibility is changing to `"hidden"`.
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 false
3225
+ * @default true
3155
3226
  * @type {boolean}
3156
3227
  */
3157
- addOption('preventUnloadListener', [SNIPPET_SRC, PENDO_CONFIG_SRC], false);
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 || window.pendo_options, name: SNIPPET_SRC };
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.343.1_';
4062
- let PACKAGE_VERSION = '2.343.1';
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.origin)
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
- if (urlMatchesPatterns(host, ConfigReader.get('allowedOriginServers', []), ConfigReader.get('allowedOriginServerHashes', [])))
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 backdropInertRuntimeScript(step, guide, pendo) {
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 = sniffer.supportsInert()
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 backdropInertStep = {
23696
- script: backdropInertRuntimeScript
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
- backdropInertStep,
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
- guides ? enableGuides() : disableGuides();
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
- this._impl.connectToTop({
26037
- action: 'pendo::connect',
26038
- frameId: this._store.state.frames.frameId
26039
- }, this._targetOrigin);
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 requestReidentification = function () {
29556
- if (!isTrackingConsentDenied() && isVisitorDNT()) {
29557
- Events.identifyRequested.trigger();
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 ? requestReidentification() : clearSession({ doNotTrack: true });
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
- const enabledProducts = applyConsentTiers(tiers);
29597
- Events['consent:changed'].trigger(enabledProducts);
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
- updateCookieConsent(tiers);
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
- if (window.pendo_options) { // Support pre-2.0 initialization
30285
- initialize(window.pendo_options);
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 = window.pendo_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
- syncIdentity();
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
- if (pluginAPI.ConfigReader.get('frameIdentitySync')) {
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
- let params = this.loadParameters(PluginAPI.agentStorage);
42398
- if (!params || pendo._.isEmpty(params)) {
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, PluginAPI.agentStorage);
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
- agentStorage.clear(WEB_ANALYTICS_STORAGE_KEY);
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
- if (this.ws && this.ws.readyState !== WebSocket.CLOSED)
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();