@pendo/agent 2.341.0 → 2.343.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.
@@ -3926,6 +3926,17 @@ function shouldWrapHashChange() {
3926
3926
  function isMobileUserAgent() {
3927
3927
  return (/Android|webOS|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i).test(getUA());
3928
3928
  }
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
+ }
3929
3940
  function isSafari(userAgent = getUA()) {
3930
3941
  return userAgent.indexOf('Safari') >= 0 && !isChromium(userAgent);
3931
3942
  }
@@ -3938,6 +3949,7 @@ function isChromium(userAgent = getUA()) {
3938
3949
  const sniffer = {
3939
3950
  supportsHistoryApi: shouldWrapNativeHistory,
3940
3951
  supportsHashChange: shouldWrapHashChange,
3952
+ supportsInert,
3941
3953
  hasEvent(event) {
3942
3954
  // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
3943
3955
  // it. In particular the event is not fired when backspace or delete key are pressed or
@@ -4046,8 +4058,8 @@ let SERVER = '';
4046
4058
  let ASSET_HOST = '';
4047
4059
  let ASSET_PATH = '';
4048
4060
  let DESIGNER_SERVER = '';
4049
- let VERSION = '2.341.0_';
4050
- let PACKAGE_VERSION = '2.341.0';
4061
+ let VERSION = '2.343.0_';
4062
+ let PACKAGE_VERSION = '2.343.0';
4051
4063
  let LOADER = 'xhr';
4052
4064
  /* eslint-enable web-sdk-eslint-rules/no-gulp-env-references */
4053
4065
  /**
@@ -4464,15 +4476,35 @@ class LogEmitter extends EventTarget {
4464
4476
  }
4465
4477
  const log = new LogEmitter();
4466
4478
 
4467
- /** Key used to lookup the consent collection policy in the pendo configuration. */
4468
4479
  const CONSENT_COLLECTION = 'cookieConsentCollection';
4469
- /** Key used to lookup the analytics events in the consent collection policy. */
4470
4480
  const ANALYTICS = 'analytics';
4471
- /** Key used to lookup the guide analytics events in the consent collection policy. */
4472
4481
  const GUIDE_ANALYTICS = 'guideAnalytics';
4473
- /** Key used to lookup the text collection mode in the consent collection policy. */
4474
4482
  const TEXT_COLLECTION = 'textCollection';
4475
4483
  const TEXT_COLLECTION_LEVELS = ['none', 'whitelist', 'all'];
4484
+ const ANALYTICS_EVENT_TYPES = {
4485
+ agentResponse: 'agent_response',
4486
+ clicks: 'click',
4487
+ fieldChanges: 'change',
4488
+ focus: 'focus',
4489
+ formSubmits: 'submit',
4490
+ identify: 'identify',
4491
+ meta: 'meta',
4492
+ pageLoads: 'load',
4493
+ prompt: 'prompt',
4494
+ trackEvents: 'track',
4495
+ userReaction: 'user_reaction'
4496
+ };
4497
+ const GUIDE_EVENT_TYPES = [
4498
+ 'guideActivity',
4499
+ 'guideAdvanced',
4500
+ 'guideDismissed',
4501
+ 'guideNotSeen',
4502
+ 'guideSeen',
4503
+ 'guideSnoozeCanceled',
4504
+ 'guideSnoozed',
4505
+ 'guideTimeout',
4506
+ 'pollResponse'
4507
+ ];
4476
4508
  let enabledProducts = null;
4477
4509
  function getClassification() {
4478
4510
  return ConfigReader.get(CONSENT_COLLECTION) || {};
@@ -4492,30 +4524,59 @@ function warnOnUnconfiguredTiers(grantedTiers, classification) {
4492
4524
  function moreTextPermissive(a, b) {
4493
4525
  return TEXT_COLLECTION_LEVELS.indexOf(b) > TEXT_COLLECTION_LEVELS.indexOf(a) ? b : a;
4494
4526
  }
4495
- function orBoolean(a, b) {
4496
- return a || Boolean(b);
4527
+ function toTextCollectionLevel(mode) {
4528
+ return _.contains(TEXT_COLLECTION_LEVELS, mode) ? mode : TEXT_COLLECTION_LEVELS[0];
4497
4529
  }
4498
- function unionArrays(a, b) {
4499
- return _.union(a, b);
4530
+ function toAnalyticsEventTypes(eventCollection) {
4531
+ return _.reduce(ANALYTICS_EVENT_TYPES, function (types, eventType, key) {
4532
+ if (eventCollection[key] === true) {
4533
+ types.push(eventType);
4534
+ }
4535
+ return types;
4536
+ }, []);
4537
+ }
4538
+ function normalizeTier(tier) {
4539
+ const { visitorIdentity = {}, analytics = {}, guides = {}, sessionReplay = {} } = tier || {};
4540
+ const eventCollection = analytics.eventCollection || {};
4541
+ const analyticsEnabled = analytics.enabled === true;
4542
+ return {
4543
+ identity: visitorIdentity.trackVisitors === true,
4544
+ guides: guides.enabled === true,
4545
+ replay: sessionReplay.enabled === true,
4546
+ analytics: analyticsEnabled ? toAnalyticsEventTypes(eventCollection) : [],
4547
+ guideAnalytics: analyticsEnabled && eventCollection.guideServing === true ? GUIDE_EVENT_TYPES.slice() : [],
4548
+ textCollection: analyticsEnabled ? toTextCollectionLevel(eventCollection.textCollectionMode) : TEXT_COLLECTION_LEVELS[0]
4549
+ };
4550
+ }
4551
+ function deniedProducts() {
4552
+ return {
4553
+ identity: false,
4554
+ guides: false,
4555
+ replay: false,
4556
+ [ANALYTICS]: [],
4557
+ [GUIDE_ANALYTICS]: [],
4558
+ [TEXT_COLLECTION]: TEXT_COLLECTION_LEVELS[0]
4559
+ };
4560
+ }
4561
+ function combineTiers(resolved, granted) {
4562
+ return {
4563
+ identity: resolved.identity || granted.identity,
4564
+ guides: resolved.guides || granted.guides,
4565
+ replay: resolved.replay || granted.replay,
4566
+ [ANALYTICS]: _.union(resolved[ANALYTICS], granted[ANALYTICS]),
4567
+ [GUIDE_ANALYTICS]: _.union(resolved[GUIDE_ANALYTICS], granted[GUIDE_ANALYTICS]),
4568
+ [TEXT_COLLECTION]: moreTextPermissive(resolved[TEXT_COLLECTION], granted[TEXT_COLLECTION])
4569
+ };
4500
4570
  }
4501
- // How a granted tier's value for a product combines with what's been resolved so far.
4502
- // `initial` doubles as what an ungranted tier contributes -- an unlisted product falls
4503
- // back to DEFAULT_RESOLVER, the plain "any granted tier turns it on" boolean rule.
4504
- const RESOLVERS = {
4505
- [TEXT_COLLECTION]: { initial: TEXT_COLLECTION_LEVELS[0], combine: moreTextPermissive },
4506
- [ANALYTICS]: { initial: [], combine: unionArrays },
4507
- [GUIDE_ANALYTICS]: { initial: [], combine: unionArrays }
4508
- };
4509
- const DEFAULT_RESOLVER = { initial: false, combine: orBoolean };
4510
4571
  /**
4511
- * Resolves the granted tiers against the configured product classification.
4572
+ * Resolves the tiers a visitor consented to into what each product is allowed to do.
4512
4573
  *
4513
- * A product the classification mentions anywhere starts at its resolver's `initial` value
4514
- * -- a tier the visitor hasn't opted into contributes that same `initial` value, so it has
4515
- * no say over the product, even to explicitly allow something. Only a granted tier's actual
4516
- * value is combined in, so opting into an additional tier only ever adds capability. Most
4517
- * products are plain booleans (DEFAULT_RESOLVER); `textCollection`, `analytics`, and
4518
- * `guideAnalytics` have their own entries in RESOLVERS since none of them is a boolean.
4574
+ * Every product starts denied and only granted tiers contribute, so a tier the visitor
4575
+ * hasn't opted into has no say -- it can't deny anything, and it can't allow anything.
4576
+ *
4577
+ * A subscription with no cookie consent configured resolves to an empty map instead, which
4578
+ * every read below treats as unconstrained: consent that was never set up can't deny
4579
+ * anything either.
4519
4580
  *
4520
4581
  * @access private
4521
4582
  * @param {string | string[]} tiers tier or tiers the visitor consented to
@@ -4524,26 +4585,22 @@ const DEFAULT_RESOLVER = { initial: false, combine: orBoolean };
4524
4585
  function applyConsentTiers(tiers) {
4525
4586
  const cookieConsentCollection = getClassification();
4526
4587
  const consentedCookieTiers = toTierList(tiers);
4527
- const resolved = {};
4528
4588
  warnOnUnconfiguredTiers(consentedCookieTiers, cookieConsentCollection);
4529
- _.each(cookieConsentCollection, function (products, tier) {
4530
- var isGranted = _.contains(consentedCookieTiers, tier);
4531
- _.each(products, function (level, product) {
4532
- var resolver = RESOLVERS[product] || DEFAULT_RESOLVER;
4533
- var current = _.has(resolved, product) ? resolved[product] : resolver.initial;
4534
- var contributed = isGranted ? level : resolver.initial;
4535
- resolved[product] = resolver.combine(current, contributed);
4536
- });
4537
- });
4538
- enabledProducts = resolved;
4589
+ if (_.isEmpty(cookieConsentCollection)) {
4590
+ enabledProducts = {};
4591
+ return getEnabledProducts();
4592
+ }
4593
+ enabledProducts = _.reduce(consentedCookieTiers, function (resolved, tier) {
4594
+ const granted = cookieConsentCollection[tier];
4595
+ return granted ? combineTiers(resolved, normalizeTier(granted)) : resolved;
4596
+ }, deniedProducts());
4539
4597
  return getEnabledProducts();
4540
4598
  }
4541
4599
  function getEnabledProducts() {
4542
4600
  return enabledProducts ? _.extend({}, enabledProducts) : null;
4543
4601
  }
4544
4602
  /**
4545
- * A product is enabled unless a tier has explicitly resolved it to false -- with no cookie
4546
- * consent configured at all, or a product no tier ever mentions, there's nothing to deny.
4603
+ * A product is enabled only if some granted tier turned it on or cookie consent has not been set.
4547
4604
  *
4548
4605
  * @access private
4549
4606
  * @param {string} product
@@ -4557,6 +4614,9 @@ function isProductEnabled(product) {
4557
4614
  function isTrackingConsentDenied() {
4558
4615
  return !isProductEnabled('identity');
4559
4616
  }
4617
+ function isGuideConsentDenied() {
4618
+ return !isProductEnabled('guides');
4619
+ }
4560
4620
  /**
4561
4621
  * Session replay is denied whenever tracking itself is denied (DNT) -- a visitor
4562
4622
  * who can't be identified shouldn't have their session recorded either -- or when a
@@ -4592,6 +4652,9 @@ function hasConsentForAnalyticsEvent(type) {
4592
4652
  function hasConsentForGuideEvent(type) {
4593
4653
  return hasConsentForProduct(type, GUIDE_ANALYTICS);
4594
4654
  }
4655
+ function resetConsentPolicy() {
4656
+ enabledProducts = null;
4657
+ }
4595
4658
 
4596
4659
  const allowKeys = function () {
4597
4660
  var allowKeys = ConfigReader.get('storage.allowKeys') || '*';
@@ -14353,9 +14416,13 @@ function getPendoCdnHost() {
14353
14416
  return stripScheme(ASSET_HOST);
14354
14417
  return serverHost.replace(appLabel, '$1cdn.');
14355
14418
  }
14356
- // NOTE: this previously was *only* checking the snippet provided config for contentHost
14419
+ // A contentHost set up as a CNAME in app settings is delivered in the hosted config, while a
14420
+ // snippet value is the customer's own override of it. Same precedence getDataHost uses.
14421
+ function getConfiguredContentHost() {
14422
+ return ConfigReader.getLocalConfig('contentHost') || ConfigReader.getHostedConfig('contentHost');
14423
+ }
14357
14424
  function replaceWithContentHost(str) {
14358
- var contentHost = ConfigReader.getLocalConfig('contentHost');
14425
+ var contentHost = getConfiguredContentHost();
14359
14426
  if (!contentHost || !str)
14360
14427
  return str;
14361
14428
  contentHost = normalizeContentHost(contentHost);
@@ -14380,7 +14447,7 @@ function replaceWithContentHost(str) {
14380
14447
  }, str);
14381
14448
  }
14382
14449
  function replaceObjectWithContentHost(obj) {
14383
- var contentHost = ConfigReader.getLocalConfig('contentHost');
14450
+ var contentHost = getConfiguredContentHost();
14384
14451
  if (!contentHost)
14385
14452
  return obj;
14386
14453
  return JSON.parse(replaceWithContentHost(JSON.stringify(obj)));
@@ -22031,7 +22098,9 @@ Badge.behaviors = [
22031
22098
  function InlinePosition() {
22032
22099
  function getShadowDomAdjustedTarget(target) {
22033
22100
  try {
22034
- if (target.shadowRoot && !target.shadowRoot.querySelector('slot')) {
22101
+ // Only a default (unnamed) slot can render a badge appended as a light-DOM
22102
+ // child, so fall back to the shadow root when there isn't one
22103
+ if (target.shadowRoot && !target.shadowRoot.querySelector('slot:not([name])')) {
22035
22104
  return target.shadowRoot;
22036
22105
  }
22037
22106
  }
@@ -22052,7 +22121,7 @@ function InlinePosition() {
22052
22121
  if (targetElem && targetElem.tagName) {
22053
22122
  var tagName = targetElem.tagName.toLowerCase();
22054
22123
  var shadowDomAdjustedTarget = getShadowDomAdjustedTarget(targetElem);
22055
- if (/br|input|img|select|textarea/.test(tagName)) {
22124
+ if (/^(?:br|input|img|select|textarea)$/.test(tagName)) {
22056
22125
  if (badgeElem.parentNode === targetElem.parentNode)
22057
22126
  return;
22058
22127
  // Insert after if targetElem cannot have children
@@ -22143,14 +22212,29 @@ function ClickActivation() {
22143
22212
  }
22144
22213
  stopEvent(evt);
22145
22214
  };
22215
+ // Safari does not focus the badge on mousedown, so focus leaves the app instead.
22216
+ // Apps that re-render on blur can remove the badge before mouseup, and the browser
22217
+ // then generates no click at all. Holding focus where it is avoids that (APP-168412)
22218
+ var preserveFocus = function (evt) {
22219
+ if (evt.preventDefault) {
22220
+ evt.preventDefault();
22221
+ }
22222
+ else {
22223
+ evt.returnValue = false;
22224
+ }
22225
+ };
22146
22226
  badge.after('show', function () {
22147
22227
  if (!attached) {
22148
22228
  attachEvent(badgeElem, 'click', click);
22229
+ if (badge.preserveFocusOnClick) {
22230
+ attachEvent(badgeElem, 'mousedown', preserveFocus);
22231
+ }
22149
22232
  attached = true;
22150
22233
  }
22151
22234
  });
22152
22235
  badge.after('hide', function () {
22153
22236
  detachEvent(badgeElem, 'click', click);
22237
+ detachEvent(badgeElem, 'mousedown', preserveFocus);
22154
22238
  attached = false;
22155
22239
  });
22156
22240
  return badge;
@@ -22621,7 +22705,7 @@ function stepShowingProc(guide, step) {
22621
22705
  return;
22622
22706
  }
22623
22707
  const embedSelector = _.get(guide, 'attributes.embedConfig.selector');
22624
- if (embedSelector && !dom(embedSelector).length) {
22708
+ if (embedSelector && (!dom(embedSelector).length || (guideElement && !isInDocument(guideElement[0])))) {
22625
22709
  step.hide();
22626
22710
  return;
22627
22711
  }
@@ -23590,16 +23674,23 @@ function backdropInertRuntimeScript(step, guide, pendo) {
23590
23674
  return;
23591
23675
  if (step.hasBackdrop) {
23592
23676
  const BuildingBlockGuides = pendo.BuildingBlocks.BuildingBlockGuides;
23593
- const excludeElements = getTargetAreaElementsForBlockOut(step);
23594
- // The preview bar iframe is a sibling of the guide under <body>; inerting it
23595
- // would make preview mode's controls unclickable while the backdrop shows.
23596
- const previewBar = document.getElementById('pendo-preview');
23597
- if (previewBar) {
23598
- excludeElements.push(previewBar);
23599
- }
23600
- const inertElements = BuildingBlockGuides.addInertOutsideGuide(step.guideElement[0], [], excludeElements);
23601
- step.hiddenElements = (step.hiddenElements || []).concat(inertElements);
23677
+ const guideElement = step.guideElement[0];
23678
+ const excludeElements = getExcludedElements(step);
23679
+ const disabledElements = sniffer.supportsInert()
23680
+ ? BuildingBlockGuides.addInertOutsideGuide(guideElement, [], excludeElements)
23681
+ : BuildingBlockGuides.addAriaHiddenOutsideGuide(guideElement, [], excludeElements);
23682
+ step.hiddenElements = (step.hiddenElements || []).concat(disabledElements);
23683
+ }
23684
+ }
23685
+ function getExcludedElements(step) {
23686
+ const excludeElements = getTargetAreaElementsForBlockOut(step);
23687
+ // The preview bar iframe is a sibling of the guide under <body>; inerting it
23688
+ // would make preview mode's controls unclickable while the backdrop shows.
23689
+ const previewBar = document.getElementById('pendo-preview');
23690
+ if (previewBar) {
23691
+ excludeElements.push(previewBar);
23602
23692
  }
23693
+ return excludeElements;
23603
23694
  }
23604
23695
  var backdropInertStep = {
23605
23696
  script: backdropInertRuntimeScript
@@ -25358,6 +25449,9 @@ var initGuides = function (observer) {
25358
25449
  return;
25359
25450
  guides ? enableGuides() : disableGuides();
25360
25451
  });
25452
+ if (isGuideConsentDenied()) {
25453
+ disableGuides();
25454
+ }
25361
25455
  teardownFns.push(onTurbolinksPageLoad(document, function onPageLoad() {
25362
25456
  if (pendoDotUrl.get() === reloadGuides.lastUrl) {
25363
25457
  // Force a reload if Turbolinks replaces the document body without changing the url
@@ -29300,6 +29394,7 @@ const PluginAPI = {
29300
29394
  q,
29301
29395
  SendQueue,
29302
29396
  sessionStorage: agentStorage.getSession(),
29397
+ storageIsDisabled,
29303
29398
  sizeElements,
29304
29399
  store,
29305
29400
  Tombstone,
@@ -29478,6 +29573,34 @@ function initIdentityEvents() {
29478
29573
  };
29479
29574
  }
29480
29575
 
29576
+ /**
29577
+ * Enforces the cookie consent policy a visitor has agreed to. The policy is defined by the subscription's cookie consent configuration,
29578
+ * and is used to determine whether each product the web SDK may run.
29579
+ *
29580
+ * @access public
29581
+ * @category Core
29582
+ * @param {string | string[]} tiers tier or tiers the visitor consented to
29583
+ * @returns {object} map of product to its resolved value -- `guides` and `identity` are
29584
+ * booleans (identity disabled means DNT), `textCollection` is `none`/`whitelist`/`all`,
29585
+ * `analytics` and `guideAnalytics` are each an array of allowed event types
29586
+ * @example
29587
+ * pendo.updateCookieConsent('strictlyNecessary');
29588
+ * @example
29589
+ * pendo.updateCookieConsent(['strictlyNecessary', 'functional']);
29590
+ */
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;
29596
+ }
29597
+ function initializeCookieConsent(tiers) {
29598
+ resetConsentPolicy();
29599
+ if (tiers) {
29600
+ updateCookieConsent(tiers);
29601
+ }
29602
+ }
29603
+
29481
29604
  /*
29482
29605
  * ResourceCenterActivity Event
29483
29606
  *
@@ -29961,6 +30084,8 @@ function initialize(options) {
29961
30084
  }
29962
30085
  // Save the options somewhere
29963
30086
  ConfigReader.setLocalConfig(options);
30087
+ const cookieConsentTiers = _.get(options, 'cookieConsentTiers');
30088
+ initializeCookieConsent(cookieConsentTiers);
29964
30089
  initDataHost();
29965
30090
  exportDataHost(pendo$1);
29966
30091
  const cookieDomain = ConfigReader.get('cookieDomain');
@@ -30932,21 +31057,26 @@ var BuildingBlockGuides = (function () {
30932
31057
  domElem.append(endFocusBumper);
30933
31058
  return [startFocusBumper, endFocusBumper];
30934
31059
  }
30935
- function addAriaHiddenOutsideGuide(elem, hidden = []) {
31060
+ // native contains() can't see into shadow trees, so excluded elements inside a
31061
+ // shadow root (e.g. blockOut target areas) need the shadow-aware walk
31062
+ function containsExcludedElement(elem, excludeElements) {
31063
+ return _.some(excludeElements, (excluded) => excluded && (elem === excluded || contains(elem, excluded)));
31064
+ }
31065
+ function addAriaHiddenOutsideGuide(elem, hidden = [], excludeElements = []) {
30936
31066
  if (!elem || elem === getBody())
30937
31067
  return hidden;
30938
31068
  const siblings = getSiblings(elem);
30939
31069
  _.each(siblings, (s) => {
30940
- if (hidden.indexOf(s) === -1 && s.getAttribute('aria-hidden') !== 'true') {
30941
- const tagName = s.tagName.toLowerCase();
30942
- if (tagName !== 'script' && tagName !== 'style') {
30943
- s.setAttribute('aria-hidden', 'true');
30944
- hidden.push(s);
30945
- }
31070
+ const tagName = s.tagName.toLowerCase();
31071
+ const alreadyHidden = s.getAttribute('aria-hidden') === 'true';
31072
+ const isExcluded = containsExcludedElement(s, excludeElements);
31073
+ if (hidden.indexOf(s) === -1 && !alreadyHidden && !isExcluded && tagName !== 'script' && tagName !== 'style') {
31074
+ s.setAttribute('aria-hidden', 'true');
31075
+ hidden.push(s);
30946
31076
  }
30947
31077
  });
30948
31078
  if (elem.parentNode) {
30949
- addAriaHiddenOutsideGuide(elem.parentNode, hidden);
31079
+ addAriaHiddenOutsideGuide(elem.parentNode, hidden, excludeElements);
30950
31080
  }
30951
31081
  return hidden;
30952
31082
  }
@@ -30960,9 +31090,7 @@ var BuildingBlockGuides = (function () {
30960
31090
  _.each(siblings, (s) => {
30961
31091
  const tagName = s.tagName.toLowerCase();
30962
31092
  const alreadyDisabled = s.getAttribute('inert') === 'true';
30963
- // native contains() can't see into shadow trees, so excluded elements inside a
30964
- // shadow root (e.g. blockOut target areas) need the shadow-aware walk
30965
- const isExcluded = _.some(excludeElements, (excluded) => excluded && (s === excluded || contains(s, excluded)));
31093
+ const isExcluded = containsExcludedElement(s, excludeElements);
30966
31094
  if (hidden.indexOf(s) === -1 && !alreadyDisabled && !isExcluded && tagName !== 'script' && tagName !== 'style') {
30967
31095
  s.setAttribute('inert', 'true');
30968
31096
  hidden.push(s);
@@ -31204,28 +31332,6 @@ logPublic.setActiveContexts = setActiveContexts;
31204
31332
  logPublic.showLogHistory = showLogHistory;
31205
31333
  logPublic.getLoggedContexts = getLoggedContexts;
31206
31334
 
31207
- /**
31208
- * Enforces the cookie consent policy a visitor has agreed to. The policy is defined by the subscription's cookie consent configuration,
31209
- * and is used to determine whether each product the web SDK may run.
31210
- *
31211
- * @access public
31212
- * @category Core
31213
- * @param {string | string[]} tiers tier or tiers the visitor consented to
31214
- * @returns {object} map of product to its resolved value -- `guides` and `identity` are
31215
- * booleans (identity disabled means DNT), `textCollection` is `none`/`whitelist`/`all`,
31216
- * `analytics` and `guideAnalytics` are each an array of allowed event types
31217
- * @example
31218
- * pendo.updateCookieConsent('strictlyNecessary');
31219
- * @example
31220
- * pendo.updateCookieConsent(['strictlyNecessary', 'functional']);
31221
- */
31222
- function updateCookieConsent(tiers) {
31223
- const enabledProducts = applyConsentTiers(tiers);
31224
- Events['consent:changed'].trigger(enabledProducts);
31225
- log.debug('Cookie consent updated for ' + JSON.stringify(tiers) + '.');
31226
- return enabledProducts;
31227
- }
31228
-
31229
31335
  /**
31230
31336
  * Shared helper for event collection
31231
31337
  */
@@ -42707,6 +42813,310 @@ var guideMarkdownUtil = {
42707
42813
  removeMarkdownSyntax
42708
42814
  };
42709
42815
 
42816
+ const AUTO_ADVANCE_DELAY_MS = 250;
42817
+ function isAutoAdvanceRequested(step, _) {
42818
+ return _.get(step, 'attributes.autoAdvance') === true;
42819
+ }
42820
+ // Survey-authored DOM carries no question-type classes, so eligibility keys off
42821
+ // answer-control shape: every required poll id must be radio-backed, which also
42822
+ // refuses a future non-radio type by default.
42823
+ function isStepEligibleInDom(step, _, requiredPollIds) {
42824
+ const guideElement = step && step.guideElement;
42825
+ if (!guideElement)
42826
+ return false;
42827
+ if (guideElement.find('textarea, input:text, input:checkbox, select').length)
42828
+ return false;
42829
+ const pollIds = _.uniq(_.map(guideElement.find('[data-pendo-poll-id]'), (element) => element.getAttribute('data-pendo-poll-id')));
42830
+ if (!pollIds.length)
42831
+ return false;
42832
+ return _.all(pollIds, (pollId) => _.contains(requiredPollIds, pollId) &&
42833
+ guideElement.find(`input:radio[data-pendo-poll-id="${pollId}"]`).length > 0);
42834
+ }
42835
+ function hideSubmitButtons(step, _, submitButtons) {
42836
+ const hiddenButtons = [];
42837
+ _.each(submitButtons, (button) => {
42838
+ const element = step.guideElement.find(`#${button.props.id}`)[0];
42839
+ if (!element)
42840
+ return;
42841
+ // display, not visibility: a reserved-but-invisible box leaves an empty gap
42842
+ // at the foot of the step. Authored styles reach the button inline
42843
+ // (build.js:151), so the original value is kept rather than blanked.
42844
+ hiddenButtons.push({ element, display: element.style.display });
42845
+ element.style.display = 'none';
42846
+ });
42847
+ return function restoreSubmitButtons() {
42848
+ _.each(hiddenButtons, (hiddenButton) => {
42849
+ hiddenButton.element.style.display = hiddenButton.display;
42850
+ });
42851
+ };
42852
+ }
42853
+ // The container's pixel height is measured during render (layout.js) before custom
42854
+ // scripts run, so collapsing a row afterwards leaves the box stale and the step keeps
42855
+ // the empty space. Same reflow pair metadata-substitution uses, scoped to this step so
42856
+ // a second guide on screen is left alone.
42857
+ function reflowGuide(step, _, pendo) {
42858
+ const recalculateGuideHeight = _.get(pendo, 'BuildingBlocks.BuildingBlockGuides.recalculateGuideHeight');
42859
+ try {
42860
+ if (_.isFunction(pendo.flexElement)) {
42861
+ pendo.flexElement(step.guideElement.find(guideMarkdownUtil.containerSelector));
42862
+ }
42863
+ if (_.isFunction(recalculateGuideHeight)) {
42864
+ recalculateGuideHeight(`pendo-g-${step.id}`, step.guideElement);
42865
+ }
42866
+ }
42867
+ catch (error) {
42868
+ pendo.log('[auto-advance] guide reflow failed, the step may keep an empty row', error && error.message);
42869
+ }
42870
+ }
42871
+ function createInertController() {
42872
+ return { isActive: false, notifyAnswered() { }, cancel() { } };
42873
+ }
42874
+ function createAutoAdvanceController(step, pendo, submitButtons, requiredPollIds) {
42875
+ const _ = pendo._;
42876
+ const inert = createInertController();
42877
+ if (!isAutoAdvanceRequested(step, _))
42878
+ return inert;
42879
+ // Only reachable for API-authored guides; choosing between two
42880
+ // submitPollAndGoToStep targets would be a guess.
42881
+ if (_.size(submitButtons) !== 1) {
42882
+ pendo.log(`[auto-advance] not arming: step ${step.id} needs exactly one submit button, found ${_.size(submitButtons)}`);
42883
+ return inert;
42884
+ }
42885
+ if (!isStepEligibleInDom(step, _, requiredPollIds)) {
42886
+ pendo.log(`[auto-advance] not arming: rendered step ${step.id} failed the eligibility re-check`);
42887
+ return inert;
42888
+ }
42889
+ const submitButton = submitButtons[0];
42890
+ // A click binds every action on the node and dispatches one event each
42891
+ // (build.js:205-207); we dispatch one, so a second action would be dropped.
42892
+ if (_.size(submitButton.actions) !== 1) {
42893
+ pendo.log(`[auto-advance] not arming: step ${step.id} submit button carries ${_.size(submitButton.actions)} actions, expected one`);
42894
+ return inert;
42895
+ }
42896
+ const submitAction = _.find(submitButton.actions, (action) => action.action === 'submitPoll' || action.action === 'submitPollAndGoToStep');
42897
+ if (!submitAction)
42898
+ return inert;
42899
+ let hasFired = false;
42900
+ let restoreSubmitButtons = _.noop;
42901
+ function fire() {
42902
+ if (hasFired)
42903
+ return;
42904
+ hasFired = true;
42905
+ try {
42906
+ // Shape-identical to what bindActionToNode emits for a click, so
42907
+ // beforeAdvance consumers intercept an auto-advance the same way.
42908
+ step.eventRouter.eventable.trigger('pendoEvent', {
42909
+ action: submitAction.action,
42910
+ params: submitAction.parameters,
42911
+ step,
42912
+ ignore: !!submitAction.ignore,
42913
+ srcElement: step.guideElement.find(`#${submitButton.props.id}`)[0]
42914
+ });
42915
+ }
42916
+ catch (error) {
42917
+ restoreSubmitButtons();
42918
+ reflowGuide(step, _, pendo);
42919
+ pendo.log('[auto-advance] submit dispatch failed, restored the submit button', error && error.message);
42920
+ }
42921
+ }
42922
+ const debouncedFire = _.debounce(fire, AUTO_ADVANCE_DELAY_MS);
42923
+ // Last, so nothing between here and the return can throw and leave the step
42924
+ // with a hidden button and no controller to un-hide it.
42925
+ restoreSubmitButtons = hideSubmitButtons(step, _, submitButtons);
42926
+ reflowGuide(step, _, pendo);
42927
+ return {
42928
+ isActive: true,
42929
+ notifyAnswered: debouncedFire,
42930
+ cancel: debouncedFire.cancel
42931
+ };
42932
+ }
42933
+
42934
+ const SURVEY_ANSWERS_KEY = 'pendo_survey_answers';
42935
+ const ANSWER_CONTROLS = 'input:radio, input:text, textarea, select';
42936
+ const TEXT_CONTROLS = 'input:text, textarea';
42937
+ // Matches MULTISTEP_CONTINUATION_TIME_LIMIT (walkthrough-guide.js:67), past which a guide is not continued.
42938
+ const ANSWER_TTL_MS = 12 * 60 * 60 * 1000;
42939
+ let pluginApi = null;
42940
+ let inMemoryAnswers = {};
42941
+ let persistedAnswersDropped = false;
42942
+ // A restricted storage.allowKeys silently rejects the session write, so mirror the visit here.
42943
+ const visitedSteps = {};
42944
+ function setPluginApi(api) {
42945
+ pluginApi = api;
42946
+ if (!api || !api.agentStorage)
42947
+ return;
42948
+ /**
42949
+ * Answers the visitor has given on each step of a survey, so a step re-shown
42950
+ * after Previous comes back filled in rather than blank.
42951
+ *
42952
+ * @name pendo_survey_answers
42953
+ * @category Cookies/sessionStorage
42954
+ * @access public
42955
+ * @label SURVEY_ANSWERS_KEY
42956
+ */
42957
+ api.agentStorage.registry.addSession(SURVEY_ANSWERS_KEY);
42958
+ }
42959
+ // getSession() checks the allow-list but not storageIsDisabled, which the cookie path applies.
42960
+ function persistenceAllowed() {
42961
+ const allowed = !!pluginApi &&
42962
+ !!pluginApi.sessionStorage &&
42963
+ typeof pluginApi.storageIsDisabled === 'function' &&
42964
+ !pluginApi.storageIsDisabled();
42965
+ if (allowed) {
42966
+ persistedAnswersDropped = false;
42967
+ }
42968
+ else {
42969
+ dropPersistedAnswers();
42970
+ }
42971
+ return allowed;
42972
+ }
42973
+ // agentStorage.clearAll only walks local keys, so a session key has to be dropped here.
42974
+ function dropPersistedAnswers() {
42975
+ if (persistedAnswersDropped || !pluginApi || !pluginApi.sessionStorage)
42976
+ return;
42977
+ persistedAnswersDropped = true;
42978
+ pluginApi.sessionStorage.removeItem(SURVEY_ANSWERS_KEY);
42979
+ }
42980
+ function readAll() {
42981
+ if (!persistenceAllowed())
42982
+ return inMemoryAnswers;
42983
+ try {
42984
+ return JSON.parse(pluginApi.sessionStorage.getItem(SURVEY_ANSWERS_KEY)) || {};
42985
+ }
42986
+ catch (error) {
42987
+ return {};
42988
+ }
42989
+ }
42990
+ function writeAll(answers) {
42991
+ if (!persistenceAllowed()) {
42992
+ inMemoryAnswers = answers;
42993
+ return;
42994
+ }
42995
+ pluginApi.sessionStorage.setItem(SURVEY_ANSWERS_KEY, JSON.stringify(answers));
42996
+ }
42997
+ // identify() sets the new id before the reload tears the step down, so resolve this once per render.
42998
+ function visitorKey(pendo) {
42999
+ return (pendo._.isFunction(pendo.get_visitor_id) && pendo.get_visitor_id()) || 'unknown';
43000
+ }
43001
+ function stateKey(step, guide, visitor) {
43002
+ return `${visitor}:${guide.id}:${step.id}`;
43003
+ }
43004
+ // Host pages reassign Date.now to return a Date, which JSON writes out as a string.
43005
+ function getNow() {
43006
+ return new Date().getTime();
43007
+ }
43008
+ function isFresh(writtenAt) {
43009
+ return !!writtenAt && (getNow() - writtenAt) <= ANSWER_TTL_MS;
43010
+ }
43011
+ function readRecord(step, guide, visitor) {
43012
+ const forVisitor = readAll()[visitor];
43013
+ const forGuide = forVisitor && forVisitor[guide.id];
43014
+ const record = (forGuide && forGuide[step.id]) || null;
43015
+ return isFresh(record && record.at) ? record : null;
43016
+ }
43017
+ function updateRecord(step, guide, visitor, changes) {
43018
+ const all = readAll();
43019
+ const current = readRecord(step, guide, visitor) || {};
43020
+ if (!all[visitor])
43021
+ all[visitor] = {};
43022
+ if (!all[visitor][guide.id])
43023
+ all[visitor][guide.id] = {};
43024
+ all[visitor][guide.id][step.id] = {
43025
+ at: getNow(),
43026
+ visited: changes.visited || current.visited || 0,
43027
+ answers: changes.answers || current.answers || {}
43028
+ };
43029
+ writeAll(all);
43030
+ }
43031
+ // Previous only renders after an auto-advance step, so any other guide needs none of this.
43032
+ function guideAutoAdvances(guide, _) {
43033
+ return _.any(_.get(guide, 'steps', []), (step) => isAutoAdvanceRequested(step, _));
43034
+ }
43035
+ function pollIdFor(control) {
43036
+ let node = control;
43037
+ while (node && node.getAttribute) {
43038
+ const pollId = node.getAttribute('data-pendo-poll-id');
43039
+ if (pollId)
43040
+ return pollId;
43041
+ node = node.parentNode;
43042
+ }
43043
+ return null;
43044
+ }
43045
+ // submitPoll never reads a checkbox back (event-router.js:264), so restoring one shows nothing sent.
43046
+ function isRadio(control) {
43047
+ return control.type === 'radio';
43048
+ }
43049
+ function collectAnswers(step, pendo) {
43050
+ const answers = {};
43051
+ pendo._.each(step.guideElement.find(ANSWER_CONTROLS), (control) => {
43052
+ const pollId = pollIdFor(control);
43053
+ // One value per poll id, first in document order, as submitPoll reads it.
43054
+ if (!pollId || answers[pollId])
43055
+ return;
43056
+ if (isRadio(control) && !control.checked)
43057
+ return;
43058
+ if (!control.value)
43059
+ return;
43060
+ answers[pollId] = [control.value];
43061
+ });
43062
+ return answers;
43063
+ }
43064
+ // No event dispatched: a restored answer must not reach the gate listeners or re-arm auto-advance.
43065
+ function restoreAnswers(step, pendo, answers) {
43066
+ pendo._.each(step.guideElement.find(ANSWER_CONTROLS), (control) => {
43067
+ const pollId = pollIdFor(control);
43068
+ const values = pollId && answers[pollId];
43069
+ if (!values)
43070
+ return;
43071
+ if (isRadio(control)) {
43072
+ if (pendo._.contains(values, control.value))
43073
+ control.checked = true;
43074
+ return;
43075
+ }
43076
+ control.value = values[0];
43077
+ });
43078
+ }
43079
+ function isRevisit(step, guide, pendo) {
43080
+ if (!guideAutoAdvances(guide, pendo._))
43081
+ return false;
43082
+ const visitor = visitorKey(pendo);
43083
+ if (isFresh(visitedSteps[stateKey(step, guide, visitor)]))
43084
+ return true;
43085
+ const record = readRecord(step, guide, visitor);
43086
+ return !!record && (!!record.visited || !pendo._.isEmpty(record.answers));
43087
+ }
43088
+ const AnswerCache = {
43089
+ name: 'AnswerCache',
43090
+ script(step, guide, pendo) {
43091
+ const visitor = visitorKey(pendo);
43092
+ const record = readRecord(step, guide, visitor);
43093
+ restoreAnswers(step, pendo, (record && record.answers) || {});
43094
+ // Marked on leaving, not on render: sessionStorage survives a reload, which is not a revisit.
43095
+ this.on('beforeUnmount', (event) => {
43096
+ if (event && event.reason === 'hidden')
43097
+ return;
43098
+ visitedSteps[stateKey(step, guide, visitor)] = getNow();
43099
+ updateRecord(step, guide, visitor, { visited: 1 });
43100
+ });
43101
+ const stepRoot = step.guideElement[0];
43102
+ // change does not bubble on legacy browsers; it is here only for keyboard selection.
43103
+ step.attachEvent(stepRoot, 'click', capture);
43104
+ step.attachEvent(stepRoot, 'change', capture);
43105
+ pendo._.each(step.guideElement.find(TEXT_CONTROLS), (control) => {
43106
+ step.attachEvent(control, 'input', capture);
43107
+ });
43108
+ function capture() {
43109
+ updateRecord(step, guide, visitor, { answers: collectAnswers(step, pendo) });
43110
+ }
43111
+ },
43112
+ test(step, guide, pendo) {
43113
+ const guideElement = step && step.guideElement;
43114
+ return !!guideElement &&
43115
+ guideAutoAdvances(guide, pendo._) &&
43116
+ guideElement.find(ANSWER_CONTROLS).length > 0;
43117
+ }
43118
+ };
43119
+
42710
43120
  function getZoneSafeMethod(_, method, target = window) {
42711
43121
  var zoneSymbol = '__symbol__';
42712
43122
  /* global Zone */
@@ -43183,124 +43593,6 @@ function substitutionIcon(color, size) {
43183
43593
  13.7239 15.5 14V17Z" fill="${color}"></path> </g></svg></div>`);
43184
43594
  }
43185
43595
 
43186
- const AUTO_ADVANCE_DELAY_MS = 250;
43187
- function isAutoAdvanceRequested(step, _) {
43188
- return _.get(step, 'attributes.autoAdvance') === true;
43189
- }
43190
- // Survey-authored DOM carries no question-type classes, so eligibility keys off
43191
- // answer-control shape: every required poll id must be radio-backed, which also
43192
- // refuses a future non-radio type by default.
43193
- function isStepEligibleInDom(step, _, requiredPollIds) {
43194
- const guideElement = step && step.guideElement;
43195
- if (!guideElement)
43196
- return false;
43197
- if (guideElement.find('textarea, input:text, input:checkbox, select').length)
43198
- return false;
43199
- const pollIds = _.uniq(_.map(guideElement.find('[data-pendo-poll-id]'), (element) => element.getAttribute('data-pendo-poll-id')));
43200
- if (!pollIds.length)
43201
- return false;
43202
- return _.all(pollIds, (pollId) => _.contains(requiredPollIds, pollId) &&
43203
- guideElement.find(`input:radio[data-pendo-poll-id="${pollId}"]`).length > 0);
43204
- }
43205
- function hideSubmitButtons(step, _, submitButtons) {
43206
- const hiddenButtons = [];
43207
- _.each(submitButtons, (button) => {
43208
- const element = step.guideElement.find(`#${button.props.id}`)[0];
43209
- if (!element)
43210
- return;
43211
- // display, not visibility: a reserved-but-invisible box leaves an empty gap
43212
- // at the foot of the step. Authored styles reach the button inline
43213
- // (build.js:151), so the original value is kept rather than blanked.
43214
- hiddenButtons.push({ element, display: element.style.display });
43215
- element.style.display = 'none';
43216
- });
43217
- return function restoreSubmitButtons() {
43218
- _.each(hiddenButtons, (hiddenButton) => {
43219
- hiddenButton.element.style.display = hiddenButton.display;
43220
- });
43221
- };
43222
- }
43223
- // The container's pixel height is measured during render (layout.js) before custom
43224
- // scripts run, so collapsing a row afterwards leaves the box stale and the step keeps
43225
- // the empty space. Same reflow pair metadata-substitution uses, scoped to this step so
43226
- // a second guide on screen is left alone.
43227
- function reflowGuide(step, _, pendo) {
43228
- const recalculateGuideHeight = _.get(pendo, 'BuildingBlocks.BuildingBlockGuides.recalculateGuideHeight');
43229
- try {
43230
- if (_.isFunction(pendo.flexElement)) {
43231
- pendo.flexElement(step.guideElement.find(guideMarkdownUtil.containerSelector));
43232
- }
43233
- if (_.isFunction(recalculateGuideHeight)) {
43234
- recalculateGuideHeight(`pendo-g-${step.id}`, step.guideElement);
43235
- }
43236
- }
43237
- catch (error) {
43238
- pendo.log('[auto-advance] guide reflow failed, the step may keep an empty row', error && error.message);
43239
- }
43240
- }
43241
- function createInertController() {
43242
- return { isActive: false, notifyAnswered() { }, cancel() { } };
43243
- }
43244
- function createAutoAdvanceController(step, pendo, submitButtons, requiredPollIds) {
43245
- const _ = pendo._;
43246
- const inert = createInertController();
43247
- if (!isAutoAdvanceRequested(step, _))
43248
- return inert;
43249
- // Only reachable for API-authored guides; choosing between two
43250
- // submitPollAndGoToStep targets would be a guess.
43251
- if (_.size(submitButtons) !== 1) {
43252
- pendo.log(`[auto-advance] not arming: step ${step.id} needs exactly one submit button, found ${_.size(submitButtons)}`);
43253
- return inert;
43254
- }
43255
- if (!isStepEligibleInDom(step, _, requiredPollIds)) {
43256
- pendo.log(`[auto-advance] not arming: rendered step ${step.id} failed the eligibility re-check`);
43257
- return inert;
43258
- }
43259
- const submitButton = submitButtons[0];
43260
- // A click binds every action on the node and dispatches one event each
43261
- // (build.js:205-207); we dispatch one, so a second action would be dropped.
43262
- if (_.size(submitButton.actions) !== 1) {
43263
- pendo.log(`[auto-advance] not arming: step ${step.id} submit button carries ${_.size(submitButton.actions)} actions, expected one`);
43264
- return inert;
43265
- }
43266
- const submitAction = _.find(submitButton.actions, (action) => action.action === 'submitPoll' || action.action === 'submitPollAndGoToStep');
43267
- if (!submitAction)
43268
- return inert;
43269
- let hasFired = false;
43270
- let restoreSubmitButtons = _.noop;
43271
- function fire() {
43272
- if (hasFired)
43273
- return;
43274
- hasFired = true;
43275
- try {
43276
- // Shape-identical to what bindActionToNode emits for a click, so
43277
- // beforeAdvance consumers intercept an auto-advance the same way.
43278
- step.eventRouter.eventable.trigger('pendoEvent', {
43279
- action: submitAction.action,
43280
- params: submitAction.parameters,
43281
- step,
43282
- ignore: !!submitAction.ignore,
43283
- srcElement: step.guideElement.find(`#${submitButton.props.id}`)[0]
43284
- });
43285
- }
43286
- catch (error) {
43287
- restoreSubmitButtons();
43288
- reflowGuide(step, _, pendo);
43289
- pendo.log('[auto-advance] submit dispatch failed, restored the submit button', error && error.message);
43290
- }
43291
- }
43292
- const debouncedFire = _.debounce(fire, AUTO_ADVANCE_DELAY_MS);
43293
- // Last, so nothing between here and the return can throw and leave the step
43294
- // with a hidden button and no controller to un-hide it.
43295
- restoreSubmitButtons = hideSubmitButtons(step, _, submitButtons);
43296
- reflowGuide(step, _, pendo);
43297
- return {
43298
- isActive: true,
43299
- notifyAnswered: debouncedFire,
43300
- cancel: debouncedFire.cancel
43301
- };
43302
- }
43303
-
43304
43596
  const requiredElement = '<span class="_pendo-required-indicator" style="color:red; font-style:italic;" title="Question is required"> *</span>';
43305
43597
  const requiredSyntax = '{required/}';
43306
43598
  const RequiredQuestions = {
@@ -43326,9 +43618,14 @@ const RequiredQuestions = {
43326
43618
  }
43327
43619
  });
43328
43620
  }
43621
+ // Restored answers reach no listener, and the controller is still inert here.
43622
+ const revisited = isRevisit(step, guide, pendo);
43623
+ if (revisited) {
43624
+ evaluateRequiredQuestions(requiredQuestions);
43625
+ }
43329
43626
  // Armed last, after the listeners above: runCustomScripts abandons the rest of
43330
43627
  // this script on a throw, and the submit button is already disabled by now.
43331
- if (isAutoAdvanceRequested(step, pendo._)) {
43628
+ if (isAutoAdvanceRequested(step, pendo._) && !revisited) {
43332
43629
  this.on('beforeUnmount', () => autoAdvance.cancel());
43333
43630
  try {
43334
43631
  autoAdvance = createAutoAdvanceController(step, pendo, submitButtons, requiredPollIds);
@@ -43679,7 +43976,9 @@ function GuideMarkdown() {
43679
43976
  const markdownScriptsEnabled = configReader.get(GUIDEMARKDOWN_CONFIG);
43680
43977
  if (!markdownScriptsEnabled)
43681
43978
  return;
43682
- guideMarkdown = [PollBranching, MetadataSubstitution, RequiredQuestions, SkipToEligibleStep];
43979
+ setPluginApi(PluginAPI);
43980
+ // AnswerCache first: it restores the answers RequiredQuestions reads the gate against.
43981
+ guideMarkdown = [AnswerCache, PollBranching, MetadataSubstitution, RequiredQuestions, SkipToEligibleStep];
43683
43982
  if (!pendo.designer) {
43684
43983
  pendo._.each(guideMarkdown, function (script) {
43685
43984
  PluginAPI.GlobalRuntime.addGlobalScript(script);
@@ -51470,11 +51769,13 @@ class Transport {
51470
51769
  }
51471
51770
  }
51472
51771
 
51473
- const CATCHALL_TYPES = ['load', 'click', 'track'];
51772
+ const CATCHALL_TYPES = ['load', 'click', 'track', 'guideSeen'];
51773
+ const AGENTIC_TYPES = ["prompt", "agent_response", "user_reaction"];
51474
51774
  const EVENT_KINDS = {
51475
- catchall: 'catchall',
51476
- devlog: 'devlog',
51477
- recording: 'recording'
51775
+ AGENTIC: 'agentic',
51776
+ CATCHALL: 'catchall',
51777
+ DEVLOG: 'devlog',
51778
+ RECORDING: 'recording'
51478
51779
  };
51479
51780
  class LiveReplayClient {
51480
51781
  constructor(pendo, api) {
@@ -51555,18 +51856,21 @@ class LiveReplayClient {
51555
51856
  catch (_a) { }
51556
51857
  }
51557
51858
  sendRecordingEvent(fields) {
51558
- this.send(Object.assign(Object.assign({}, fields), { kind: EVENT_KINDS.recording, sequence: this.sequence++ }));
51859
+ this.send(Object.assign(Object.assign({}, fields), { kind: EVENT_KINDS.RECORDING, sequence: this.sequence++ }));
51559
51860
  }
51560
51861
  forwardEvent(event) {
51561
51862
  if (!event || !event.data || !event.data.length)
51562
51863
  return;
51563
51864
  const capturedEvent = event.data[0];
51564
51865
  const config = { event: capturedEvent };
51866
+ if (this.pendo._.contains(AGENTIC_TYPES, capturedEvent.type)) {
51867
+ config.kind = EVENT_KINDS.AGENTIC;
51868
+ }
51565
51869
  if (this.pendo._.contains(CATCHALL_TYPES, capturedEvent.type)) {
51566
- config.kind = EVENT_KINDS.catchall;
51870
+ config.kind = EVENT_KINDS.CATCHALL;
51567
51871
  }
51568
- if (capturedEvent.type === EVENT_KINDS.devlog) {
51569
- config.kind = EVENT_KINDS.devlog;
51872
+ if (capturedEvent.type === EVENT_KINDS.DEVLOG) {
51873
+ config.kind = EVENT_KINDS.DEVLOG;
51570
51874
  }
51571
51875
  if (!config.kind)
51572
51876
  return;
@@ -52153,7 +52457,6 @@ class SessionRecorder {
52153
52457
  var bind = this.pendo._.bind;
52154
52458
  this.visitorConfig = visitorConfig;
52155
52459
  this.clearOldSessionId();
52156
- this.liveReplayClient.onLiveFlag(visitorConfig.live, this.visitorId);
52157
52460
  this.sendQueue.start();
52158
52461
  this.interval = setInterval(() => {
52159
52462
  if (!this.sendQueue.failed()) {
@@ -52166,6 +52469,7 @@ class SessionRecorder {
52166
52469
  errorHandler: this.errorHandler
52167
52470
  }, config));
52168
52471
  this._refreshIds();
52472
+ this.liveReplayClient.onLiveFlag(visitorConfig.live, this.visitorId, () => this.snapshot());
52169
52473
  this.onRecordingStart();
52170
52474
  }
52171
52475
  catch (e) {