@pendo/agent 2.343.0 → 2.344.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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.0_';
4062
- let PACKAGE_VERSION = '2.343.0';
4132
+ let VERSION = '2.344.0_';
4133
+ let PACKAGE_VERSION = '2.344.0';
4063
4134
  let LOADER = 'xhr';
4064
4135
  /* eslint-enable web-sdk-eslint-rules/no-gulp-env-references */
4065
4136
  /**
@@ -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;
@@ -26282,23 +26513,6 @@ _.extend(PostMessageImpl.prototype, ImplCommon, {
26282
26513
  }
26283
26514
  });
26284
26515
 
26285
- function getLocationOrigin(window) {
26286
- const strNull = 'null';
26287
- const origin = window.location.origin;
26288
- if (origin === strNull && window.parent && window.parent != window) {
26289
- try {
26290
- const parentOrigin = window.parent.location.origin;
26291
- if (parentOrigin && parentOrigin !== strNull) {
26292
- return parentOrigin;
26293
- }
26294
- }
26295
- catch (e) {
26296
- // cross-origin ancestor; cannot read top location
26297
- }
26298
- }
26299
- return origin;
26300
- }
26301
-
26302
26516
  function createSingletonMessageHandler(window, isTrustedOriginFn) {
26303
26517
  var listeners = [];
26304
26518
  var secure = false;
@@ -29344,7 +29558,8 @@ const PluginAPI = {
29344
29558
  collectEvent,
29345
29559
  hasConsentForAnalyticsEvent,
29346
29560
  getTextCollectionMode,
29347
- isReplayConsentDenied
29561
+ isReplayConsentDenied,
29562
+ isUtmConsentDenied
29348
29563
  },
29349
29564
  attachEvent,
29350
29565
  attachEventInternal,
@@ -29415,7 +29630,8 @@ const PluginAPI = {
29415
29630
  escapeRegExp,
29416
29631
  getZoneSafeMethod(method, target) {
29417
29632
  return getZoneSafeMethod$1(_, method, target);
29418
- }
29633
+ },
29634
+ isNativeCode
29419
29635
  },
29420
29636
  constants: {
29421
29637
  URL_MAX_LENGTH,
@@ -29548,16 +29764,18 @@ function initIdentityEvents() {
29548
29764
  }
29549
29765
  }
29550
29766
  };
29551
- const requestReidentification = function () {
29552
- if (!isTrackingConsentDenied() && isVisitorDNT()) {
29553
- Events.identifyRequested.trigger();
29767
+ const resumeIdentity = function (newIdentity) {
29768
+ if (!newIdentity || isTrackingConsentDenied() || !isVisitorDNT()) {
29769
+ return;
29554
29770
  }
29771
+ updateOptions(newIdentity);
29555
29772
  };
29556
29773
  const handleConsentChange = function (event) {
29557
29774
  var { identity } = event.data[0];
29775
+ var newIdentity = event.data[1];
29558
29776
  if (_.isUndefined(identity))
29559
29777
  return;
29560
- identity ? requestReidentification() : clearSession({ doNotTrack: true });
29778
+ identity ? resumeIdentity(newIdentity) : clearSession({ doNotTrack: true });
29561
29779
  };
29562
29780
  Events.identify.on(collectIdentifyEvent);
29563
29781
  Events.metadata.on(collectMetadataEvent);
@@ -29573,13 +29791,38 @@ function initIdentityEvents() {
29573
29791
  };
29574
29792
  }
29575
29793
 
29794
+ const CONSENT_TIERS = 'cookieConsentTiers';
29795
+ function applyCookieConsent(tiers, identity) {
29796
+ rememberConsentTiers(tiers);
29797
+ const enabledProducts = applyConsentTiers(tiers);
29798
+ Events['consent:changed'].trigger(enabledProducts, identity);
29799
+ log.debug('Cookie consent updated for ' + JSON.stringify(tiers) + '.');
29800
+ return enabledProducts;
29801
+ }
29802
+ function rememberConsentTiers(tiers) {
29803
+ const options = ConfigReader.getLocalConfig() || {};
29804
+ options[CONSENT_TIERS] = tiers;
29805
+ ConfigReader.setLocalConfig(options);
29806
+ }
29807
+ function getCookieConsentTiers() {
29808
+ return ConfigReader.get(CONSENT_TIERS);
29809
+ }
29810
+ function isCurrentCookieConsent(tiers) {
29811
+ return sameConsentTiers(tiers, getCookieConsentTiers());
29812
+ }
29576
29813
  /**
29577
29814
  * Enforces the cookie consent policy a visitor has agreed to. The policy is defined by the subscription's cookie consent configuration,
29578
29815
  * and is used to determine whether each product the web SDK may run.
29579
29816
  *
29817
+ * A visitor who was not being tracked has no identity for the web SDK to resume, so pass one in
29818
+ * `identity` when the new tiers start allowing identity. It is ignored otherwise.
29819
+ *
29580
29820
  * @access public
29581
29821
  * @category Core
29582
29822
  * @param {string | string[]} tiers tier or tiers the visitor consented to
29823
+ * @param {object} [identity] visitor and account to identify, in the same shape as
29824
+ * `pendo.updateOptions` -- applied only when consent moves a do-not-track visitor to a tier
29825
+ * that permits identity
29583
29826
  * @returns {object} map of product to its resolved value -- `guides` and `identity` are
29584
29827
  * booleans (identity disabled means DNT), `textCollection` is `none`/`whitelist`/`all`,
29585
29828
  * `analytics` and `guideAnalytics` are each an array of allowed event types
@@ -29587,17 +29830,20 @@ function initIdentityEvents() {
29587
29830
  * pendo.updateCookieConsent('strictlyNecessary');
29588
29831
  * @example
29589
29832
  * pendo.updateCookieConsent(['strictlyNecessary', 'functional']);
29833
+ * @example
29834
+ * pendo.updateCookieConsent(['strictlyNecessary', 'functional'], {
29835
+ * visitor: { id: 'put_visitor_id_here', role: 'admin' },
29836
+ * account: { id: 'put_account_id_here' }
29837
+ * });
29590
29838
  */
29591
- function updateCookieConsent(tiers) {
29592
- const enabledProducts = applyConsentTiers(tiers);
29593
- Events['consent:changed'].trigger(enabledProducts);
29594
- log.debug('Cookie consent updated for ' + JSON.stringify(tiers) + '.');
29595
- return enabledProducts;
29839
+ function updateCookieConsent(tiers, identity) {
29840
+ store.dispatch('frames/changeCookieConsent', { tiers });
29841
+ return applyCookieConsent(tiers, identity);
29596
29842
  }
29597
29843
  function initializeCookieConsent(tiers) {
29598
29844
  resetConsentPolicy();
29599
29845
  if (tiers) {
29600
- updateCookieConsent(tiers);
29846
+ applyCookieConsent(tiers);
29601
29847
  }
29602
29848
  }
29603
29849
 
@@ -30277,8 +30523,9 @@ function autoInitialize() {
30277
30523
  log.info('already running');
30278
30524
  return;
30279
30525
  }
30280
- if (window.pendo_options) { // Support pre-2.0 initialization
30281
- initialize(window.pendo_options);
30526
+ const snippetOptions = getLegacyGlobalSnippetOptions();
30527
+ if (snippetOptions) { // Support pre-2.0 initialization
30528
+ initialize(snippetOptions);
30282
30529
  }
30283
30530
  flushCallQueue();
30284
30531
  }
@@ -34129,20 +34376,6 @@ function GuideStep(guide) {
34129
34376
  stepId: step.id,
34130
34377
  isShown: step.isShown()
34131
34378
  });
34132
- // Android and some other mobile frameworks (Cordova webviews) send a resize event when the keyboard opens on focusing a text input
34133
- // due to redrawing the guide on resize this ends up closing the keyboard immediately.
34134
- // We need to ignore resize events in these cases to not redraw yet
34135
- // This idea comes from the 2nd solution of: https://stackoverflow.com/questions/38296574/disappearing-soft-keyboard-in-android-app
34136
- if (step.guideElement && step.guideElement[0]) {
34137
- step.attachEvent(step.guideElement[0], 'focusin', function (e) {
34138
- if (e.target.tagName === 'TEXTAREA' || e.target.tagName === 'INPUT') {
34139
- pendo$1.ignoreResize = true;
34140
- setTimeout$1(function () {
34141
- pendo$1.ignoreResize = false;
34142
- }, 1000);
34143
- }
34144
- });
34145
- }
34146
34379
  };
34147
34380
  /**
34148
34381
  * Hides the Step if shown. Use an argument of an Object containing a
@@ -34369,7 +34602,7 @@ const ErrorLogModule = (function () {
34369
34602
  }
34370
34603
  const errMsg = `[${logEvent}]`;
34371
34604
  if (logEvent.level === LogLevel.Critical) {
34372
- const options = window.pendo_options || {};
34605
+ const options = getLegacyGlobalSnippetOptions() || {};
34373
34606
  logError(errMsg);
34374
34607
  if (pendo$1.logStackTraces !== false && options.logStackTraces !== false && error.stack) {
34375
34608
  writeErrorPOST(`${errMsg}\n${error.stack}`);
@@ -35107,6 +35340,22 @@ var FramesModule = (function () {
35107
35340
  return;
35108
35341
  identifyVisitor(payload.visitorId, payload.accountId);
35109
35342
  },
35343
+ changeCookieConsent(context, payload) {
35344
+ crossFrameChannel.postMessage({
35345
+ action: 'frames/receiveCookieConsent',
35346
+ frameId: context.state.frameId,
35347
+ tiers: payload.tiers
35348
+ });
35349
+ },
35350
+ receiveCookieConsent(context, payload) {
35351
+ if (context.getters.getMyApiKey() !== payload.apiKey)
35352
+ return;
35353
+ if (context.state.frameId === payload.frameId)
35354
+ return;
35355
+ if (isCurrentCookieConsent(payload.tiers))
35356
+ return;
35357
+ applyCookieConsent(payload.tiers);
35358
+ },
35110
35359
  setReady(context, payload) {
35111
35360
  context.commit('setReady', payload.isReady);
35112
35361
  },
@@ -39088,6 +39337,7 @@ const IFrameMonitor = (function () {
39088
39337
  let pendoGlobal;
39089
39338
  let pluginAPI;
39090
39339
  let topDownSync;
39340
+ let identitySync;
39091
39341
  return {
39092
39342
  name: 'IFrameMonitor',
39093
39343
  initialize: init,
@@ -39103,7 +39353,12 @@ const IFrameMonitor = (function () {
39103
39353
  removeHandlers();
39104
39354
  }
39105
39355
  function onChildFrameJoined() {
39106
- syncIdentity();
39356
+ // Consent first. A frame applies these in the order they arrive, and one whose policy
39357
+ // still denies tracking will throw away the identity sent below.
39358
+ syncCookieConsent();
39359
+ if (identitySync) {
39360
+ syncIdentity();
39361
+ }
39107
39362
  }
39108
39363
  function getAgentUrl() {
39109
39364
  const selfHostedUrl = pluginAPI.ConfigReader.get('selfHostedAgentUrl');
@@ -39206,14 +39461,23 @@ const IFrameMonitor = (function () {
39206
39461
  return;
39207
39462
  pluginAPI.store.dispatch('frames/syncIdentity', { visitorId, accountId });
39208
39463
  }
39464
+ function syncCookieConsent() {
39465
+ const tiers = getCookieConsentTiers();
39466
+ if (!tiers)
39467
+ return; // nothing to replay for a subscription that never configured consent
39468
+ pluginAPI.store.dispatch('frames/changeCookieConsent', { tiers });
39469
+ }
39209
39470
  function monitorFrames() {
39210
39471
  if (pluginAPI.ConfigReader.get('autoFrameInstall')) {
39211
39472
  pluginAPI.Events.ready.on(readyHandler);
39212
39473
  }
39213
- if (pluginAPI.ConfigReader.get('frameIdentitySync')) {
39474
+ identitySync = pluginAPI.ConfigReader.get('frameIdentitySync');
39475
+ // A frame that installs itself misses consent changes made before it joined, whether or
39476
+ // not identity is being shared with it.
39477
+ pluginAPI.Events.childFrameJoined.on(onChildFrameJoined);
39478
+ if (identitySync) {
39214
39479
  pluginAPI.Events.metadata.on(metadataHandler);
39215
39480
  pluginAPI.Events.identify.on(identifyHandler);
39216
- pluginAPI.Events.childFrameJoined.on(onChildFrameJoined);
39217
39481
  }
39218
39482
  }
39219
39483
  function removeHandlers() {
@@ -42378,7 +42642,8 @@ class WebAnalytics {
42378
42642
  const { bind } = pendo._;
42379
42643
  this.subscriptions = [
42380
42644
  PluginAPI.attachEvent(PluginAPI.Events, 'eventCaptured', bind(this.addUtmToEvent, this)),
42381
- PluginAPI.attachEvent(PluginAPI.Events, 'sessionChanged', bind(this.sessionChanged, this))
42645
+ PluginAPI.attachEvent(PluginAPI.Events, 'sessionChanged', bind(this.sessionChanged, this)),
42646
+ PluginAPI.attachEvent(PluginAPI.Events, 'consent:changed', bind(this.handleConsentChange, this))
42382
42647
  ];
42383
42648
  this.suffix = this.api.ConfigReader.get('identityStorageSuffix');
42384
42649
  /**
@@ -42390,11 +42655,19 @@ class WebAnalytics {
42390
42655
  * @label WEB_ANALYTICS_STORAGE_KEY
42391
42656
  */
42392
42657
  this.api.agentStorage.registry.addLocal(WEB_ANALYTICS_STORAGE_KEY, { cookieSuffix: this.suffix, isSecure: true });
42393
- let params = this.loadParameters(PluginAPI.agentStorage);
42394
- if (!params || pendo._.isEmpty(params)) {
42658
+ if (PluginAPI.analytics.isUtmConsentDenied()) {
42659
+ return this.clearParameters(PluginAPI.agentStorage);
42660
+ }
42661
+ this.captureParameters();
42662
+ }
42663
+ captureParameters() {
42664
+ if (this.api.analytics.isUtmConsentDenied())
42665
+ return;
42666
+ let params = this.loadParameters(this.api.agentStorage);
42667
+ if (!params || this.pendo._.isEmpty(params)) {
42395
42668
  params = this.extractParameters(window.location.href, document.referrer);
42396
42669
  }
42397
- this.storeParameters(params, PluginAPI.agentStorage);
42670
+ this.storeParameters(params, this.api.agentStorage);
42398
42671
  }
42399
42672
  extractParameters(url, referrer) {
42400
42673
  const locationUrl = new URL(url);
@@ -42450,10 +42723,16 @@ class WebAnalytics {
42450
42723
  }
42451
42724
  }
42452
42725
  storeParameters(params, storage) {
42726
+ if (this.api.analytics.isUtmConsentDenied())
42727
+ return;
42453
42728
  if (this.pendo._.size(params) > 0) {
42454
42729
  storage.write(WEB_ANALYTICS_STORAGE_KEY, JSON.stringify(params));
42455
42730
  }
42456
42731
  }
42732
+ clearParameters(storage) {
42733
+ storage.clear(WEB_ANALYTICS_STORAGE_KEY);
42734
+ storage.clear(WEB_ANALYTICS_STORAGE_KEY, false, '');
42735
+ }
42457
42736
  loadParameters(storage) {
42458
42737
  const storedParamsJson = storage.read(WEB_ANALYTICS_STORAGE_KEY) ||
42459
42738
  storage.read(WEB_ANALYTICS_STORAGE_KEY, false, '');
@@ -42468,6 +42747,8 @@ class WebAnalytics {
42468
42747
  this.subscriptions.length = 0;
42469
42748
  }
42470
42749
  addUtmToEvent(event) {
42750
+ if (this.api.analytics.isUtmConsentDenied())
42751
+ return;
42471
42752
  let utm;
42472
42753
  try {
42473
42754
  utm = this.loadParameters(this.api.agentStorage);
@@ -42481,11 +42762,19 @@ class WebAnalytics {
42481
42762
  }
42482
42763
  sessionChanged() {
42483
42764
  const agentStorage = this.api.agentStorage;
42484
- agentStorage.clear(WEB_ANALYTICS_STORAGE_KEY);
42485
- agentStorage.clear(WEB_ANALYTICS_STORAGE_KEY, false, '');
42765
+ this.clearParameters(agentStorage);
42486
42766
  const params = this.extractParameters(window.location.href, document.referrer);
42487
42767
  this.storeParameters(params, agentStorage);
42488
42768
  }
42769
+ handleConsentChange(event) {
42770
+ const { utm } = event.data[0];
42771
+ if (this.pendo._.isUndefined(utm))
42772
+ return;
42773
+ if (this.api.analytics.isUtmConsentDenied()) {
42774
+ return this.clearParameters(this.api.agentStorage);
42775
+ }
42776
+ this.captureParameters();
42777
+ }
42489
42778
  }
42490
42779
  var WebAnalytics$1 = new WebAnalytics();
42491
42780
 
@@ -51769,14 +52058,15 @@ class Transport {
51769
52058
  }
51770
52059
  }
51771
52060
 
51772
- const CATCHALL_TYPES = ['load', 'click', 'track', 'guideSeen'];
51773
- const AGENTIC_TYPES = ["prompt", "agent_response", "user_reaction"];
51774
52061
  const EVENT_KINDS = {
51775
52062
  AGENTIC: 'agentic',
51776
52063
  CATCHALL: 'catchall',
51777
52064
  DEVLOG: 'devlog',
51778
52065
  RECORDING: 'recording'
51779
52066
  };
52067
+ const CATCHALL_TYPES = ['load', 'click', 'track', 'guideSeen'];
52068
+ const AGENTIC_TYPES = ["prompt", "agent_response", "user_reaction"];
52069
+ const CONSENT_EXEMPT_TYPES = ['guideSeen', EVENT_KINDS.DEVLOG];
51780
52070
  class LiveReplayClient {
51781
52071
  constructor(pendo, api) {
51782
52072
  this.pendo = pendo;
@@ -51784,6 +52074,7 @@ class LiveReplayClient {
51784
52074
  this.ws = null;
51785
52075
  this.sequence = 0;
51786
52076
  this.unsubscribeEventCaptured = null;
52077
+ this.disabled = false;
51787
52078
  }
51788
52079
  buildWsUrl() {
51789
52080
  const url = new URL$1(`${this.pendo.HOST}/data/live-replay/${this.pendo.apiKey}`);
@@ -51796,11 +52087,18 @@ class LiveReplayClient {
51796
52087
  return url.href;
51797
52088
  }
51798
52089
  start(visitorId, onOpen) {
51799
- if (this.ws && this.ws.readyState !== WebSocket.CLOSED)
52090
+ const wsIsOpen = this.ws && this.ws.readyState !== WebSocket.CLOSED;
52091
+ if (wsIsOpen || this.disabled)
51800
52092
  return;
51801
52093
  this.sequence = 0;
51802
52094
  this.unsubscribeEventCaptured = this.api.attachEvent(this.api.Events, 'eventCaptured', this.pendo._.bind(this.forwardEvent, this));
51803
52095
  this.ws = new WebSocket(this.buildWsUrl());
52096
+ if (!this.api.util.isNativeCode(this.ws.send)) {
52097
+ this.disabled = true;
52098
+ this.api.log.warn('Live replay disabled: WebSocket send is not native.');
52099
+ this.stop();
52100
+ return;
52101
+ }
51804
52102
  this.ws.onopen = this.pendo._.bind(this.handleOpen, this, visitorId, onOpen);
51805
52103
  const boundHandleClose = this.pendo._.bind(this.handleClose, this);
51806
52104
  this.ws.onclose = boundHandleClose;
@@ -51858,6 +52156,11 @@ class LiveReplayClient {
51858
52156
  sendRecordingEvent(fields) {
51859
52157
  this.send(Object.assign(Object.assign({}, fields), { kind: EVENT_KINDS.RECORDING, sequence: this.sequence++ }));
51860
52158
  }
52159
+ hasConsentToForward(type) {
52160
+ if (this.pendo._.contains(CONSENT_EXEMPT_TYPES, type))
52161
+ return true;
52162
+ return this.api.analytics.hasConsentForAnalyticsEvent(type);
52163
+ }
51861
52164
  forwardEvent(event) {
51862
52165
  if (!event || !event.data || !event.data.length)
51863
52166
  return;
@@ -51874,6 +52177,8 @@ class LiveReplayClient {
51874
52177
  }
51875
52178
  if (!config.kind)
51876
52179
  return;
52180
+ if (!this.hasConsentToForward(capturedEvent.type))
52181
+ return;
51877
52182
  // other eventCaptured listeners still mutate capturedEvent in place; defer until they're done
51878
52183
  this.pendo._.defer(() => {
51879
52184
  this.send(config);
@@ -52293,6 +52598,7 @@ class SessionRecorder {
52293
52598
  this.logStopReason('CONSENT_DENIED');
52294
52599
  this.stop();
52295
52600
  }
52601
+ this.clearSessionInfo();
52296
52602
  }
52297
52603
  else if (!this.isRecording()) {
52298
52604
  this.start();