@pendo/agent 2.340.0 → 2.342.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.340.0_';
4050
- let PACKAGE_VERSION = '2.340.0';
4061
+ let VERSION = '2.342.0_';
4062
+ let PACKAGE_VERSION = '2.342.0';
4051
4063
  let LOADER = 'xhr';
4052
4064
  /* eslint-enable web-sdk-eslint-rules/no-gulp-env-references */
4053
4065
  /**
@@ -14305,6 +14317,334 @@ var XHRGuideLoader = {
14305
14317
  }
14306
14318
  };
14307
14319
 
14320
+ function base32Encode(uint8array) {
14321
+ var alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
14322
+
14323
+ var length = uint8array.length;
14324
+
14325
+ var bits = 0;
14326
+ var value = 0;
14327
+ var output = '';
14328
+
14329
+ for (var i = 0; i < length; i++) {
14330
+ value = (value << 8) | uint8array[i];
14331
+ bits += 8;
14332
+
14333
+ while (bits >= 5) {
14334
+ output += alphabet[(value >>> (bits - 5)) & 31];
14335
+ bits -= 5;
14336
+ }
14337
+ }
14338
+
14339
+ if (bits > 0) {
14340
+ output += alphabet[(value << (5 - bits)) & 31];
14341
+ }
14342
+
14343
+ return output;
14344
+ }
14345
+
14346
+ // A CNAME contentHost is commonly configured without a scheme, which would leave the rewritten
14347
+ // url relative to the current page rather than absolute. Leading-slash values are deliberate and
14348
+ // kept as-is: '//host' is protocol-relative, '/path' is same-origin (see fixContentHostUrl).
14349
+ function normalizeContentHost(contentHost) {
14350
+ if (/:\/\//.test(contentHost) || /^\//.test(contentHost))
14351
+ return contentHost;
14352
+ return 'https://' + contentHost;
14353
+ }
14354
+ function stripScheme(host) {
14355
+ return host.replace(/^https?:\/\//, '');
14356
+ }
14357
+ // Self hosted installs aim assetHost at their own host, or drop it entirely - the npm package
14358
+ // deletes it for the assets option - so it cannot locate our CDN. SERVER survives either way, and
14359
+ // app, cdn and static are sibling labels in every environment's domain template. Builds that point
14360
+ // SERVER somewhere other than an app host (--env=local) are left with assetHost.
14361
+ function getPendoCdnHost() {
14362
+ var appLabel = /(^|\.)app\./;
14363
+ var serverHost = stripScheme(SERVER);
14364
+ if (!appLabel.test(serverHost))
14365
+ return stripScheme(ASSET_HOST);
14366
+ return serverHost.replace(appLabel, '$1cdn.');
14367
+ }
14368
+ // A contentHost set up as a CNAME in app settings is delivered in the hosted config, while a
14369
+ // snippet value is the customer's own override of it. Same precedence getDataHost uses.
14370
+ function getConfiguredContentHost() {
14371
+ return ConfigReader.getLocalConfig('contentHost') || ConfigReader.getHostedConfig('contentHost');
14372
+ }
14373
+ function replaceWithContentHost(str) {
14374
+ var contentHost = getConfiguredContentHost();
14375
+ if (!contentHost || !str)
14376
+ return str;
14377
+ contentHost = normalizeContentHost(contentHost);
14378
+ var cdnHost = getPendoCdnHost();
14379
+ var assetHost = stripScheme(ASSET_HOST);
14380
+ // Content proxy hosts are the environment's domain with static in the variable label, which is
14381
+ // not always the leading one - us1 is content-<subId>.us1.static.pendo.io
14382
+ var staticHost = cdnHost.replace(/(^|\.)cdn\./, '$1static.');
14383
+ // An unknown host has to be dropped rather than interpolated: an empty one leaves a pattern
14384
+ // that matches the // of every absolute url
14385
+ var hostPatterns = [
14386
+ SUBSCRIPTION_BUCKET && `${escapeRegExp(SUBSCRIPTION_BUCKET)}-\\d+\\.storage\\.googleapis\\.com`,
14387
+ GLOBAL_BUCKET && `${escapeRegExp(GLOBAL_BUCKET)}\\.storage\\.googleapis\\.com`,
14388
+ cdnHost && `content-\\d+\\.${escapeRegExp(staticHost)}`,
14389
+ cdnHost && escapeRegExp(cdnHost),
14390
+ assetHost && assetHost !== cdnHost && escapeRegExp(assetHost)
14391
+ ];
14392
+ return _.reduce(hostPatterns, function (rewritten, hostPattern) {
14393
+ if (!hostPattern)
14394
+ return rewritten;
14395
+ return rewritten.replace(new RegExp(`(https:)?\\/\\/${hostPattern}`, 'g'), contentHost);
14396
+ }, str);
14397
+ }
14398
+ function replaceObjectWithContentHost(obj) {
14399
+ var contentHost = getConfiguredContentHost();
14400
+ if (!contentHost)
14401
+ return obj;
14402
+ return JSON.parse(replaceWithContentHost(JSON.stringify(obj)));
14403
+ }
14404
+ // APP-37205 we're only checking the remote contentHost config setting
14405
+ function getContentHostRegex() {
14406
+ var ch = escapeRegExp(ConfigReader.getHostedConfig('contentHost'));
14407
+ if (ch)
14408
+ return ch + '*';
14409
+ }
14410
+ /**
14411
+ * @access private
14412
+ * @param {Function} isValidGuideResponse
14413
+ * @param {object} guideResponse
14414
+ */
14415
+ function isValidGuideResponse(guideResponse) {
14416
+ // Is it in proper JSON format/an object? If guideResponse does not have the 'type', 'props', or the 'children'
14417
+ // prop, we should also return false.
14418
+ if (!(_.isObject(guideResponse) && guideResponse.hasOwnProperty('type') &&
14419
+ guideResponse.hasOwnProperty('children') && guideResponse.hasOwnProperty('props'))) {
14420
+ return false;
14421
+ }
14422
+ return true;
14423
+ }
14424
+ var ContentLoader = (function (pendo) {
14425
+ var cache = {};
14426
+ // For APP-37205, we're verifing that the result is what it is. I know.
14427
+ var replaceWithContentHostAndVerify = _.compose(function (url) {
14428
+ var test = getContentHostRegex();
14429
+ if (test) {
14430
+ var v = new RegExp(test);
14431
+ if (!v.test(url)) {
14432
+ log.debug('Failed to verify URL: ' + url + ' vs ' + test);
14433
+ }
14434
+ }
14435
+ return url;
14436
+ }, replaceWithContentHost);
14437
+ /**
14438
+ * @access private
14439
+ * @typedef {object} ContentRequest
14440
+ * @param {string} id uniquely identifies the container
14441
+ * @param {string} contentUrl
14442
+ * @param {string} contentUrlCss
14443
+ * @param {string} contentUrlJs
14444
+ */
14445
+ /**
14446
+ * @access private
14447
+ * @typedef {object} ContentEntry
14448
+ * @param {string} content The raw content as a string
14449
+ * @param {Function} template The template function compiled from the raw content
14450
+ * @param {Function} script The javascript to execute
14451
+ */
14452
+ /**
14453
+ * Loads the content for the given container
14454
+ *
14455
+ * @access private
14456
+ * @param {ContentRequest} contentContainer
14457
+ * @returns {Promise.<ContentEntry>} Resolved when all of the content is loaded
14458
+ */
14459
+ function load(contentContainer) {
14460
+ try {
14461
+ var cacheEntry = cache[contentContainer.id];
14462
+ if (cacheEntry && cacheEntry.language !== contentContainer.language) {
14463
+ delete cache[contentContainer.id];
14464
+ }
14465
+ if (!cache[contentContainer.id]) {
14466
+ var promises = [];
14467
+ var entry = {
14468
+ deferred: {},
14469
+ language: contentContainer.language
14470
+ };
14471
+ var fetchedDomContent = false;
14472
+ cache[contentContainer.id] = entry;
14473
+ if (XHRGuideLoader.usesXhr() && contentContainer.domUrl) {
14474
+ entry.deferred.domJson = q.defer();
14475
+ ajax.get(replaceWithContentHostAndVerify(contentContainer.domUrl)).then(function (result) {
14476
+ entry.domJson = replaceObjectWithContentHost(result.data);
14477
+ var guideResponse = result.data;
14478
+ if (isValidGuideResponse(guideResponse)) {
14479
+ entry.deferred.domJson.resolve();
14480
+ }
14481
+ else {
14482
+ // Log that the guide was invalid
14483
+ // handle promise
14484
+ log.critical(new Error('Response from the guide was not in proper JSON format'));
14485
+ entry.deferred.domJson.reject();
14486
+ }
14487
+ }).catch(function (e) {
14488
+ log.critical(e);
14489
+ Events.resourceFetchFail.trigger(contentContainer, e);
14490
+ entry.deferred.domJson.reject('Failed to load content');
14491
+ });
14492
+ promises.push(entry.deferred.domJson.promise);
14493
+ fetchedDomContent = true;
14494
+ }
14495
+ else if (contentContainer.domJsonpUrl) {
14496
+ entry.deferred.domJson = q.defer();
14497
+ var jsonpNode = loadResource(replaceWithContentHostAndVerify(contentContainer.domJsonpUrl), function (err) {
14498
+ dom.removeNode(jsonpNode);
14499
+ if (err) {
14500
+ Events.resourceFetchFail.trigger(contentContainer, err);
14501
+ return;
14502
+ }
14503
+ }, true);
14504
+ promises.push(entry.deferred.domJson.promise);
14505
+ fetchedDomContent = true;
14506
+ }
14507
+ if (contentContainer.contentUrlJs && !ConfigReader.get('preventCodeInjection')) {
14508
+ // Content, template, and script for 2.1+ agents
14509
+ entry.deferred.content = q.defer();
14510
+ var scriptNode = loadResource(replaceWithContentHostAndVerify(lightningRedirect(contentContainer.contentUrlJs)), function (err) {
14511
+ dom.removeNode(scriptNode);
14512
+ if (err) {
14513
+ Events.resourceFetchFail.trigger(contentContainer, err);
14514
+ return;
14515
+ }
14516
+ }, true);
14517
+ promises.push(entry.deferred.content.promise);
14518
+ // Style
14519
+ if (contentContainer.contentUrlCss) {
14520
+ entry.deferred.css = q.defer();
14521
+ loadResource({ url: replaceWithContentHostAndVerify(contentContainer.contentUrlCss), type: 'text/css' }, function () {
14522
+ entry.deferred.css.resolve();
14523
+ });
14524
+ promises.push(entry.deferred.css.promise);
14525
+ }
14526
+ }
14527
+ else if (contentContainer.contentUrl && !ConfigReader.get('preventCodeInjection')) {
14528
+ // Content with inlined script and css (2.0.x agents, or guides that have not migrated to the 2.1 format yet)
14529
+ entry.deferred.content = q.defer();
14530
+ var contentNode = loadResource(addExtensionToUrl(replaceWithContentHostAndVerify(contentContainer.contentUrl), '.js'), function (err) {
14531
+ dom.removeNode(contentNode);
14532
+ if (err) {
14533
+ Events.resourceFetchFail.trigger(contentContainer, err);
14534
+ return;
14535
+ }
14536
+ }, true);
14537
+ promises.push(entry.deferred.content.promise);
14538
+ }
14539
+ else if (!fetchedDomContent) {
14540
+ return q.reject('fetchedDomContent is still false');
14541
+ }
14542
+ entry.deferred.promise = q.all(promises).then(function () {
14543
+ return _.omit(entry, 'deferred');
14544
+ });
14545
+ }
14546
+ return cache[contentContainer.id].deferred.promise;
14547
+ }
14548
+ catch (e) {
14549
+ Events.resourceFetchFail.trigger(contentContainer, e);
14550
+ throw e;
14551
+ }
14552
+ }
14553
+ // Clears the content cache. Used for testing.
14554
+ function reset() {
14555
+ cache = {};
14556
+ }
14557
+ /**
14558
+ * JSONP-style callback for loading guide content
14559
+ * from script resources. Step content stored in
14560
+ * GCS is wrapped with pendo.guideContent(...)
14561
+ *
14562
+ * @access private
14563
+ * @param {string} guideId
14564
+ * @param {string} stepId
14565
+ * @param {string} content
14566
+ * @param {Function} templateFn
14567
+ * @param {Function} scriptFn
14568
+ */
14569
+ function guideContent(guideId, stepId, content, templateFn, scriptFn) {
14570
+ if (_.isString(content)) {
14571
+ content = preprocessGuideContent(content, guideId, stepId);
14572
+ if (!_.isFunction(templateFn)) {
14573
+ if (shouldCreateTemplate(content)) {
14574
+ templateFn = _.template(content);
14575
+ }
14576
+ else {
14577
+ templateFn = _.constant(content);
14578
+ }
14579
+ }
14580
+ var entry = cache[guideId + stepId];
14581
+ if (entry && entry.deferred.content) {
14582
+ entry.content = content;
14583
+ entry.template = templateFn;
14584
+ entry.script = scriptFn;
14585
+ entry.deferred.content.resolve();
14586
+ }
14587
+ }
14588
+ }
14589
+ // preprocess content to remove simple guide and step IDs templating
14590
+ function removeStandardTemplating(content, guideId, stepId) {
14591
+ const stdTemplating = `<% if (typeof guide !== 'undefined') { %>
14592
+ var guide = pendo.findGuideById('<%= guide.id %>');
14593
+ var step = guide && guide.findStepById('<%= step.id %>');
14594
+ <% } %>`;
14595
+ const sansTemplating = `var guide = pendo.findGuideById('${guideId}');
14596
+ var step = guide && guide.findStepById('${stepId}');`;
14597
+ return content.replace(stdTemplating, sansTemplating);
14598
+ }
14599
+ // execute all content preprocessors and return end result
14600
+ const GuideContentProcessorList = [removeStandardTemplating];
14601
+ function preprocessGuideContent(content, guideId, stepId) {
14602
+ return _.reduce(GuideContentProcessorList, (acc, procFn) => {
14603
+ return procFn(acc, guideId, stepId);
14604
+ }, content);
14605
+ }
14606
+ function shouldCreateTemplate(content) {
14607
+ return /<%/.test(content) && /%>/.test(content);
14608
+ }
14609
+ function receiveDomStructureJson(guideId, stepId, domJson) {
14610
+ var entry = cache[guideId + stepId];
14611
+ if (entry && entry.deferred.domJson) {
14612
+ entry.domJson = replaceObjectWithContentHost(domJson);
14613
+ entry.deferred.domJson.resolve();
14614
+ }
14615
+ }
14616
+ function lightningRedirect(url) {
14617
+ if (isSfdcLightning()) {
14618
+ // https://pendo-static-1234.storage.googleapis.com/guide-content/:guideId/:stepId/:someKindaHash.guide.js
14619
+ var path = url.replace(/^https?:\/\/[^/]+\/guide-content\//, '').split('/');
14620
+ var guideId = path[0];
14621
+ /* global $A */
14622
+ var resourcePath = $A.get('$Resource.pendoGuide' + base32Encode(toUTF8Array(guideId)));
14623
+ if (_.isString(resourcePath)) {
14624
+ return resourcePath + '/' + path.join('/');
14625
+ }
14626
+ }
14627
+ return url;
14628
+ }
14629
+ function addExtensionToUrl(url, extension) {
14630
+ if (!_.isString(url))
14631
+ return url;
14632
+ var i = _.indexOf(url, '?');
14633
+ if (i < 0) {
14634
+ return url + extension;
14635
+ }
14636
+ else {
14637
+ return url.substring(0, i) + extension + url.substring(i);
14638
+ }
14639
+ }
14640
+ return {
14641
+ load,
14642
+ reset,
14643
+ guideContent,
14644
+ receiveDomStructureJson
14645
+ };
14646
+ })();
14647
+
14308
14648
  function getHashFromContentUrl(url) {
14309
14649
  var contentUrl = parseUrl(url);
14310
14650
  var sha1Length = 27;
@@ -14391,10 +14731,12 @@ var ContentVerifier = (function () {
14391
14731
  if (!_.isString(url))
14392
14732
  return q.reject('unable to parse "' + url + '"');
14393
14733
  var stepHash = computeStepHash(contentContainer, urlProp);
14394
- return ajax.get(url).then(function (response) {
14734
+ // Verify against the host the loader fetches from; the hash still comes from the original url.
14735
+ var fetchUrl = replaceWithContentHost(url);
14736
+ return ajax.get(fetchUrl).then(function (response) {
14395
14737
  if (computeHashFromFile(response.data) === stepHash)
14396
14738
  return;
14397
- return q.reject('Unable to verify content at "' + url + '"');
14739
+ return q.reject('Unable to verify content at "' + fetchUrl + '"');
14398
14740
  });
14399
14741
  }
14400
14742
  function verifyContentHashUnlessSri(contentContainer, urlProp) {
@@ -14669,297 +15011,6 @@ function GuideStepFactory(step, guide) {
14669
15011
  GuideFactory.behaviors = [];
14670
15012
  GuideStepFactory.behaviors = [];
14671
15013
 
14672
- function base32Encode(uint8array) {
14673
- var alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
14674
-
14675
- var length = uint8array.length;
14676
-
14677
- var bits = 0;
14678
- var value = 0;
14679
- var output = '';
14680
-
14681
- for (var i = 0; i < length; i++) {
14682
- value = (value << 8) | uint8array[i];
14683
- bits += 8;
14684
-
14685
- while (bits >= 5) {
14686
- output += alphabet[(value >>> (bits - 5)) & 31];
14687
- bits -= 5;
14688
- }
14689
- }
14690
-
14691
- if (bits > 0) {
14692
- output += alphabet[(value << (5 - bits)) & 31];
14693
- }
14694
-
14695
- return output;
14696
- }
14697
-
14698
- // NOTE: this previously was *only* checking the snippet provided config for contentHost
14699
- function replaceWithContentHost(str) {
14700
- var contentHost = ConfigReader.getLocalConfig('contentHost');
14701
- if (!contentHost || !str)
14702
- return str;
14703
- var subBucketPattern = new RegExp(`(https:)?\\/\\/${SUBSCRIPTION_BUCKET}-\\d+\\.storage\\.googleapis\\.com`, 'g');
14704
- var globalBucketPattern = new RegExp(`(https:)?\\/\\/${GLOBAL_BUCKET}\\.storage\\.googleapis\\.com`, 'g');
14705
- // Content proxy hosts look like content-<subId>.static.<envDomain>, where envDomain is ASSET_HOST minus its cdn. prefix
14706
- var envDomain = ASSET_HOST.replace(/^cdn\./, '');
14707
- var contentProxyPattern = new RegExp(`(https:)?\\/\\/content-\\d+\\.static\\.${escapeRegExp(envDomain)}`, 'g');
14708
- return str.replace(subBucketPattern, contentHost)
14709
- .replace(globalBucketPattern, contentHost)
14710
- .replace(contentProxyPattern, contentHost)
14711
- .replace(new RegExp(`(https:)?\\/\\/${escapeRegExp(ASSET_HOST)}`, 'g'), contentHost);
14712
- }
14713
- function replaceObjectWithContentHost(obj) {
14714
- var contentHost = ConfigReader.getLocalConfig('contentHost');
14715
- if (!contentHost)
14716
- return obj;
14717
- return JSON.parse(replaceWithContentHost(JSON.stringify(obj)));
14718
- }
14719
- // APP-37205 we're only checking the remote contentHost config setting
14720
- function getContentHostRegex() {
14721
- var ch = escapeRegExp(ConfigReader.getHostedConfig('contentHost'));
14722
- if (ch)
14723
- return ch + '*';
14724
- }
14725
- /**
14726
- * @access private
14727
- * @param {Function} isValidGuideResponse
14728
- * @param {object} guideResponse
14729
- */
14730
- function isValidGuideResponse(guideResponse) {
14731
- // Is it in proper JSON format/an object? If guideResponse does not have the 'type', 'props', or the 'children'
14732
- // prop, we should also return false.
14733
- if (!(_.isObject(guideResponse) && guideResponse.hasOwnProperty('type') &&
14734
- guideResponse.hasOwnProperty('children') && guideResponse.hasOwnProperty('props'))) {
14735
- return false;
14736
- }
14737
- return true;
14738
- }
14739
- var ContentLoader = (function (pendo) {
14740
- var cache = {};
14741
- // For APP-37205, we're verifing that the result is what it is. I know.
14742
- var replaceWithContentHostAndVerify = _.compose(function (url) {
14743
- var test = getContentHostRegex();
14744
- if (test) {
14745
- var v = new RegExp(test);
14746
- if (!v.test(url)) {
14747
- log.debug('Failed to verify URL: ' + url + ' vs ' + test);
14748
- }
14749
- }
14750
- return url;
14751
- }, replaceWithContentHost);
14752
- /**
14753
- * @access private
14754
- * @typedef {object} ContentRequest
14755
- * @param {string} id uniquely identifies the container
14756
- * @param {string} contentUrl
14757
- * @param {string} contentUrlCss
14758
- * @param {string} contentUrlJs
14759
- */
14760
- /**
14761
- * @access private
14762
- * @typedef {object} ContentEntry
14763
- * @param {string} content The raw content as a string
14764
- * @param {Function} template The template function compiled from the raw content
14765
- * @param {Function} script The javascript to execute
14766
- */
14767
- /**
14768
- * Loads the content for the given container
14769
- *
14770
- * @access private
14771
- * @param {ContentRequest} contentContainer
14772
- * @returns {Promise.<ContentEntry>} Resolved when all of the content is loaded
14773
- */
14774
- function load(contentContainer) {
14775
- try {
14776
- var cacheEntry = cache[contentContainer.id];
14777
- if (cacheEntry && cacheEntry.language !== contentContainer.language) {
14778
- delete cache[contentContainer.id];
14779
- }
14780
- if (!cache[contentContainer.id]) {
14781
- var promises = [];
14782
- var entry = {
14783
- deferred: {},
14784
- language: contentContainer.language
14785
- };
14786
- var fetchedDomContent = false;
14787
- cache[contentContainer.id] = entry;
14788
- if (XHRGuideLoader.usesXhr() && contentContainer.domUrl) {
14789
- entry.deferred.domJson = q.defer();
14790
- ajax.get(replaceWithContentHostAndVerify(contentContainer.domUrl)).then(function (result) {
14791
- entry.domJson = replaceObjectWithContentHost(result.data);
14792
- var guideResponse = result.data;
14793
- if (isValidGuideResponse(guideResponse)) {
14794
- entry.deferred.domJson.resolve();
14795
- }
14796
- else {
14797
- // Log that the guide was invalid
14798
- // handle promise
14799
- log.critical(new Error('Response from the guide was not in proper JSON format'));
14800
- entry.deferred.domJson.reject();
14801
- }
14802
- }).catch(function (e) {
14803
- log.critical(e);
14804
- Events.resourceFetchFail.trigger(contentContainer, e);
14805
- entry.deferred.domJson.reject('Failed to load content');
14806
- });
14807
- promises.push(entry.deferred.domJson.promise);
14808
- fetchedDomContent = true;
14809
- }
14810
- else if (contentContainer.domJsonpUrl) {
14811
- entry.deferred.domJson = q.defer();
14812
- var jsonpNode = loadResource(replaceWithContentHostAndVerify(contentContainer.domJsonpUrl), function (err) {
14813
- dom.removeNode(jsonpNode);
14814
- if (err) {
14815
- Events.resourceFetchFail.trigger(contentContainer, err);
14816
- return;
14817
- }
14818
- }, true);
14819
- promises.push(entry.deferred.domJson.promise);
14820
- fetchedDomContent = true;
14821
- }
14822
- if (contentContainer.contentUrlJs && !ConfigReader.get('preventCodeInjection')) {
14823
- // Content, template, and script for 2.1+ agents
14824
- entry.deferred.content = q.defer();
14825
- var scriptNode = loadResource(replaceWithContentHostAndVerify(lightningRedirect(contentContainer.contentUrlJs)), function (err) {
14826
- dom.removeNode(scriptNode);
14827
- if (err) {
14828
- Events.resourceFetchFail.trigger(contentContainer, err);
14829
- return;
14830
- }
14831
- }, true);
14832
- promises.push(entry.deferred.content.promise);
14833
- // Style
14834
- if (contentContainer.contentUrlCss) {
14835
- entry.deferred.css = q.defer();
14836
- loadResource({ url: replaceWithContentHostAndVerify(contentContainer.contentUrlCss), type: 'text/css' }, function () {
14837
- entry.deferred.css.resolve();
14838
- });
14839
- promises.push(entry.deferred.css.promise);
14840
- }
14841
- }
14842
- else if (contentContainer.contentUrl && !ConfigReader.get('preventCodeInjection')) {
14843
- // Content with inlined script and css (2.0.x agents, or guides that have not migrated to the 2.1 format yet)
14844
- entry.deferred.content = q.defer();
14845
- var contentNode = loadResource(addExtensionToUrl(replaceWithContentHostAndVerify(contentContainer.contentUrl), '.js'), function (err) {
14846
- dom.removeNode(contentNode);
14847
- if (err) {
14848
- Events.resourceFetchFail.trigger(contentContainer, err);
14849
- return;
14850
- }
14851
- }, true);
14852
- promises.push(entry.deferred.content.promise);
14853
- }
14854
- else if (!fetchedDomContent) {
14855
- return q.reject('fetchedDomContent is still false');
14856
- }
14857
- entry.deferred.promise = q.all(promises).then(function () {
14858
- return _.omit(entry, 'deferred');
14859
- });
14860
- }
14861
- return cache[contentContainer.id].deferred.promise;
14862
- }
14863
- catch (e) {
14864
- Events.resourceFetchFail.trigger(contentContainer, e);
14865
- throw e;
14866
- }
14867
- }
14868
- // Clears the content cache. Used for testing.
14869
- function reset() {
14870
- cache = {};
14871
- }
14872
- /**
14873
- * JSONP-style callback for loading guide content
14874
- * from script resources. Step content stored in
14875
- * GCS is wrapped with pendo.guideContent(...)
14876
- *
14877
- * @access private
14878
- * @param {string} guideId
14879
- * @param {string} stepId
14880
- * @param {string} content
14881
- * @param {Function} templateFn
14882
- * @param {Function} scriptFn
14883
- */
14884
- function guideContent(guideId, stepId, content, templateFn, scriptFn) {
14885
- if (_.isString(content)) {
14886
- content = preprocessGuideContent(content, guideId, stepId);
14887
- if (!_.isFunction(templateFn)) {
14888
- if (shouldCreateTemplate(content)) {
14889
- templateFn = _.template(content);
14890
- }
14891
- else {
14892
- templateFn = _.constant(content);
14893
- }
14894
- }
14895
- var entry = cache[guideId + stepId];
14896
- if (entry && entry.deferred.content) {
14897
- entry.content = content;
14898
- entry.template = templateFn;
14899
- entry.script = scriptFn;
14900
- entry.deferred.content.resolve();
14901
- }
14902
- }
14903
- }
14904
- // preprocess content to remove simple guide and step IDs templating
14905
- function removeStandardTemplating(content, guideId, stepId) {
14906
- const stdTemplating = `<% if (typeof guide !== 'undefined') { %>
14907
- var guide = pendo.findGuideById('<%= guide.id %>');
14908
- var step = guide && guide.findStepById('<%= step.id %>');
14909
- <% } %>`;
14910
- const sansTemplating = `var guide = pendo.findGuideById('${guideId}');
14911
- var step = guide && guide.findStepById('${stepId}');`;
14912
- return content.replace(stdTemplating, sansTemplating);
14913
- }
14914
- // execute all content preprocessors and return end result
14915
- const GuideContentProcessorList = [removeStandardTemplating];
14916
- function preprocessGuideContent(content, guideId, stepId) {
14917
- return _.reduce(GuideContentProcessorList, (acc, procFn) => {
14918
- return procFn(acc, guideId, stepId);
14919
- }, content);
14920
- }
14921
- function shouldCreateTemplate(content) {
14922
- return /<%/.test(content) && /%>/.test(content);
14923
- }
14924
- function receiveDomStructureJson(guideId, stepId, domJson) {
14925
- var entry = cache[guideId + stepId];
14926
- if (entry && entry.deferred.domJson) {
14927
- entry.domJson = replaceObjectWithContentHost(domJson);
14928
- entry.deferred.domJson.resolve();
14929
- }
14930
- }
14931
- function lightningRedirect(url) {
14932
- if (isSfdcLightning()) {
14933
- // https://pendo-static-1234.storage.googleapis.com/guide-content/:guideId/:stepId/:someKindaHash.guide.js
14934
- var path = url.replace(/^https?:\/\/[^/]+\/guide-content\//, '').split('/');
14935
- var guideId = path[0];
14936
- /* global $A */
14937
- var resourcePath = $A.get('$Resource.pendoGuide' + base32Encode(toUTF8Array(guideId)));
14938
- if (_.isString(resourcePath)) {
14939
- return resourcePath + '/' + path.join('/');
14940
- }
14941
- }
14942
- return url;
14943
- }
14944
- function addExtensionToUrl(url, extension) {
14945
- if (!_.isString(url))
14946
- return url;
14947
- var i = _.indexOf(url, '?');
14948
- if (i < 0) {
14949
- return url + extension;
14950
- }
14951
- else {
14952
- return url.substring(0, i) + extension + url.substring(i);
14953
- }
14954
- }
14955
- return {
14956
- load,
14957
- reset,
14958
- guideContent,
14959
- receiveDomStructureJson
14960
- };
14961
- })();
14962
-
14963
15014
  var AsyncContent = (function () {
14964
15015
  function AsyncContent(guide) {
14965
15016
  var step = this;
@@ -15089,7 +15140,7 @@ var GuideRuntime = (function (agentEvents) {
15089
15140
  }
15090
15141
  function teardown() {
15091
15142
  EventChannel.off(handleEvent);
15092
- _.each(pluginAddGlobalScripts, GuideRuntime.removeGlobalScript);
15143
+ pluginAddGlobalScripts.length = 0;
15093
15144
  }
15094
15145
  function addGlobalScript(obj) {
15095
15146
  obj = autoSubscribe(obj);
@@ -21996,7 +22047,9 @@ Badge.behaviors = [
21996
22047
  function InlinePosition() {
21997
22048
  function getShadowDomAdjustedTarget(target) {
21998
22049
  try {
21999
- if (target.shadowRoot && !target.shadowRoot.querySelector('slot')) {
22050
+ // Only a default (unnamed) slot can render a badge appended as a light-DOM
22051
+ // child, so fall back to the shadow root when there isn't one
22052
+ if (target.shadowRoot && !target.shadowRoot.querySelector('slot:not([name])')) {
22000
22053
  return target.shadowRoot;
22001
22054
  }
22002
22055
  }
@@ -22017,7 +22070,7 @@ function InlinePosition() {
22017
22070
  if (targetElem && targetElem.tagName) {
22018
22071
  var tagName = targetElem.tagName.toLowerCase();
22019
22072
  var shadowDomAdjustedTarget = getShadowDomAdjustedTarget(targetElem);
22020
- if (/br|input|img|select|textarea/.test(tagName)) {
22073
+ if (/^(?:br|input|img|select|textarea)$/.test(tagName)) {
22021
22074
  if (badgeElem.parentNode === targetElem.parentNode)
22022
22075
  return;
22023
22076
  // Insert after if targetElem cannot have children
@@ -23533,7 +23586,7 @@ function autofocusRuntimeScript(step, guide, pendo) {
23533
23586
  if (pendo._.get(step, 'attributes.isAutoFocus')) {
23534
23587
  BuildingBlockGuides.autofocusStep(step, guideContainer);
23535
23588
  BuildingBlockGuides.trapFocusStep(step, guideContainer);
23536
- step.hiddenElements = BuildingBlockGuides.addAriaHiddenOutsideGuide(step.guideElement[0]);
23589
+ step.hiddenElements = (step.hiddenElements || []).concat(BuildingBlockGuides.addAriaHiddenOutsideGuide(step.guideElement[0]));
23537
23590
  }
23538
23591
  if (isGuideRCType(guide)) {
23539
23592
  BuildingBlockGuides.attachRCFocusHandlers();
@@ -23555,16 +23608,23 @@ function backdropInertRuntimeScript(step, guide, pendo) {
23555
23608
  return;
23556
23609
  if (step.hasBackdrop) {
23557
23610
  const BuildingBlockGuides = pendo.BuildingBlocks.BuildingBlockGuides;
23558
- const excludeElements = getTargetAreaElementsForBlockOut(step);
23559
- // The preview bar iframe is a sibling of the guide under <body>; inerting it
23560
- // would make preview mode's controls unclickable while the backdrop shows.
23561
- const previewBar = document.getElementById('pendo-preview');
23562
- if (previewBar) {
23563
- excludeElements.push(previewBar);
23564
- }
23565
- const inertElements = BuildingBlockGuides.addInertOutsideGuide(step.guideElement[0], [], excludeElements);
23566
- step.hiddenElements = (step.hiddenElements || []).concat(inertElements);
23611
+ const guideElement = step.guideElement[0];
23612
+ const excludeElements = getExcludedElements(step);
23613
+ const disabledElements = sniffer.supportsInert()
23614
+ ? BuildingBlockGuides.addInertOutsideGuide(guideElement, [], excludeElements)
23615
+ : BuildingBlockGuides.addAriaHiddenOutsideGuide(guideElement, [], excludeElements);
23616
+ step.hiddenElements = (step.hiddenElements || []).concat(disabledElements);
23617
+ }
23618
+ }
23619
+ function getExcludedElements(step) {
23620
+ const excludeElements = getTargetAreaElementsForBlockOut(step);
23621
+ // The preview bar iframe is a sibling of the guide under <body>; inerting it
23622
+ // would make preview mode's controls unclickable while the backdrop shows.
23623
+ const previewBar = document.getElementById('pendo-preview');
23624
+ if (previewBar) {
23625
+ excludeElements.push(previewBar);
23567
23626
  }
23627
+ return excludeElements;
23568
23628
  }
23569
23629
  var backdropInertStep = {
23570
23630
  script: backdropInertRuntimeScript
@@ -25168,22 +25228,31 @@ function hasLegacyGuides(guides, pendo) {
25168
25228
  }
25169
25229
  const DEFAULT_CSS_ID = '_pendo-default-css_';
25170
25230
  const CUSTOM_CSS_ID = '_pendo-css_';
25231
+ // Several SDK instances can share a page. Scoping the <link> ids by api key stops one app's
25232
+ // teardown, or its "no custom css" path, from stripping the stylesheet another app injected.
25233
+ function getCssElementId(baseId) {
25234
+ return pendo$1.apiKey ? baseId + pendo$1.apiKey : baseId;
25235
+ }
25236
+ // Api keys are arbitrary customer-supplied strings, so '#' + id is not a safe selector.
25237
+ function removeGuideCss(baseId) {
25238
+ dom(document.getElementById(getCssElementId(baseId))).remove();
25239
+ }
25171
25240
  function loadGuideCss() {
25172
25241
  var promises = [];
25173
25242
  if (!shouldLoadGlobalCSS()) {
25174
25243
  return q.resolve();
25175
25244
  }
25176
25245
  if (hasLegacyGuides(getActiveGuides(), pendo$1)) {
25177
- promises.push(loadExternalCss(DEFAULT_CSS_ID, getDefaultCssUrl()));
25246
+ promises.push(loadExternalCss(getCssElementId(DEFAULT_CSS_ID), getDefaultCssUrl()));
25178
25247
  }
25179
25248
  var guideWidget = pendo$1.guideWidget || {};
25180
25249
  var data = guideWidget.data || {};
25181
25250
  var customCssUrl = data.guideCssUrl;
25182
25251
  if (customCssUrl) {
25183
- promises.push(loadExternalCss(CUSTOM_CSS_ID, replaceWithContentHost(customCssUrl)));
25252
+ promises.push(loadExternalCss(getCssElementId(CUSTOM_CSS_ID), replaceWithContentHost(customCssUrl)));
25184
25253
  }
25185
25254
  else {
25186
- dom('#' + CUSTOM_CSS_ID).remove();
25255
+ removeGuideCss(CUSTOM_CSS_ID);
25187
25256
  }
25188
25257
  return promises;
25189
25258
  }
@@ -25400,8 +25469,8 @@ var initGuides = function (observer) {
25400
25469
  _.each(teardownFns, function (teardownFn) {
25401
25470
  teardownFn();
25402
25471
  });
25403
- dom('#' + DEFAULT_CSS_ID).remove();
25404
- dom('#' + CUSTOM_CSS_ID).remove();
25472
+ removeGuideCss(DEFAULT_CSS_ID);
25473
+ removeGuideCss(CUSTOM_CSS_ID);
25405
25474
  GuideRuntime.teardown();
25406
25475
  };
25407
25476
  };
@@ -25871,6 +25940,8 @@ _.extend(TopFrameRelay.prototype, {
25871
25940
  if (!message.data)
25872
25941
  return;
25873
25942
  if (message.data.action === 'pendo::connect') {
25943
+ if (!isSameWindowTree(message, this._window))
25944
+ return;
25874
25945
  this._impl.frameConnect(message);
25875
25946
  }
25876
25947
  if (message.data.action === REMOVE_FRAME_ACTION) {
@@ -25966,6 +26037,38 @@ var ImplCommon = {
25966
26037
  function isPortValid(port) {
25967
26038
  return port && _.all(['addEventListener', 'start'], function (cmd) { return _.isFunction(port[cmd]); });
25968
26039
  }
26040
+ // A connect is only ever posted to window.top by a frame below us, so a sender we
26041
+ // cannot find in our own frame tree is not one of our frames no matter what it
26042
+ // claims. window.open gives an attacker a handle to post through, and neither
26043
+ // X-Frame-Options nor frame-ancestors covers that - they only govern embedding
26044
+ // (APP-168708). frames and its indexed children are readable across origins, so
26045
+ // this holds where an origin check can't: no ancestorOrigins needed, and sandboxed
26046
+ // frames (origin "null") are still found.
26047
+ function isFrameBelowUs(source, window) {
26048
+ if (!source)
26049
+ return false;
26050
+ if (source === window)
26051
+ return true;
26052
+ var pending = [window];
26053
+ while (pending.length) {
26054
+ var children = pending.shift().frames;
26055
+ var count = children ? children.length : 0;
26056
+ for (var i = 0; i < count; i++) {
26057
+ if (children[i] === source)
26058
+ return true;
26059
+ pending.push(children[i]);
26060
+ }
26061
+ }
26062
+ return false;
26063
+ }
26064
+ function isSameWindowTree(message, window) {
26065
+ try {
26066
+ return isFrameBelowUs(message.source, window);
26067
+ }
26068
+ catch (e) {
26069
+ return false;
26070
+ }
26071
+ }
25969
26072
  function MessageChannelImpl(window, singletonMessageHandler) {
25970
26073
  this._window = window;
25971
26074
  this._ports = {};
@@ -30854,21 +30957,26 @@ var BuildingBlockGuides = (function () {
30854
30957
  domElem.append(endFocusBumper);
30855
30958
  return [startFocusBumper, endFocusBumper];
30856
30959
  }
30857
- function addAriaHiddenOutsideGuide(elem, hidden = []) {
30960
+ // native contains() can't see into shadow trees, so excluded elements inside a
30961
+ // shadow root (e.g. blockOut target areas) need the shadow-aware walk
30962
+ function containsExcludedElement(elem, excludeElements) {
30963
+ return _.some(excludeElements, (excluded) => excluded && (elem === excluded || contains(elem, excluded)));
30964
+ }
30965
+ function addAriaHiddenOutsideGuide(elem, hidden = [], excludeElements = []) {
30858
30966
  if (!elem || elem === getBody())
30859
30967
  return hidden;
30860
30968
  const siblings = getSiblings(elem);
30861
30969
  _.each(siblings, (s) => {
30862
- if (hidden.indexOf(s) === -1 && s.getAttribute('aria-hidden') !== 'true') {
30863
- const tagName = s.tagName.toLowerCase();
30864
- if (tagName !== 'script' && tagName !== 'style') {
30865
- s.setAttribute('aria-hidden', 'true');
30866
- hidden.push(s);
30867
- }
30970
+ const tagName = s.tagName.toLowerCase();
30971
+ const alreadyHidden = s.getAttribute('aria-hidden') === 'true';
30972
+ const isExcluded = containsExcludedElement(s, excludeElements);
30973
+ if (hidden.indexOf(s) === -1 && !alreadyHidden && !isExcluded && tagName !== 'script' && tagName !== 'style') {
30974
+ s.setAttribute('aria-hidden', 'true');
30975
+ hidden.push(s);
30868
30976
  }
30869
30977
  });
30870
30978
  if (elem.parentNode) {
30871
- addAriaHiddenOutsideGuide(elem.parentNode, hidden);
30979
+ addAriaHiddenOutsideGuide(elem.parentNode, hidden, excludeElements);
30872
30980
  }
30873
30981
  return hidden;
30874
30982
  }
@@ -30882,9 +30990,7 @@ var BuildingBlockGuides = (function () {
30882
30990
  _.each(siblings, (s) => {
30883
30991
  const tagName = s.tagName.toLowerCase();
30884
30992
  const alreadyDisabled = s.getAttribute('inert') === 'true';
30885
- // native contains() can't see into shadow trees, so excluded elements inside a
30886
- // shadow root (e.g. blockOut target areas) need the shadow-aware walk
30887
- const isExcluded = _.some(excludeElements, (excluded) => excluded && (s === excluded || contains(s, excluded)));
30993
+ const isExcluded = containsExcludedElement(s, excludeElements);
30888
30994
  if (hidden.indexOf(s) === -1 && !alreadyDisabled && !isExcluded && tagName !== 'script' && tagName !== 'style') {
30889
30995
  s.setAttribute('inert', 'true');
30890
30996
  hidden.push(s);
@@ -43105,6 +43211,124 @@ function substitutionIcon(color, size) {
43105
43211
  13.7239 15.5 14V17Z" fill="${color}"></path> </g></svg></div>`);
43106
43212
  }
43107
43213
 
43214
+ const AUTO_ADVANCE_DELAY_MS = 250;
43215
+ function isAutoAdvanceRequested(step, _) {
43216
+ return _.get(step, 'attributes.autoAdvance') === true;
43217
+ }
43218
+ // Survey-authored DOM carries no question-type classes, so eligibility keys off
43219
+ // answer-control shape: every required poll id must be radio-backed, which also
43220
+ // refuses a future non-radio type by default.
43221
+ function isStepEligibleInDom(step, _, requiredPollIds) {
43222
+ const guideElement = step && step.guideElement;
43223
+ if (!guideElement)
43224
+ return false;
43225
+ if (guideElement.find('textarea, input:text, input:checkbox, select').length)
43226
+ return false;
43227
+ const pollIds = _.uniq(_.map(guideElement.find('[data-pendo-poll-id]'), (element) => element.getAttribute('data-pendo-poll-id')));
43228
+ if (!pollIds.length)
43229
+ return false;
43230
+ return _.all(pollIds, (pollId) => _.contains(requiredPollIds, pollId) &&
43231
+ guideElement.find(`input:radio[data-pendo-poll-id="${pollId}"]`).length > 0);
43232
+ }
43233
+ function hideSubmitButtons(step, _, submitButtons) {
43234
+ const hiddenButtons = [];
43235
+ _.each(submitButtons, (button) => {
43236
+ const element = step.guideElement.find(`#${button.props.id}`)[0];
43237
+ if (!element)
43238
+ return;
43239
+ // display, not visibility: a reserved-but-invisible box leaves an empty gap
43240
+ // at the foot of the step. Authored styles reach the button inline
43241
+ // (build.js:151), so the original value is kept rather than blanked.
43242
+ hiddenButtons.push({ element, display: element.style.display });
43243
+ element.style.display = 'none';
43244
+ });
43245
+ return function restoreSubmitButtons() {
43246
+ _.each(hiddenButtons, (hiddenButton) => {
43247
+ hiddenButton.element.style.display = hiddenButton.display;
43248
+ });
43249
+ };
43250
+ }
43251
+ // The container's pixel height is measured during render (layout.js) before custom
43252
+ // scripts run, so collapsing a row afterwards leaves the box stale and the step keeps
43253
+ // the empty space. Same reflow pair metadata-substitution uses, scoped to this step so
43254
+ // a second guide on screen is left alone.
43255
+ function reflowGuide(step, _, pendo) {
43256
+ const recalculateGuideHeight = _.get(pendo, 'BuildingBlocks.BuildingBlockGuides.recalculateGuideHeight');
43257
+ try {
43258
+ if (_.isFunction(pendo.flexElement)) {
43259
+ pendo.flexElement(step.guideElement.find(guideMarkdownUtil.containerSelector));
43260
+ }
43261
+ if (_.isFunction(recalculateGuideHeight)) {
43262
+ recalculateGuideHeight(`pendo-g-${step.id}`, step.guideElement);
43263
+ }
43264
+ }
43265
+ catch (error) {
43266
+ pendo.log('[auto-advance] guide reflow failed, the step may keep an empty row', error && error.message);
43267
+ }
43268
+ }
43269
+ function createInertController() {
43270
+ return { isActive: false, notifyAnswered() { }, cancel() { } };
43271
+ }
43272
+ function createAutoAdvanceController(step, pendo, submitButtons, requiredPollIds) {
43273
+ const _ = pendo._;
43274
+ const inert = createInertController();
43275
+ if (!isAutoAdvanceRequested(step, _))
43276
+ return inert;
43277
+ // Only reachable for API-authored guides; choosing between two
43278
+ // submitPollAndGoToStep targets would be a guess.
43279
+ if (_.size(submitButtons) !== 1) {
43280
+ pendo.log(`[auto-advance] not arming: step ${step.id} needs exactly one submit button, found ${_.size(submitButtons)}`);
43281
+ return inert;
43282
+ }
43283
+ if (!isStepEligibleInDom(step, _, requiredPollIds)) {
43284
+ pendo.log(`[auto-advance] not arming: rendered step ${step.id} failed the eligibility re-check`);
43285
+ return inert;
43286
+ }
43287
+ const submitButton = submitButtons[0];
43288
+ // A click binds every action on the node and dispatches one event each
43289
+ // (build.js:205-207); we dispatch one, so a second action would be dropped.
43290
+ if (_.size(submitButton.actions) !== 1) {
43291
+ pendo.log(`[auto-advance] not arming: step ${step.id} submit button carries ${_.size(submitButton.actions)} actions, expected one`);
43292
+ return inert;
43293
+ }
43294
+ const submitAction = _.find(submitButton.actions, (action) => action.action === 'submitPoll' || action.action === 'submitPollAndGoToStep');
43295
+ if (!submitAction)
43296
+ return inert;
43297
+ let hasFired = false;
43298
+ let restoreSubmitButtons = _.noop;
43299
+ function fire() {
43300
+ if (hasFired)
43301
+ return;
43302
+ hasFired = true;
43303
+ try {
43304
+ // Shape-identical to what bindActionToNode emits for a click, so
43305
+ // beforeAdvance consumers intercept an auto-advance the same way.
43306
+ step.eventRouter.eventable.trigger('pendoEvent', {
43307
+ action: submitAction.action,
43308
+ params: submitAction.parameters,
43309
+ step,
43310
+ ignore: !!submitAction.ignore,
43311
+ srcElement: step.guideElement.find(`#${submitButton.props.id}`)[0]
43312
+ });
43313
+ }
43314
+ catch (error) {
43315
+ restoreSubmitButtons();
43316
+ reflowGuide(step, _, pendo);
43317
+ pendo.log('[auto-advance] submit dispatch failed, restored the submit button', error && error.message);
43318
+ }
43319
+ }
43320
+ const debouncedFire = _.debounce(fire, AUTO_ADVANCE_DELAY_MS);
43321
+ // Last, so nothing between here and the return can throw and leave the step
43322
+ // with a hidden button and no controller to un-hide it.
43323
+ restoreSubmitButtons = hideSubmitButtons(step, _, submitButtons);
43324
+ reflowGuide(step, _, pendo);
43325
+ return {
43326
+ isActive: true,
43327
+ notifyAnswered: debouncedFire,
43328
+ cancel: debouncedFire.cancel
43329
+ };
43330
+ }
43331
+
43108
43332
  const requiredElement = '<span class="_pendo-required-indicator" style="color:red; font-style:italic;" title="Question is required"> *</span>';
43109
43333
  const requiredSyntax = '{required/}';
43110
43334
  const RequiredQuestions = {
@@ -43114,6 +43338,7 @@ const RequiredQuestions = {
43114
43338
  let requiredPollIds = [];
43115
43339
  let submitButtons = (_a = guideMarkdownUtil.lookupGuideButtons(step.domJson)) === null || _a === void 0 ? void 0 : _a.filter(button => button.actions.find(action => action.action === 'submitPoll' || action.action === 'submitPollAndGoToStep'));
43116
43340
  const requiredQuestions = processRequiredQuestions();
43341
+ let autoAdvance = createInertController();
43117
43342
  if (requiredQuestions) {
43118
43343
  pendo._.forEach(requiredQuestions, (question) => {
43119
43344
  if (question.classList.contains('_pendo-open-text-poll-question')) {
@@ -43129,6 +43354,17 @@ const RequiredQuestions = {
43129
43354
  }
43130
43355
  });
43131
43356
  }
43357
+ // Armed last, after the listeners above: runCustomScripts abandons the rest of
43358
+ // this script on a throw, and the submit button is already disabled by now.
43359
+ if (isAutoAdvanceRequested(step, pendo._)) {
43360
+ this.on('beforeUnmount', () => autoAdvance.cancel());
43361
+ try {
43362
+ autoAdvance = createAutoAdvanceController(step, pendo, submitButtons, requiredPollIds);
43363
+ }
43364
+ catch (error) {
43365
+ pendo.log('[auto-advance] arming failed, leaving the step on manual submit', error && error.message);
43366
+ }
43367
+ }
43132
43368
  function getEligibleQuestions() {
43133
43369
  const pollQuestions = pendo._.toArray(step.guideElement.find(`[class*=-poll-question]:contains(${requiredSyntax})`));
43134
43370
  const surveyMatches = pendo._.toArray(step.guideElement.find(`[data-pendo-poll-id]:contains(${requiredSyntax})`));
@@ -43205,9 +43441,11 @@ const RequiredQuestions = {
43205
43441
  }
43206
43442
  if (allRequiredComplete) {
43207
43443
  enableEligibleButtons(submitButtons);
43444
+ autoAdvance.notifyAnswered();
43208
43445
  }
43209
43446
  else {
43210
43447
  disableEligibleButtons(submitButtons);
43448
+ autoAdvance.cancel();
43211
43449
  }
43212
43450
  }
43213
43451
  function disableEligibleButtons(buttons) {