@pendo/agent 2.338.2 → 2.339.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4046,8 +4046,8 @@ let SERVER = '';
4046
4046
  let ASSET_HOST = '';
4047
4047
  let ASSET_PATH = '';
4048
4048
  let DESIGNER_SERVER = '';
4049
- let VERSION = '2.338.2_';
4050
- let PACKAGE_VERSION = '2.338.2';
4049
+ let VERSION = '2.339.1_';
4050
+ let PACKAGE_VERSION = '2.339.1';
4051
4051
  let LOADER = 'xhr';
4052
4052
  /* eslint-enable web-sdk-eslint-rules/no-gulp-env-references */
4053
4053
  /**
@@ -4528,7 +4528,7 @@ function createStorageShim(storageAccessor) {
4528
4528
  return fake;
4529
4529
  }
4530
4530
  }
4531
- var pendoLocalStorage$4 = createStorageShim(function () { return window.localStorage; });
4531
+ var pendoLocalStorage$3 = createStorageShim(function () { return window.localStorage; });
4532
4532
  var pendoSessionStorage$2 = createStorageShim(function () { return window.sessionStorage; });
4533
4533
 
4534
4534
  /**
@@ -4916,7 +4916,7 @@ var agentStorage = (function () {
4916
4916
  }
4917
4917
  const wrappedPendoSessionStorage = _.extend({}, pendoSessionStorage$2);
4918
4918
  wrappedPendoSessionStorage.setItem = wrapStorageWriteMethod(wrappedPendoSessionStorage.setItem, 'hasSession');
4919
- const wrappedPendoLocalStorage = _.extend({}, pendoLocalStorage$4);
4919
+ const wrappedPendoLocalStorage = _.extend({}, pendoLocalStorage$3);
4920
4920
  wrappedPendoLocalStorage.setItem = wrapStorageWriteMethod(wrappedPendoLocalStorage.setItem, 'hasLocal');
4921
4921
  return {
4922
4922
  /* API */
@@ -11826,7 +11826,7 @@ _.extend(dom.prototype, DomEvent.$, DomQuery.$);
11826
11826
 
11827
11827
  // Domains that Pendo owns, will be swapped in by build patches
11828
11828
  const pendoDomainPatterns = [
11829
- /^https:\/\/((adopt|oem)\.)?((us1)\.)?(app|via|adopt|cdn|oem|novus-api|novus)(\.(au|eu|gov|hsbc|jpn|tv))?\.pendo\.io$/,
11829
+ /^https:\/\/((adopt|oem)\.)?((us1)\.)?(app|via|adopt|cdn|oem|novus-api|novus|portal)(\.(au|eu|gov|hsbc|jpn|tv))?\.pendo\.io$/,
11830
11830
  /^https:\/\/([0-9]{8}t[0-9]{4}-dot-)(pendo-au|pendo-eu|pendo-govramp|pendo-hsbc|pendo-io|pendo-jp-prod|pendo-tv|pendo-us1)\.appspot\.com$/,
11831
11831
  /^https:\/\/hotfix-(ops|app)-([0-9]+-dot-)(pendo-au|pendo-eu|pendo-govramp|pendo-hsbc|pendo-io|pendo-jp-prod|pendo-tv|pendo-us1)\.appspot\.com$/,
11832
11832
  /^https:\/\/(pendo-au|pendo-eu|pendo-govramp|pendo-hsbc|pendo-io|pendo-jp-prod|pendo-tv|pendo-us1)-static\.storage\.googleapis\.com$/
@@ -20548,6 +20548,23 @@ var GuideStateModule = (function () {
20548
20548
  };
20549
20549
  })();
20550
20550
 
20551
+ function prioritizeAdoptOrCoreGuides(guides, shouldPrioritizeAdopt) {
20552
+ var adoptGuides = [];
20553
+ var coreGuides = [];
20554
+ _.each(guides, function (guide) {
20555
+ if (guide.isTraining) {
20556
+ adoptGuides.push(guide);
20557
+ }
20558
+ else {
20559
+ coreGuides.push(guide);
20560
+ }
20561
+ });
20562
+ if (shouldPrioritizeAdopt) {
20563
+ return adoptGuides.concat(coreGuides);
20564
+ }
20565
+ return coreGuides.concat(adoptGuides);
20566
+ }
20567
+
20551
20568
  var AutoDisplay = (function () {
20552
20569
  return {
20553
20570
  canDisplay,
@@ -20631,24 +20648,6 @@ var AutoDisplay = (function () {
20631
20648
  var prioritizeAdopt = ConfigReader.get('adoptPrioritizeAdoptGuides');
20632
20649
  return prioritizeAdoptOrCoreGuides(sortedGuides, prioritizeAdopt);
20633
20650
  }
20634
- function prioritizeAdoptOrCoreGuides(guides, shouldPrioritizeAdopt) {
20635
- var adoptGuides = [];
20636
- var coreGuides = [];
20637
- _.each(guides, function (guide) {
20638
- if (guide.isTraining) {
20639
- adoptGuides.push(guide);
20640
- }
20641
- else {
20642
- coreGuides.push(guide);
20643
- }
20644
- });
20645
- if (shouldPrioritizeAdopt) {
20646
- return adoptGuides.concat(coreGuides);
20647
- }
20648
- else {
20649
- return coreGuides.concat(adoptGuides);
20650
- }
20651
- }
20652
20651
  function lastDismissedTime(pendo) {
20653
20652
  return Math.max(pendo.latestDismissedAutoAt || -Infinity, pendo.finalAdvancedAutoAt || -Infinity, pendo.latestSnoozedAutoAt || -Infinity);
20654
20653
  }
@@ -20858,642 +20857,6 @@ class AutoDisplayPhase {
20858
20857
  }
20859
20858
  }
20860
20859
 
20861
- /*
20862
- * Guide Loop
20863
- *
20864
- * The function that gets called to process and handle the ever
20865
- * changing landscape of a web app to best handle showing / hiding of
20866
- * guides as needed.
20867
- */
20868
- /*
20869
- * Holds the timeout handle for the startGuides process.
20870
- * This should work as a simple monitor on that process
20871
- * to prevent it from being run too often.
20872
- */
20873
- var guidesProcessingThreadHandle = null;
20874
- var DEFAULT_TIMER_LENGTH = 500;
20875
- var waitThenLoop = function (delayAmount) {
20876
- delayAmount = delayAmount || DEFAULT_TIMER_LENGTH;
20877
- guidesProcessingThreadHandle = _.delay(function () {
20878
- guidesProcessingThreadHandle = null;
20879
- startGuides();
20880
- }, delayAmount);
20881
- pendo$1.guidesProcessingThreadHandle = guidesProcessingThreadHandle;
20882
- };
20883
- var clearLoopTimer = function () {
20884
- if (guidesProcessingThreadHandle) {
20885
- clearTimeout(guidesProcessingThreadHandle);
20886
- guidesProcessingThreadHandle = null;
20887
- pendo$1.guidesProcessingThreadHandle = guidesProcessingThreadHandle;
20888
- }
20889
- store.dispatch('guideUpdate/stopScheduledUpdate');
20890
- };
20891
- /**
20892
- * Clears any showing guides and prevents any loaded guides from rendering.
20893
- * Loaded guides remain in memory, so calling startGuides will work just fine
20894
- * after a stopGuides call.
20895
- *
20896
- * @access public
20897
- * @category Guides
20898
- * @example
20899
- * pendo.stopGuides()
20900
- */
20901
- var stopGuides = function () {
20902
- hideGuides();
20903
- updateRunner.cancel();
20904
- // Remove other stuff.
20905
- for (var i = 0; i < activeElements.length; i++) {
20906
- var elem = activeElements[i];
20907
- elem.parentNode.removeChild(elem);
20908
- }
20909
- activeElements.length = 0;
20910
- store.dispatch('guideUpdate/stopObserver');
20911
- Events.guideLoopStopped.trigger();
20912
- };
20913
- /**
20914
- * Starts the process that loops over all currently loaded guides and
20915
- * determines what to show. Checks multi-step guide continuation,
20916
- * auto-display guides, launcher guides, and badges.
20917
- *
20918
- * @access public
20919
- * @category Guides
20920
- * @example
20921
- * pendo.startGuides()
20922
- */
20923
- var startGuides = function () {
20924
- clearLoopTimer();
20925
- if (isGuideRequestPending()) {
20926
- log.info('guides are loading.', { contexts: ['guides', 'loading'] });
20927
- return;
20928
- }
20929
- if (areGuidesDisabled()) {
20930
- log.info('guides are disabled.', { contexts: ['guides', 'disabled'] });
20931
- return;
20932
- }
20933
- if (areGuidesDelayed()) {
20934
- log.info('guides are delayed.', { contexts: ['guides', 'delayed'] });
20935
- return;
20936
- }
20937
- if (store.getters['guideState/tabLostFocus']())
20938
- return;
20939
- try {
20940
- const activeGuides = getActiveGuides();
20941
- if (!activeGuides || activeGuides.length === 0)
20942
- return;
20943
- store.dispatch('guideUpdate/startObserverIfNeeded');
20944
- if (updateRunner.isComplete()) {
20945
- updateRunner.markDirty();
20946
- }
20947
- if (leaderExists()) {
20948
- updateRunner.process();
20949
- }
20950
- }
20951
- catch (e) {
20952
- updateRunner.cancel();
20953
- log.critical('ERROR in guide-loop', { error: e });
20954
- }
20955
- finally {
20956
- if (!updateRunner.paused) {
20957
- continueOrCompleteUpdate();
20958
- }
20959
- }
20960
- };
20961
- function continueOrCompleteUpdate() {
20962
- if (!updateRunner.isComplete()) {
20963
- waitThenLoop(50);
20964
- }
20965
- else {
20966
- if (!store.state.guideUpdate.useObserver || store.state.guideUpdate.needsUpdate) {
20967
- waitThenLoop();
20968
- }
20969
- Events.guideUpdateComplete.trigger();
20970
- }
20971
- }
20972
- /*
20973
- * If delayGuides is true, removes the delay and starts guides.
20974
- * Otherwise, it is the same as calling startGuides.
20975
- */
20976
- var manuallyStartGuides = function () {
20977
- setGuidesDelayed(false);
20978
- startGuides();
20979
- };
20980
- var manuallyStopGuides = function () {
20981
- if (!areGuidesDisabled()) {
20982
- setGuidesDelayed(true);
20983
- }
20984
- resetPendoUI();
20985
- };
20986
- // a phase that only has one unit of work per update
20987
- class SimplePhase {
20988
- constructor(name, processFn) {
20989
- this.dirty = false;
20990
- this.name = name;
20991
- this.processFn = _.isFunction(processFn) ? processFn : _.noop;
20992
- }
20993
- markDirty() {
20994
- this.dirty = true;
20995
- }
20996
- process() {
20997
- this.processFn();
20998
- this.dirty = false;
20999
- }
21000
- cancel() {
21001
- this.dirty = false;
21002
- }
21003
- isComplete() {
21004
- return !this.dirty;
21005
- }
21006
- }
21007
- /**
21008
- * Runs on a timer whenever a guide is displayed.
21009
- *
21010
- * @access private
21011
- * @param {Guide} guide The currently visible guide
21012
- * @param {GuideStep} step The currently visible step
21013
- */
21014
- function guideShowingProc() {
21015
- const shownGuides = _.compact(_.map(_.filter(frameLocalGuides(getActiveGuides()), (guide) => guide.isShown()), (guide) => {
21016
- if (BuildingBlockResourceCenter.isModule(guide)) {
21017
- return BuildingBlockResourceCenter.getResourceCenter();
21018
- }
21019
- else {
21020
- return guide;
21021
- }
21022
- }));
21023
- _.each(shownGuides, function (guide) {
21024
- _.each(guide.steps, function (step) {
21025
- if (step.isShown()) {
21026
- stepShowingProc(guide, step);
21027
- }
21028
- });
21029
- guide.checkForHiddenGroupSteps();
21030
- });
21031
- }
21032
- function stepShowingProc(guide, step) {
21033
- if (step.isLocked()) {
21034
- return;
21035
- }
21036
- if (store.getters['frames/isShownInAnotherFrame']()(step)) {
21037
- return;
21038
- }
21039
- var element = step.element;
21040
- var targetElement = step.targetElement;
21041
- var guideElement = step.guideElement;
21042
- var guideElm = dom('.' + GUIDE_CSS_NAME);
21043
- // As part of the guides accessibility work, we need to make sure that all
21044
- // guide content remain visible and/or accessible during text-only (font) zoom.
21045
- // Since text zoom does NOT trigger our existing resize listener, the
21046
- // alternative is to track text zoom via font-size changes.
21047
- var guideContainer = guideElement.find('#pendo-guide-container')[0];
21048
- var guideHasTextContent = guideContainer && guideContainer.textContent;
21049
- if (guideHasTextContent) {
21050
- var currentBrowserFontSize = getComputedStyle_safe(guideContainer).fontSize;
21051
- var hasBrowserFontSizeChanged = step.attributes.currentTextZoomFontSize !== currentBrowserFontSize;
21052
- if (hasBrowserFontSizeChanged) {
21053
- if (step.attributes.currentTextZoomFontSize) {
21054
- step.redraw();
21055
- }
21056
- step.attributes.currentTextZoomFontSize = currentBrowserFontSize;
21057
- }
21058
- }
21059
- syncColorMode(step, true);
21060
- if (step.elementPathRule && targetElement && !SizzleProxy.matchesSelector(targetElement, step.elementPathRule)) {
21061
- step.hide();
21062
- return;
21063
- }
21064
- const embedSelector = _.get(guide, 'attributes.embedConfig.selector');
21065
- if (embedSelector && !dom(embedSelector).length) {
21066
- step.hide();
21067
- return;
21068
- }
21069
- var elementIsStillValidTarget = isElementVisible(element);
21070
- var elementMatchesContainsRules = true;
21071
- // targetElement is specifically the elementPathRule dom node
21072
- // element is either the element path rule OR the badge element
21073
- // we can rely on the isElementVisible check as a shortcut for having to call isElementVisible twice
21074
- if (targetElement && elementIsStillValidTarget && step.elementContainsRulesList) {
21075
- // Only bother trying to check element contains rules if the element is still visible
21076
- elementMatchesContainsRules = doesElementMatchContainsRules(targetElement, step.elementContainsRulesList);
21077
- elementIsStillValidTarget = elementMatchesContainsRules;
21078
- }
21079
- if (element && (elementIsStillValidTarget || guideElm.hasClass('mouseover'))) {
21080
- step.checkPlacement();
21081
- return;
21082
- }
21083
- var isTooltip = (step.type === 'tooltip' || step.attributes.calculatedType === 'tooltip');
21084
- if (isTooltip && wouldBeVisibleAfterAutoScroll(element) && elementMatchesContainsRules) {
21085
- // the element was scrolled out of view, the guide should be invisible for now, but still "active"
21086
- return;
21087
- }
21088
- step.hide();
21089
- }
21090
- class BadgePhase {
21091
- constructor() {
21092
- this.name = 'badge';
21093
- this.queue = [];
21094
- }
21095
- markDirty() {
21096
- this.queue.push.apply(this.queue, _.filter(frameLocalGuides(getActiveGuides()), isBadge));
21097
- }
21098
- process(monitor) {
21099
- var badge = this.queue.shift();
21100
- monitor.saveContext(badge);
21101
- if (badge) {
21102
- badge.placeBadge();
21103
- }
21104
- }
21105
- handleProcessTimeExceeded(monitor) {
21106
- const badge = monitor.getContext();
21107
- log.warn('Badge (' + badge.id + ') timeout exceeded');
21108
- }
21109
- cancel() {
21110
- this.queue.length = 0;
21111
- }
21112
- isComplete() {
21113
- return !this.queue.length;
21114
- }
21115
- }
21116
- function resourceCenterShowingProc(resourceCenter, resourceCenterBadgeProc) {
21117
- var reEvaluateModuleContent = false;
21118
- // Find eligible modules
21119
- var controlGuidesInAllModules = [];
21120
- var eligibleModules = _.filter(resourceCenter.modules, function (module) {
21121
- if (!module)
21122
- return false;
21123
- // Iframe modules can not dynamically show/hide.
21124
- if (BuildingBlockResourceCenter.isIframeModule(module))
21125
- return true;
21126
- // Sandbox modules can not dynamically show/hide.
21127
- if (BuildingBlockResourceCenter.isSandboxModule(module))
21128
- return true;
21129
- if (BuildingBlockResourceCenter.isIntegrationModule(module)) {
21130
- if (!BuildingBlockResourceCenter.integrationModuleHasContent(module)) {
21131
- module.hasResourceCenterContent = false;
21132
- return false;
21133
- }
21134
- else {
21135
- return true;
21136
- }
21137
- }
21138
- var eligibleGuidesInModule = _.filter(module.guidesInModule, function (guide) {
21139
- if (!guide.shouldBeAddedToResourceCenter()) {
21140
- guide.ineligibleForRC = true;
21141
- if (guide.control) {
21142
- controlGuidesInAllModules.push(guide);
21143
- }
21144
- return false;
21145
- }
21146
- guide.ineligibleForRC = false;
21147
- return true;
21148
- });
21149
- var isEligible = eligibleGuidesInModule.length;
21150
- module.hasResourceCenterContent = !!isEligible;
21151
- // We only need to hash the active module, no other module has content rendered
21152
- if (resourceCenter.activeModule && module.id === resourceCenter.activeModule.id) {
21153
- var moduleContentHash = crc32(_.map(eligibleGuidesInModule, function (guide) {
21154
- // don't change content hash when announcements update their seen state, as that will cause
21155
- // the RC to re-render (and lose scroll position)
21156
- var isAnnouncementOrWhatsNew = guide.isAnnouncement() || guide.isWhatsNew();
21157
- var seenState = isAnnouncementOrWhatsNew ? null : _.pluck(guide.steps, 'seenState');
21158
- return {
21159
- id: guide.id,
21160
- seenState
21161
- };
21162
- }));
21163
- if (module.eligibleGuidesInModuleHash && moduleContentHash !== module.eligibleGuidesInModuleHash)
21164
- reEvaluateModuleContent = true;
21165
- module.eligibleGuidesInModuleHash = moduleContentHash;
21166
- }
21167
- return isEligible;
21168
- });
21169
- // If we are rendering the home view and its modules change re-render it
21170
- var reEvaluateHomeViewContent = false;
21171
- if (!resourceCenter.activeModule) {
21172
- var eligibleModulesHash = crc32(_.map(eligibleModules, function (module) {
21173
- return {
21174
- id: module.id
21175
- };
21176
- }));
21177
- if (resourceCenter.eligibleModulesHash && resourceCenter.eligibleModulesHash !== eligibleModulesHash) {
21178
- reEvaluateHomeViewContent = true;
21179
- BuildingBlockResourceCenter.replaceResourceCenterContent(resourceCenter.id, undefined, true);
21180
- }
21181
- resourceCenter.eligibleModulesHash = eligibleModulesHash;
21182
- }
21183
- resourceCenterBadgeProc(resourceCenter, eligibleModules, controlGuidesInAllModules);
21184
- // Skip directly to the one eligible module
21185
- if (eligibleModules.length === 1) {
21186
- resourceCenter.skipResourceCenterHomeView = true;
21187
- resourceCenter.moduleIdToReplaceHomeViewWith = eligibleModules[0].id;
21188
- var noActiveModule = !resourceCenter.activeModule && reEvaluateHomeViewContent;
21189
- var differentActiveModule = resourceCenter.activeModule && resourceCenter.activeModule.id !== eligibleModules[0].id;
21190
- // If the one module is different or content needs to be reevaluated, reload
21191
- // or if we are on the home view and the modules on the home view have changed
21192
- if (noActiveModule || differentActiveModule || reEvaluateModuleContent) {
21193
- BuildingBlockResourceCenter.replaceResourceCenterContent(resourceCenter.moduleIdToReplaceHomeViewWith, undefined, true);
21194
- }
21195
- }
21196
- if (eligibleModules) {
21197
- // find the in-progress native integration module and set continueToNativeModule on both the
21198
- // RC container and the module since both will try to render
21199
- var continuedModule = _.find(eligibleModules, function (module) {
21200
- return module.isContinuation(getLastGuideStepSeen());
21201
- });
21202
- var shouldNotResume = _.get(continuedModule, 'attributes.doNotResume');
21203
- var integrationProvider = _.get(continuedModule, 'attributes.resourceCenter.integrationProvider');
21204
- var isNativeModule = BuildingBlockResourceCenter.isSupportedNativeIntegration(integrationProvider);
21205
- if (isNativeModule && !shouldNotResume && resourceCenter) {
21206
- resourceCenter.continueToNativeModule = true;
21207
- continuedModule.continueToNativeModule = true;
21208
- }
21209
- }
21210
- // Ensure the active module is still eligible and re-evaluate its content if need be.
21211
- // Go to Home View if active module is no longer eligible
21212
- var activeModuleisEligible;
21213
- if (resourceCenter.activeModule) {
21214
- activeModuleisEligible = _.find(eligibleModules, function (module) {
21215
- return module.id === resourceCenter.activeModule.id;
21216
- });
21217
- if (activeModuleisEligible && reEvaluateModuleContent) {
21218
- BuildingBlockResourceCenter.replaceResourceCenterContent(resourceCenter.activeModule.id, undefined, true);
21219
- }
21220
- if (!activeModuleisEligible) {
21221
- BuildingBlockResourceCenter.replaceResourceCenterContent(resourceCenter.id);
21222
- }
21223
- }
21224
- BuildingBlockResourceCenter.repositionResourceCenter();
21225
- }
21226
- function resourceCenterNotShowingProc(resourceCenter, resourceCenterBadgeProc) {
21227
- var eligibleModules = [];
21228
- var controlGuidesInAllModules = [];
21229
- var staticModule = _.find(resourceCenter.modules, function (module) {
21230
- if (BuildingBlockResourceCenter.isIframeModule(module))
21231
- return true;
21232
- if (BuildingBlockResourceCenter.isSandboxModule(module))
21233
- return true;
21234
- if (BuildingBlockResourceCenter.isIntegrationModule(module)) {
21235
- if (!BuildingBlockResourceCenter.integrationModuleHasContent(module)) {
21236
- module.hasResourceCenterContent = false;
21237
- return false;
21238
- }
21239
- else {
21240
- return true;
21241
- }
21242
- }
21243
- return false;
21244
- });
21245
- if (staticModule) {
21246
- eligibleModules.push(staticModule);
21247
- }
21248
- var allGuidesInModules = _.flatten(_.pluck(resourceCenter.modules, 'guidesInModule'));
21249
- controlGuidesInAllModules = _.filter(_.compact(allGuidesInModules), function (guide) {
21250
- return guide.control;
21251
- });
21252
- if (!eligibleModules.length) {
21253
- var moduleLookup = _.reduce(resourceCenter.modules, function (lookup, module) {
21254
- _.each(module.guidesInModule, function (guide) {
21255
- lookup[guide.id] = module;
21256
- });
21257
- return lookup;
21258
- }, {});
21259
- var guidesWithSteps = _.filter(allGuidesInModules, function (guide) {
21260
- return guide && guide.steps && guide.steps.length;
21261
- });
21262
- // Sort guides so that untargeted guides are first
21263
- var sortedGuides = _.sortBy(guidesWithSteps, function (guide) {
21264
- return guide.steps[0].elementPathRule ? 1 : 0;
21265
- });
21266
- var firstEligibleGuide = _.find(sortedGuides, function (guide) {
21267
- return guide.shouldBeAddedToResourceCenter();
21268
- });
21269
- var firstEligibleModule = moduleLookup[firstEligibleGuide && firstEligibleGuide.id];
21270
- if (firstEligibleModule) {
21271
- eligibleModules.push(firstEligibleModule);
21272
- }
21273
- }
21274
- resourceCenterBadgeProc(resourceCenter, eligibleModules, controlGuidesInAllModules);
21275
- }
21276
- function resourceCenterBadgeProc(resourceCenter, eligibleModules, controlGuidesInAllModules) {
21277
- var RCBadge = badgesShown[resourceCenter.id];
21278
- // If we get no modules back, hide the RC and RC Badge, clear active module
21279
- if (!eligibleModules || !eligibleModules.length) {
21280
- var isBadgeActivated = resourceCenter.launchMethod.indexOf('badge') !== -1;
21281
- var isElementActivated = resourceCenter.launchMethod.indexOf('dom') !== -1;
21282
- resourceCenter.hasResourceCenterContent = false;
21283
- // If the RC is badge activated, hide it from the dom completely. Otherwise, leave it alone
21284
- // This is important for element-activated RC, which should still display with an empty state
21285
- // even if it has no content
21286
- if (isBadgeActivated) {
21287
- if (resourceCenter.isShown())
21288
- resourceCenter.hide();
21289
- if (RCBadge) {
21290
- BuildingBlockResourceCenter.hideRCBadge(RCBadge, resourceCenter);
21291
- }
21292
- // If all modules are not eligible because of control group guides
21293
- // Send guideNotSeen events where appropriate
21294
- _.each(controlGuidesInAllModules, function (guide) {
21295
- if (!guide.hasGuideBeenControlled()) {
21296
- guide.steps[0].onControlGuide('control');
21297
- }
21298
- });
21299
- resourceCenter.badgeHidden = true;
21300
- delete resourceCenter.activeModule;
21301
- }
21302
- else if (isElementActivated) {
21303
- resourceCenter.showEmptyState = true;
21304
- resourceCenter.controlGuidesInAllModules = controlGuidesInAllModules;
21305
- }
21306
- }
21307
- // Show the badge if it was hidden but now has content
21308
- if (resourceCenter.badgeHidden && resourceCenter.hasResourceCenterContent && !resourceCenter.isNativeModuleActive) {
21309
- if (!RCBadge) {
21310
- if (_.isFunction(resourceCenter.placeBadge))
21311
- resourceCenter.placeBadge();
21312
- RCBadge = badgesShown[resourceCenter.id];
21313
- }
21314
- if (RCBadge)
21315
- RCBadge.show();
21316
- resourceCenter.badgeHidden = false;
21317
- }
21318
- }
21319
- function resourceCenterProc(resourceCenter) {
21320
- if (!resourceCenter || !resourceCenter.launchMethod)
21321
- return;
21322
- // Reset state to start each loop
21323
- resourceCenter.skipResourceCenterHomeView = false;
21324
- resourceCenter.hasResourceCenterContent = true;
21325
- delete resourceCenter.moduleIdToReplaceHomeViewWith;
21326
- var activeModule = resourceCenter.activeModule;
21327
- var activeIntegrationProvider = _.get(activeModule, 'attributes.resourceCenter.integrationProvider');
21328
- resourceCenter.isNativeModuleActive = activeModule && BuildingBlockResourceCenter.isSupportedNativeIntegration(activeIntegrationProvider);
21329
- if (resourceCenter.isShown() || !store.getters['frames/isLeader']()) {
21330
- resourceCenterShowingProc(resourceCenter, resourceCenterBadgeProc);
21331
- }
21332
- else {
21333
- resourceCenterNotShowingProc(resourceCenter, resourceCenterBadgeProc);
21334
- }
21335
- // Hide the badge if we're displaying a native integration module
21336
- if (resourceCenter.isNativeModuleActive) {
21337
- if (resourceCenter.isShown())
21338
- resourceCenter.hide();
21339
- var RCBadge = badgesShown[resourceCenter.id];
21340
- if (RCBadge) {
21341
- BuildingBlockResourceCenter.hideRCBadge(RCBadge, resourceCenter);
21342
- }
21343
- }
21344
- // Place Element Activated Notification Badge
21345
- if (resourceCenter.launchMethod === 'dom' && resourceCenter.canShowOnDevice()) {
21346
- BuildingBlockResourceCenter.updateNotificationBubbles();
21347
- BuildingBlockResourceCenter.adjustNotificationBubbleForResize();
21348
- }
21349
- }
21350
- class PhaseProcessTimeMonitor {
21351
- constructor(timeLimit = 50) {
21352
- this.startTime = -1;
21353
- this.timeLimit = timeLimit;
21354
- this.context = null;
21355
- }
21356
- start() {
21357
- this.context = null;
21358
- this.startTime = getNow();
21359
- }
21360
- isTimeExceeded() {
21361
- const currTime = getNow();
21362
- return (currTime - this.startTime) > this.timeLimit;
21363
- }
21364
- saveContext(context) {
21365
- this.context = context;
21366
- }
21367
- getContext() {
21368
- return this.context;
21369
- }
21370
- }
21371
- class UpdateRunner {
21372
- constructor(phases) {
21373
- this.phases = phases;
21374
- this.queue = [];
21375
- this.paused = false;
21376
- }
21377
- markDirty() {
21378
- for (let phase of this.phases) {
21379
- phase.markDirty();
21380
- }
21381
- this.queue = this.phases.slice();
21382
- }
21383
- runGuidePhase() {
21384
- const currentPhase = this.queue[0];
21385
- var promise;
21386
- const monitor = new PhaseProcessTimeMonitor(50);
21387
- while (!this.timedOut() && !currentPhase.isComplete() && !promise) {
21388
- monitor.start();
21389
- promise = currentPhase.process(monitor);
21390
- if (!promise && monitor.isTimeExceeded()) {
21391
- performanceMonitor.count(GUIDE_LOOP_TIMEOUT);
21392
- if (currentPhase.handleProcessTimeExceeded) {
21393
- currentPhase.handleProcessTimeExceeded(monitor);
21394
- }
21395
- this.ejectGuide(monitor.getContext());
21396
- }
21397
- }
21398
- if (isPromise(promise)) {
21399
- var id = this.updateId;
21400
- var resume = _.bind(function () {
21401
- if (this.updateId === id) {
21402
- this.paused = false;
21403
- startGuides();
21404
- }
21405
- }, this);
21406
- this.paused = true;
21407
- promise.then(resume).catch(resume);
21408
- }
21409
- else if (currentPhase.isComplete()) {
21410
- this.queue.shift();
21411
- }
21412
- }
21413
- addPhase(phaseFn, phaseName) {
21414
- phaseName = phaseName || phaseFn.name || this.phases.length;
21415
- if (_.isFunction(phaseFn)) {
21416
- phaseFn = new SimplePhase(phaseName, phaseFn);
21417
- }
21418
- this.phases.push(phaseFn);
21419
- }
21420
- removePhase(phaseName) {
21421
- var i = _.findIndex(this.phases, function (phase) {
21422
- return phase.name === phaseName;
21423
- });
21424
- if (i >= 0) {
21425
- this.phases.splice(i, 1);
21426
- }
21427
- }
21428
- timedOut() {
21429
- if (!this.iterationStartTime)
21430
- return false;
21431
- var elapsed = getNow() - this.iterationStartTime;
21432
- return elapsed > store.state.guideUpdate.iterationTimeLimit;
21433
- }
21434
- ejectGuide(guide) {
21435
- if (this.ejectOnTimeout === undefined) {
21436
- this.ejectOnTimeout = ConfigReader.get('guides.ejectOnTimeout');
21437
- }
21438
- if (this.ejectOnTimeout && guide && !guide.isShown()) {
21439
- Tombstone.addGuide(guide);
21440
- }
21441
- }
21442
- process() {
21443
- var now = getNow();
21444
- if (!this.updateId) {
21445
- this.updateId = now;
21446
- this.updateTotalTime = 0;
21447
- store.commit('guideUpdate/startUpdate', now);
21448
- }
21449
- this.iterationStartTime = now;
21450
- while (!this.paused &&
21451
- this.queue.length &&
21452
- !this.timedOut()) {
21453
- this.runGuidePhase();
21454
- }
21455
- now = getNow();
21456
- this.updateTotalTime += now - this.iterationStartTime;
21457
- if (this.isComplete()) {
21458
- this.complete(now);
21459
- }
21460
- }
21461
- complete(time) {
21462
- store.commit('guideUpdate/completeUpdate', time);
21463
- this.cancel();
21464
- }
21465
- cancel() {
21466
- this.updateId = null;
21467
- this.paused = false;
21468
- this.queue.length = 0;
21469
- for (let phase of this.phases) {
21470
- if (_.isFunction(phase.cancel)) {
21471
- phase.cancel();
21472
- }
21473
- }
21474
- }
21475
- isComplete() {
21476
- return !this.queue.length;
21477
- }
21478
- }
21479
- var updateRunner = new UpdateRunner([
21480
- new SimplePhase('guideShowing', guideShowingProc),
21481
- new AutoDisplayPhase(),
21482
- new BadgePhase(),
21483
- new SimplePhase('resourceCenter', function () {
21484
- resourceCenterProc(BuildingBlockResourceCenter.getResourceCenter());
21485
- }),
21486
- new SimplePhase('frameVisibility', function () {
21487
- store.dispatch('frames/checkFrameVisibility');
21488
- })
21489
- ]);
21490
- function addUpdatePhase() {
21491
- updateRunner.addPhase(...arguments);
21492
- }
21493
- function removeUpdatePhase() {
21494
- updateRunner.removePhase(...arguments);
21495
- }
21496
-
21497
20860
  function Wrappable() {
21498
20861
  var wrappers = {};
21499
20862
  var wrap = function (wrappedMethod, before, after) {
@@ -22690,119 +22053,810 @@ function ShowOnHover() {
22690
22053
  attached = true;
22691
22054
  mouseout();
22692
22055
  }
22693
- });
22694
- badge.after('hide', function () {
22695
- if (attached) {
22696
- detachEvent(target, 'mouseover', mouseover);
22697
- attached = false;
22056
+ });
22057
+ badge.after('hide', function () {
22058
+ if (attached) {
22059
+ detachEvent(target, 'mouseover', mouseover);
22060
+ attached = false;
22061
+ }
22062
+ mouseout();
22063
+ });
22064
+ }
22065
+ return badge;
22066
+ }
22067
+ var BADGE_CLAIM_PROP = '_pendoBadgeClaimIsTraining';
22068
+ var badgeClaimTargets = [];
22069
+ function resetBadgeTargetClaims() {
22070
+ _.each(badgeClaimTargets, function (target) {
22071
+ target[BADGE_CLAIM_PROP] = undefined;
22072
+ });
22073
+ badgeClaimTargets.length = 0;
22074
+ }
22075
+ function isBadgeTargetClaimedByOppositeTraining(guide, target) {
22076
+ if (!target)
22077
+ return false;
22078
+ var claimedIsTraining = target[BADGE_CLAIM_PROP];
22079
+ if (claimedIsTraining === undefined)
22080
+ return false;
22081
+ return claimedIsTraining !== !!guide.isTraining;
22082
+ }
22083
+ function claimBadgeTarget(guide, target) {
22084
+ if (!target)
22085
+ return;
22086
+ target[BADGE_CLAIM_PROP] = !!guide.isTraining;
22087
+ if (!_.contains(badgeClaimTargets, target)) {
22088
+ badgeClaimTargets.push(target);
22089
+ }
22090
+ }
22091
+ function hideBadgeForGuideInMap(guide, badgesShownObj) {
22092
+ var existingBadge = badgesShownObj[guide.id];
22093
+ if (!existingBadge)
22094
+ return;
22095
+ var badgeStep = existingBadge.step();
22096
+ if (badgeStep && badgeStep.isShown && badgeStep.isShown())
22097
+ return;
22098
+ delete badgesShownObj[guide.id];
22099
+ if (_.isFunction(existingBadge.hide)) {
22100
+ existingBadge.hide();
22101
+ }
22102
+ }
22103
+ /*
22104
+ * Maintains the DOM presence of the badge for the specified guide
22105
+ * where in the badge is either added or removed depending on if
22106
+ * the appropiate DOM element is found.
22107
+ *
22108
+ * For this reason, when Badges are in the play the setTimeout to
22109
+ * keep showing the startGuides needs to also continue running.
22110
+ *
22111
+ * input: guide obj, and Map of ids to badges being shown currently
22112
+ */
22113
+ var placeBadge = function (guide, badgesShownObj) {
22114
+ badgesShownObj = badgesShownObj || badgesShown;
22115
+ var badge = badgesShownObj[guide.id];
22116
+ var step = badge ? badge.step() : Badge.findStep(guide);
22117
+ if (!step)
22118
+ return;
22119
+ var target = badge ? badge.target() : getElementForTargeting(step);
22120
+ var isRcBadge = _.get(guide, 'attributes.resourceCenter');
22121
+ if (isRcBadge && !store.getters['frames/canShowRCBadge']()) {
22122
+ if (badge && _.isFunction(badge.hide)) {
22123
+ // If top frame comes in late and RC badge showing in inner frame bc of delay, hide it here
22124
+ delete badgesShownObj[guide.id];
22125
+ badge.hide();
22126
+ }
22127
+ return;
22128
+ }
22129
+ // Don't render the resource center badge if the resource center itself has nothing to show
22130
+ if (isRcBadge && !guide.hasResourceCenterContent)
22131
+ return;
22132
+ BuildingBlockResourceCenter.updateNotificationBubbles();
22133
+ var passesElementContainsRules = true;
22134
+ if (step.elementContainsRulesList) {
22135
+ passesElementContainsRules = target && doesElementMatchContainsRules(target, step.elementContainsRulesList);
22136
+ }
22137
+ const badgeCanShow = step.elementPathRule &&
22138
+ isElementVisible(target) &&
22139
+ SizzleProxy.matchesSelector(target, step.elementPathRule) &&
22140
+ passesElementContainsRules;
22141
+ if (badgeCanShow) {
22142
+ if (isBadgeTargetClaimedByOppositeTraining(guide, target)) {
22143
+ if (isDebuggingEnabled()) {
22144
+ Events.stepEligibility.trigger({
22145
+ stepId: step.id,
22146
+ guideId: guide.id,
22147
+ eligible: false,
22148
+ reason: 'adopt_badge_priority'
22149
+ });
22150
+ }
22151
+ hideBadgeForGuideInMap(guide, badgesShownObj);
22152
+ return;
22153
+ }
22154
+ claimBadgeTarget(guide, target);
22155
+ if (!badge) {
22156
+ badge = Badge.create(guide);
22157
+ }
22158
+ badge.show();
22159
+ badgesShownObj[guide.id] = badge;
22160
+ }
22161
+ else if (badge) {
22162
+ // if the step is being show for this badge then
22163
+ // don't remove it.
22164
+ if (!step.isShown()) {
22165
+ // 3. If the element is not found, remove it if it's currently shown.
22166
+ delete badgesShownObj[guide.id];
22167
+ badge.hide();
22168
+ }
22169
+ }
22170
+ if (isDebuggingEnabled()) {
22171
+ Events.stepEligibility.trigger({
22172
+ stepId: step.id,
22173
+ guideId: guide.id,
22174
+ eligible: badgeCanShow,
22175
+ reason: 'target_element_badge'
22176
+ });
22177
+ }
22178
+ BuildingBlockResourceCenter.repositionDoubleResourceCenterBadge();
22179
+ };
22180
+ var removeAllBadges = function () {
22181
+ _.map(badgesShown, removeBadge);
22182
+ setBadgesShown({});
22183
+ };
22184
+ var removeBadge = function (badge, guide) {
22185
+ if (!badge)
22186
+ return;
22187
+ if (_.isFunction(badge.hide)) {
22188
+ badge.hide();
22189
+ }
22190
+ if (_.isFunction(badge.shouldShow) && !badge.shouldShow() && guide) {
22191
+ delete badgesShown[guide.id];
22192
+ }
22193
+ };
22194
+ var removeBadgeForGuide = function (guideGroup) {
22195
+ var badge = badgesShown[guideGroup.id];
22196
+ if (badge) {
22197
+ removeBadge(badge, guideGroup);
22198
+ }
22199
+ };
22200
+ function _adjustBadgesForResize() {
22201
+ _.map(badgesShown, function (badge) {
22202
+ if (badge && badge.show) {
22203
+ badge.show();
22204
+ }
22205
+ });
22206
+ }
22207
+ var adjustBadgesForResize = _.throttle(_adjustBadgesForResize, 10);
22208
+ function pruneBadges(activeGuideIds) {
22209
+ if (!activeGuideIds || !activeGuideIds.length)
22210
+ return;
22211
+ const lookup = _.indexBy(activeGuideIds);
22212
+ _.each(badgesShown, function (badge, guideId) {
22213
+ if (!lookup[guideId] && badge) {
22214
+ badge.hide();
22215
+ delete badgesShown[guideId];
22216
+ }
22217
+ });
22218
+ }
22219
+
22220
+ /*
22221
+ * Guide Loop
22222
+ *
22223
+ * The function that gets called to process and handle the ever
22224
+ * changing landscape of a web app to best handle showing / hiding of
22225
+ * guides as needed.
22226
+ */
22227
+ /*
22228
+ * Holds the timeout handle for the startGuides process.
22229
+ * This should work as a simple monitor on that process
22230
+ * to prevent it from being run too often.
22231
+ */
22232
+ var guidesProcessingThreadHandle = null;
22233
+ var DEFAULT_TIMER_LENGTH = 500;
22234
+ var waitThenLoop = function (delayAmount) {
22235
+ delayAmount = delayAmount || DEFAULT_TIMER_LENGTH;
22236
+ guidesProcessingThreadHandle = _.delay(function () {
22237
+ guidesProcessingThreadHandle = null;
22238
+ startGuides();
22239
+ }, delayAmount);
22240
+ pendo$1.guidesProcessingThreadHandle = guidesProcessingThreadHandle;
22241
+ };
22242
+ var clearLoopTimer = function () {
22243
+ if (guidesProcessingThreadHandle) {
22244
+ clearTimeout(guidesProcessingThreadHandle);
22245
+ guidesProcessingThreadHandle = null;
22246
+ pendo$1.guidesProcessingThreadHandle = guidesProcessingThreadHandle;
22247
+ }
22248
+ store.dispatch('guideUpdate/stopScheduledUpdate');
22249
+ };
22250
+ /**
22251
+ * Clears any showing guides and prevents any loaded guides from rendering.
22252
+ * Loaded guides remain in memory, so calling startGuides will work just fine
22253
+ * after a stopGuides call.
22254
+ *
22255
+ * @access public
22256
+ * @category Guides
22257
+ * @example
22258
+ * pendo.stopGuides()
22259
+ */
22260
+ var stopGuides = function () {
22261
+ hideGuides();
22262
+ updateRunner.cancel();
22263
+ // Remove other stuff.
22264
+ for (var i = 0; i < activeElements.length; i++) {
22265
+ var elem = activeElements[i];
22266
+ elem.parentNode.removeChild(elem);
22267
+ }
22268
+ activeElements.length = 0;
22269
+ store.dispatch('guideUpdate/stopObserver');
22270
+ Events.guideLoopStopped.trigger();
22271
+ };
22272
+ /**
22273
+ * Starts the process that loops over all currently loaded guides and
22274
+ * determines what to show. Checks multi-step guide continuation,
22275
+ * auto-display guides, launcher guides, and badges.
22276
+ *
22277
+ * @access public
22278
+ * @category Guides
22279
+ * @example
22280
+ * pendo.startGuides()
22281
+ */
22282
+ var startGuides = function () {
22283
+ clearLoopTimer();
22284
+ if (isGuideRequestPending()) {
22285
+ log.info('guides are loading.', { contexts: ['guides', 'loading'] });
22286
+ return;
22287
+ }
22288
+ if (areGuidesDisabled()) {
22289
+ log.info('guides are disabled.', { contexts: ['guides', 'disabled'] });
22290
+ return;
22291
+ }
22292
+ if (areGuidesDelayed()) {
22293
+ log.info('guides are delayed.', { contexts: ['guides', 'delayed'] });
22294
+ return;
22295
+ }
22296
+ if (store.getters['guideState/tabLostFocus']())
22297
+ return;
22298
+ try {
22299
+ const activeGuides = getActiveGuides();
22300
+ if (!activeGuides || activeGuides.length === 0)
22301
+ return;
22302
+ store.dispatch('guideUpdate/startObserverIfNeeded');
22303
+ if (updateRunner.isComplete()) {
22304
+ updateRunner.markDirty();
22305
+ }
22306
+ if (leaderExists()) {
22307
+ updateRunner.process();
22308
+ }
22309
+ }
22310
+ catch (e) {
22311
+ updateRunner.cancel();
22312
+ log.critical('ERROR in guide-loop', { error: e });
22313
+ }
22314
+ finally {
22315
+ if (!updateRunner.paused) {
22316
+ continueOrCompleteUpdate();
22317
+ }
22318
+ }
22319
+ };
22320
+ function continueOrCompleteUpdate() {
22321
+ if (!updateRunner.isComplete()) {
22322
+ waitThenLoop(50);
22323
+ }
22324
+ else {
22325
+ if (!store.state.guideUpdate.useObserver || store.state.guideUpdate.needsUpdate) {
22326
+ waitThenLoop();
22327
+ }
22328
+ Events.guideUpdateComplete.trigger();
22329
+ }
22330
+ }
22331
+ /*
22332
+ * If delayGuides is true, removes the delay and starts guides.
22333
+ * Otherwise, it is the same as calling startGuides.
22334
+ */
22335
+ var manuallyStartGuides = function () {
22336
+ setGuidesDelayed(false);
22337
+ startGuides();
22338
+ };
22339
+ var manuallyStopGuides = function () {
22340
+ if (!areGuidesDisabled()) {
22341
+ setGuidesDelayed(true);
22342
+ }
22343
+ resetPendoUI();
22344
+ };
22345
+ // a phase that only has one unit of work per update
22346
+ class SimplePhase {
22347
+ constructor(name, processFn) {
22348
+ this.dirty = false;
22349
+ this.name = name;
22350
+ this.processFn = _.isFunction(processFn) ? processFn : _.noop;
22351
+ }
22352
+ markDirty() {
22353
+ this.dirty = true;
22354
+ }
22355
+ process() {
22356
+ this.processFn();
22357
+ this.dirty = false;
22358
+ }
22359
+ cancel() {
22360
+ this.dirty = false;
22361
+ }
22362
+ isComplete() {
22363
+ return !this.dirty;
22364
+ }
22365
+ }
22366
+ /**
22367
+ * Runs on a timer whenever a guide is displayed.
22368
+ *
22369
+ * @access private
22370
+ * @param {Guide} guide The currently visible guide
22371
+ * @param {GuideStep} step The currently visible step
22372
+ */
22373
+ function guideShowingProc() {
22374
+ const shownGuides = _.compact(_.map(_.filter(frameLocalGuides(getActiveGuides()), (guide) => guide.isShown()), (guide) => {
22375
+ if (BuildingBlockResourceCenter.isModule(guide)) {
22376
+ return BuildingBlockResourceCenter.getResourceCenter();
22377
+ }
22378
+ else {
22379
+ return guide;
22380
+ }
22381
+ }));
22382
+ _.each(shownGuides, function (guide) {
22383
+ _.each(guide.steps, function (step) {
22384
+ if (step.isShown()) {
22385
+ stepShowingProc(guide, step);
22386
+ }
22387
+ });
22388
+ guide.checkForHiddenGroupSteps();
22389
+ });
22390
+ }
22391
+ function stepShowingProc(guide, step) {
22392
+ if (step.isLocked()) {
22393
+ return;
22394
+ }
22395
+ if (store.getters['frames/isShownInAnotherFrame']()(step)) {
22396
+ return;
22397
+ }
22398
+ var element = step.element;
22399
+ var targetElement = step.targetElement;
22400
+ var guideElement = step.guideElement;
22401
+ var guideElm = dom('.' + GUIDE_CSS_NAME);
22402
+ // As part of the guides accessibility work, we need to make sure that all
22403
+ // guide content remain visible and/or accessible during text-only (font) zoom.
22404
+ // Since text zoom does NOT trigger our existing resize listener, the
22405
+ // alternative is to track text zoom via font-size changes.
22406
+ var guideContainer = guideElement.find('#pendo-guide-container')[0];
22407
+ var guideHasTextContent = guideContainer && guideContainer.textContent;
22408
+ if (guideHasTextContent) {
22409
+ var currentBrowserFontSize = getComputedStyle_safe(guideContainer).fontSize;
22410
+ var hasBrowserFontSizeChanged = step.attributes.currentTextZoomFontSize !== currentBrowserFontSize;
22411
+ if (hasBrowserFontSizeChanged) {
22412
+ if (step.attributes.currentTextZoomFontSize) {
22413
+ step.redraw();
22414
+ }
22415
+ step.attributes.currentTextZoomFontSize = currentBrowserFontSize;
22416
+ }
22417
+ }
22418
+ syncColorMode(step, true);
22419
+ if (step.elementPathRule && targetElement && !SizzleProxy.matchesSelector(targetElement, step.elementPathRule)) {
22420
+ step.hide();
22421
+ return;
22422
+ }
22423
+ const embedSelector = _.get(guide, 'attributes.embedConfig.selector');
22424
+ if (embedSelector && !dom(embedSelector).length) {
22425
+ step.hide();
22426
+ return;
22427
+ }
22428
+ var elementIsStillValidTarget = isElementVisible(element);
22429
+ var elementMatchesContainsRules = true;
22430
+ // targetElement is specifically the elementPathRule dom node
22431
+ // element is either the element path rule OR the badge element
22432
+ // we can rely on the isElementVisible check as a shortcut for having to call isElementVisible twice
22433
+ if (targetElement && elementIsStillValidTarget && step.elementContainsRulesList) {
22434
+ // Only bother trying to check element contains rules if the element is still visible
22435
+ elementMatchesContainsRules = doesElementMatchContainsRules(targetElement, step.elementContainsRulesList);
22436
+ elementIsStillValidTarget = elementMatchesContainsRules;
22437
+ }
22438
+ if (element && (elementIsStillValidTarget || guideElm.hasClass('mouseover'))) {
22439
+ step.checkPlacement();
22440
+ return;
22441
+ }
22442
+ var isTooltip = (step.type === 'tooltip' || step.attributes.calculatedType === 'tooltip');
22443
+ if (isTooltip && wouldBeVisibleAfterAutoScroll(element) && elementMatchesContainsRules) {
22444
+ // the element was scrolled out of view, the guide should be invisible for now, but still "active"
22445
+ return;
22446
+ }
22447
+ step.hide();
22448
+ }
22449
+ class BadgePhase {
22450
+ constructor() {
22451
+ this.name = 'badge';
22452
+ this.queue = [];
22453
+ }
22454
+ markDirty() {
22455
+ var badgeGuides = _.filter(frameLocalGuides(getActiveGuides()), isBadge);
22456
+ var hasAdoptGuides = _.find(badgeGuides, function (guide) { return guide.isTraining; });
22457
+ if (hasAdoptGuides) {
22458
+ badgeGuides = prioritizeAdoptOrCoreGuides(badgeGuides, ConfigReader.get('adoptPrioritizeAdoptGuides'));
22459
+ }
22460
+ resetBadgeTargetClaims();
22461
+ this.queue.push.apply(this.queue, badgeGuides);
22462
+ }
22463
+ process(monitor) {
22464
+ var badge = this.queue.shift();
22465
+ monitor.saveContext(badge);
22466
+ if (badge) {
22467
+ badge.placeBadge();
22468
+ }
22469
+ }
22470
+ handleProcessTimeExceeded(monitor) {
22471
+ const badge = monitor.getContext();
22472
+ log.warn('Badge (' + badge.id + ') timeout exceeded');
22473
+ }
22474
+ cancel() {
22475
+ this.queue.length = 0;
22476
+ }
22477
+ isComplete() {
22478
+ return !this.queue.length;
22479
+ }
22480
+ }
22481
+ function resourceCenterShowingProc(resourceCenter, resourceCenterBadgeProc) {
22482
+ var reEvaluateModuleContent = false;
22483
+ // Find eligible modules
22484
+ var controlGuidesInAllModules = [];
22485
+ var eligibleModules = _.filter(resourceCenter.modules, function (module) {
22486
+ if (!module)
22487
+ return false;
22488
+ // Iframe modules can not dynamically show/hide.
22489
+ if (BuildingBlockResourceCenter.isIframeModule(module))
22490
+ return true;
22491
+ // Sandbox modules can not dynamically show/hide.
22492
+ if (BuildingBlockResourceCenter.isSandboxModule(module))
22493
+ return true;
22494
+ if (BuildingBlockResourceCenter.isIntegrationModule(module)) {
22495
+ if (!BuildingBlockResourceCenter.integrationModuleHasContent(module)) {
22496
+ module.hasResourceCenterContent = false;
22497
+ return false;
22498
+ }
22499
+ else {
22500
+ return true;
22501
+ }
22502
+ }
22503
+ var eligibleGuidesInModule = _.filter(module.guidesInModule, function (guide) {
22504
+ if (!guide.shouldBeAddedToResourceCenter()) {
22505
+ guide.ineligibleForRC = true;
22506
+ if (guide.control) {
22507
+ controlGuidesInAllModules.push(guide);
22508
+ }
22509
+ return false;
22510
+ }
22511
+ guide.ineligibleForRC = false;
22512
+ return true;
22513
+ });
22514
+ var isEligible = eligibleGuidesInModule.length;
22515
+ module.hasResourceCenterContent = !!isEligible;
22516
+ // We only need to hash the active module, no other module has content rendered
22517
+ if (resourceCenter.activeModule && module.id === resourceCenter.activeModule.id) {
22518
+ var moduleContentHash = crc32(_.map(eligibleGuidesInModule, function (guide) {
22519
+ // don't change content hash when announcements update their seen state, as that will cause
22520
+ // the RC to re-render (and lose scroll position)
22521
+ var isAnnouncementOrWhatsNew = guide.isAnnouncement() || guide.isWhatsNew();
22522
+ var seenState = isAnnouncementOrWhatsNew ? null : _.pluck(guide.steps, 'seenState');
22523
+ return {
22524
+ id: guide.id,
22525
+ seenState
22526
+ };
22527
+ }));
22528
+ if (module.eligibleGuidesInModuleHash && moduleContentHash !== module.eligibleGuidesInModuleHash)
22529
+ reEvaluateModuleContent = true;
22530
+ module.eligibleGuidesInModuleHash = moduleContentHash;
22531
+ }
22532
+ return isEligible;
22533
+ });
22534
+ // If we are rendering the home view and its modules change re-render it
22535
+ var reEvaluateHomeViewContent = false;
22536
+ if (!resourceCenter.activeModule) {
22537
+ var eligibleModulesHash = crc32(_.map(eligibleModules, function (module) {
22538
+ return {
22539
+ id: module.id
22540
+ };
22541
+ }));
22542
+ if (resourceCenter.eligibleModulesHash && resourceCenter.eligibleModulesHash !== eligibleModulesHash) {
22543
+ reEvaluateHomeViewContent = true;
22544
+ BuildingBlockResourceCenter.replaceResourceCenterContent(resourceCenter.id, undefined, true);
22545
+ }
22546
+ resourceCenter.eligibleModulesHash = eligibleModulesHash;
22547
+ }
22548
+ resourceCenterBadgeProc(resourceCenter, eligibleModules, controlGuidesInAllModules);
22549
+ // Skip directly to the one eligible module
22550
+ if (eligibleModules.length === 1) {
22551
+ resourceCenter.skipResourceCenterHomeView = true;
22552
+ resourceCenter.moduleIdToReplaceHomeViewWith = eligibleModules[0].id;
22553
+ var noActiveModule = !resourceCenter.activeModule && reEvaluateHomeViewContent;
22554
+ var differentActiveModule = resourceCenter.activeModule && resourceCenter.activeModule.id !== eligibleModules[0].id;
22555
+ // If the one module is different or content needs to be reevaluated, reload
22556
+ // or if we are on the home view and the modules on the home view have changed
22557
+ if (noActiveModule || differentActiveModule || reEvaluateModuleContent) {
22558
+ BuildingBlockResourceCenter.replaceResourceCenterContent(resourceCenter.moduleIdToReplaceHomeViewWith, undefined, true);
22559
+ }
22560
+ }
22561
+ if (eligibleModules) {
22562
+ // find the in-progress native integration module and set continueToNativeModule on both the
22563
+ // RC container and the module since both will try to render
22564
+ var continuedModule = _.find(eligibleModules, function (module) {
22565
+ return module.isContinuation(getLastGuideStepSeen());
22566
+ });
22567
+ var shouldNotResume = _.get(continuedModule, 'attributes.doNotResume');
22568
+ var integrationProvider = _.get(continuedModule, 'attributes.resourceCenter.integrationProvider');
22569
+ var isNativeModule = BuildingBlockResourceCenter.isSupportedNativeIntegration(integrationProvider);
22570
+ if (isNativeModule && !shouldNotResume && resourceCenter) {
22571
+ resourceCenter.continueToNativeModule = true;
22572
+ continuedModule.continueToNativeModule = true;
22573
+ }
22574
+ }
22575
+ // Ensure the active module is still eligible and re-evaluate its content if need be.
22576
+ // Go to Home View if active module is no longer eligible
22577
+ var activeModuleisEligible;
22578
+ if (resourceCenter.activeModule) {
22579
+ activeModuleisEligible = _.find(eligibleModules, function (module) {
22580
+ return module.id === resourceCenter.activeModule.id;
22581
+ });
22582
+ if (activeModuleisEligible && reEvaluateModuleContent) {
22583
+ BuildingBlockResourceCenter.replaceResourceCenterContent(resourceCenter.activeModule.id, undefined, true);
22584
+ }
22585
+ if (!activeModuleisEligible) {
22586
+ BuildingBlockResourceCenter.replaceResourceCenterContent(resourceCenter.id);
22587
+ }
22588
+ }
22589
+ BuildingBlockResourceCenter.repositionResourceCenter();
22590
+ }
22591
+ function resourceCenterNotShowingProc(resourceCenter, resourceCenterBadgeProc) {
22592
+ var eligibleModules = [];
22593
+ var controlGuidesInAllModules = [];
22594
+ var staticModule = _.find(resourceCenter.modules, function (module) {
22595
+ if (BuildingBlockResourceCenter.isIframeModule(module))
22596
+ return true;
22597
+ if (BuildingBlockResourceCenter.isSandboxModule(module))
22598
+ return true;
22599
+ if (BuildingBlockResourceCenter.isIntegrationModule(module)) {
22600
+ if (!BuildingBlockResourceCenter.integrationModuleHasContent(module)) {
22601
+ module.hasResourceCenterContent = false;
22602
+ return false;
22603
+ }
22604
+ else {
22605
+ return true;
22698
22606
  }
22699
- mouseout();
22607
+ }
22608
+ return false;
22609
+ });
22610
+ if (staticModule) {
22611
+ eligibleModules.push(staticModule);
22612
+ }
22613
+ var allGuidesInModules = _.flatten(_.pluck(resourceCenter.modules, 'guidesInModule'));
22614
+ controlGuidesInAllModules = _.filter(_.compact(allGuidesInModules), function (guide) {
22615
+ return guide.control;
22616
+ });
22617
+ if (!eligibleModules.length) {
22618
+ var moduleLookup = _.reduce(resourceCenter.modules, function (lookup, module) {
22619
+ _.each(module.guidesInModule, function (guide) {
22620
+ lookup[guide.id] = module;
22621
+ });
22622
+ return lookup;
22623
+ }, {});
22624
+ var guidesWithSteps = _.filter(allGuidesInModules, function (guide) {
22625
+ return guide && guide.steps && guide.steps.length;
22626
+ });
22627
+ // Sort guides so that untargeted guides are first
22628
+ var sortedGuides = _.sortBy(guidesWithSteps, function (guide) {
22629
+ return guide.steps[0].elementPathRule ? 1 : 0;
22630
+ });
22631
+ var firstEligibleGuide = _.find(sortedGuides, function (guide) {
22632
+ return guide.shouldBeAddedToResourceCenter();
22700
22633
  });
22634
+ var firstEligibleModule = moduleLookup[firstEligibleGuide && firstEligibleGuide.id];
22635
+ if (firstEligibleModule) {
22636
+ eligibleModules.push(firstEligibleModule);
22637
+ }
22701
22638
  }
22702
- return badge;
22639
+ resourceCenterBadgeProc(resourceCenter, eligibleModules, controlGuidesInAllModules);
22703
22640
  }
22704
- /*
22705
- * Maintains the DOM presence of the badge for the specified guide
22706
- * where in the badge is either added or removed depending on if
22707
- * the appropiate DOM element is found.
22708
- *
22709
- * For this reason, when Badges are in the play the setTimeout to
22710
- * keep showing the startGuides needs to also continue running.
22711
- *
22712
- * input: guide obj, and Map of ids to badges being shown currently
22713
- */
22714
- var placeBadge = function (guide, badgesShownObj) {
22715
- badgesShownObj = badgesShownObj || badgesShown;
22716
- var badge = badgesShownObj[guide.id];
22717
- var step = badge ? badge.step() : Badge.findStep(guide);
22718
- if (!step)
22719
- return;
22720
- var target = badge ? badge.target() : getElementForTargeting(step);
22721
- var isRcBadge = _.get(guide, 'attributes.resourceCenter');
22722
- if (isRcBadge && !store.getters['frames/canShowRCBadge']()) {
22723
- if (badge && _.isFunction(badge.hide)) {
22724
- // If top frame comes in late and RC badge showing in inner frame bc of delay, hide it here
22725
- badgesShownObj[guide.id] = undefined;
22726
- badge.hide();
22641
+ function resourceCenterBadgeProc(resourceCenter, eligibleModules, controlGuidesInAllModules) {
22642
+ var RCBadge = badgesShown[resourceCenter.id];
22643
+ // If we get no modules back, hide the RC and RC Badge, clear active module
22644
+ if (!eligibleModules || !eligibleModules.length) {
22645
+ var isBadgeActivated = resourceCenter.launchMethod.indexOf('badge') !== -1;
22646
+ var isElementActivated = resourceCenter.launchMethod.indexOf('dom') !== -1;
22647
+ resourceCenter.hasResourceCenterContent = false;
22648
+ // If the RC is badge activated, hide it from the dom completely. Otherwise, leave it alone
22649
+ // This is important for element-activated RC, which should still display with an empty state
22650
+ // even if it has no content
22651
+ if (isBadgeActivated) {
22652
+ if (resourceCenter.isShown())
22653
+ resourceCenter.hide();
22654
+ if (RCBadge) {
22655
+ BuildingBlockResourceCenter.hideRCBadge(RCBadge, resourceCenter);
22656
+ }
22657
+ // If all modules are not eligible because of control group guides
22658
+ // Send guideNotSeen events where appropriate
22659
+ _.each(controlGuidesInAllModules, function (guide) {
22660
+ if (!guide.hasGuideBeenControlled()) {
22661
+ guide.steps[0].onControlGuide('control');
22662
+ }
22663
+ });
22664
+ resourceCenter.badgeHidden = true;
22665
+ delete resourceCenter.activeModule;
22666
+ }
22667
+ else if (isElementActivated) {
22668
+ resourceCenter.showEmptyState = true;
22669
+ resourceCenter.controlGuidesInAllModules = controlGuidesInAllModules;
22727
22670
  }
22728
- return;
22729
22671
  }
22730
- // Don't render the resource center badge if the resource center itself has nothing to show
22731
- if (isRcBadge && !guide.hasResourceCenterContent)
22672
+ // Show the badge if it was hidden but now has content
22673
+ if (resourceCenter.badgeHidden && resourceCenter.hasResourceCenterContent && !resourceCenter.isNativeModuleActive) {
22674
+ if (!RCBadge) {
22675
+ if (_.isFunction(resourceCenter.placeBadge))
22676
+ resourceCenter.placeBadge();
22677
+ RCBadge = badgesShown[resourceCenter.id];
22678
+ }
22679
+ if (RCBadge)
22680
+ RCBadge.show();
22681
+ resourceCenter.badgeHidden = false;
22682
+ }
22683
+ }
22684
+ function resourceCenterProc(resourceCenter) {
22685
+ if (!resourceCenter || !resourceCenter.launchMethod)
22732
22686
  return;
22733
- BuildingBlockResourceCenter.updateNotificationBubbles();
22734
- var passesElementContainsRules = true;
22735
- if (step.elementContainsRulesList) {
22736
- passesElementContainsRules = target && doesElementMatchContainsRules(target, step.elementContainsRulesList);
22687
+ // Reset state to start each loop
22688
+ resourceCenter.skipResourceCenterHomeView = false;
22689
+ resourceCenter.hasResourceCenterContent = true;
22690
+ delete resourceCenter.moduleIdToReplaceHomeViewWith;
22691
+ var activeModule = resourceCenter.activeModule;
22692
+ var activeIntegrationProvider = _.get(activeModule, 'attributes.resourceCenter.integrationProvider');
22693
+ resourceCenter.isNativeModuleActive = activeModule && BuildingBlockResourceCenter.isSupportedNativeIntegration(activeIntegrationProvider);
22694
+ if (resourceCenter.isShown() || !store.getters['frames/isLeader']()) {
22695
+ resourceCenterShowingProc(resourceCenter, resourceCenterBadgeProc);
22737
22696
  }
22738
- const badgeCanShow = step.elementPathRule &&
22739
- isElementVisible(target) &&
22740
- SizzleProxy.matchesSelector(target, step.elementPathRule) &&
22741
- passesElementContainsRules;
22742
- if (badgeCanShow) {
22743
- if (!badge) {
22744
- badge = Badge.create(guide);
22697
+ else {
22698
+ resourceCenterNotShowingProc(resourceCenter, resourceCenterBadgeProc);
22699
+ }
22700
+ // Hide the badge if we're displaying a native integration module
22701
+ if (resourceCenter.isNativeModuleActive) {
22702
+ if (resourceCenter.isShown())
22703
+ resourceCenter.hide();
22704
+ var RCBadge = badgesShown[resourceCenter.id];
22705
+ if (RCBadge) {
22706
+ BuildingBlockResourceCenter.hideRCBadge(RCBadge, resourceCenter);
22745
22707
  }
22746
- badge.show();
22747
- badgesShownObj[guide.id] = badge;
22748
22708
  }
22749
- else if (badge) {
22750
- // if the step is being show for this badge then
22751
- // don't remove it.
22752
- if (!step.isShown()) {
22753
- // 3. If the element is not found, remove it if it's currently shown.
22754
- badgesShownObj[guide.id] = undefined;
22755
- badge.hide();
22709
+ // Place Element Activated Notification Badge
22710
+ if (resourceCenter.launchMethod === 'dom' && resourceCenter.canShowOnDevice()) {
22711
+ BuildingBlockResourceCenter.updateNotificationBubbles();
22712
+ BuildingBlockResourceCenter.adjustNotificationBubbleForResize();
22713
+ }
22714
+ }
22715
+ class PhaseProcessTimeMonitor {
22716
+ constructor(timeLimit = 50) {
22717
+ this.startTime = -1;
22718
+ this.timeLimit = timeLimit;
22719
+ this.context = null;
22720
+ }
22721
+ start() {
22722
+ this.context = null;
22723
+ this.startTime = getNow();
22724
+ }
22725
+ isTimeExceeded() {
22726
+ const currTime = getNow();
22727
+ return (currTime - this.startTime) > this.timeLimit;
22728
+ }
22729
+ saveContext(context) {
22730
+ this.context = context;
22731
+ }
22732
+ getContext() {
22733
+ return this.context;
22734
+ }
22735
+ }
22736
+ class UpdateRunner {
22737
+ constructor(phases) {
22738
+ this.phases = phases;
22739
+ this.queue = [];
22740
+ this.paused = false;
22741
+ }
22742
+ markDirty() {
22743
+ for (let phase of this.phases) {
22744
+ phase.markDirty();
22756
22745
  }
22746
+ this.queue = this.phases.slice();
22757
22747
  }
22758
- if (isDebuggingEnabled()) {
22759
- Events.stepEligibility.trigger({
22760
- stepId: step.id,
22761
- guideId: guide.id,
22762
- eligible: badgeCanShow,
22763
- reason: 'target_element_badge'
22748
+ runGuidePhase() {
22749
+ const currentPhase = this.queue[0];
22750
+ var promise;
22751
+ const monitor = new PhaseProcessTimeMonitor(50);
22752
+ while (!this.timedOut() && !currentPhase.isComplete() && !promise) {
22753
+ monitor.start();
22754
+ promise = currentPhase.process(monitor);
22755
+ if (!promise && monitor.isTimeExceeded()) {
22756
+ performanceMonitor.count(GUIDE_LOOP_TIMEOUT);
22757
+ if (currentPhase.handleProcessTimeExceeded) {
22758
+ currentPhase.handleProcessTimeExceeded(monitor);
22759
+ }
22760
+ this.ejectGuide(monitor.getContext());
22761
+ }
22762
+ }
22763
+ if (isPromise(promise)) {
22764
+ var id = this.updateId;
22765
+ var resume = _.bind(function () {
22766
+ if (this.updateId === id) {
22767
+ this.paused = false;
22768
+ startGuides();
22769
+ }
22770
+ }, this);
22771
+ this.paused = true;
22772
+ promise.then(resume).catch(resume);
22773
+ }
22774
+ else if (currentPhase.isComplete()) {
22775
+ this.queue.shift();
22776
+ }
22777
+ }
22778
+ addPhase(phaseFn, phaseName) {
22779
+ phaseName = phaseName || phaseFn.name || this.phases.length;
22780
+ if (_.isFunction(phaseFn)) {
22781
+ phaseFn = new SimplePhase(phaseName, phaseFn);
22782
+ }
22783
+ this.phases.push(phaseFn);
22784
+ }
22785
+ removePhase(phaseName) {
22786
+ var i = _.findIndex(this.phases, function (phase) {
22787
+ return phase.name === phaseName;
22764
22788
  });
22789
+ if (i >= 0) {
22790
+ this.phases.splice(i, 1);
22791
+ }
22765
22792
  }
22766
- BuildingBlockResourceCenter.repositionDoubleResourceCenterBadge();
22767
- };
22768
- var removeAllBadges = function () {
22769
- _.map(badgesShown, removeBadge);
22770
- setBadgesShown({});
22771
- };
22772
- var removeBadge = function (badge, guide) {
22773
- if (!badge)
22774
- return;
22775
- if (_.isFunction(badge.hide)) {
22776
- badge.hide();
22793
+ timedOut() {
22794
+ if (!this.iterationStartTime)
22795
+ return false;
22796
+ var elapsed = getNow() - this.iterationStartTime;
22797
+ return elapsed > store.state.guideUpdate.iterationTimeLimit;
22777
22798
  }
22778
- if (_.isFunction(badge.shouldShow) && !badge.shouldShow() && guide) {
22779
- delete badgesShown[guide.id];
22799
+ ejectGuide(guide) {
22800
+ if (this.ejectOnTimeout === undefined) {
22801
+ this.ejectOnTimeout = ConfigReader.get('guides.ejectOnTimeout');
22802
+ }
22803
+ if (this.ejectOnTimeout && guide && !guide.isShown()) {
22804
+ Tombstone.addGuide(guide);
22805
+ }
22780
22806
  }
22781
- };
22782
- var removeBadgeForGuide = function (guideGroup) {
22783
- var badge = badgesShown[guideGroup.id];
22784
- if (badge) {
22785
- removeBadge(badge, guideGroup);
22807
+ process() {
22808
+ var now = getNow();
22809
+ if (!this.updateId) {
22810
+ this.updateId = now;
22811
+ this.updateTotalTime = 0;
22812
+ store.commit('guideUpdate/startUpdate', now);
22813
+ }
22814
+ this.iterationStartTime = now;
22815
+ while (!this.paused &&
22816
+ this.queue.length &&
22817
+ !this.timedOut()) {
22818
+ this.runGuidePhase();
22819
+ }
22820
+ now = getNow();
22821
+ this.updateTotalTime += now - this.iterationStartTime;
22822
+ if (this.isComplete()) {
22823
+ this.complete(now);
22824
+ }
22786
22825
  }
22787
- };
22788
- function _adjustBadgesForResize() {
22789
- _.map(badgesShown, function (badge) {
22790
- if (badge && badge.show) {
22791
- badge.show();
22826
+ complete(time) {
22827
+ store.commit('guideUpdate/completeUpdate', time);
22828
+ this.cancel();
22829
+ }
22830
+ cancel() {
22831
+ this.updateId = null;
22832
+ this.paused = false;
22833
+ this.queue.length = 0;
22834
+ for (let phase of this.phases) {
22835
+ if (_.isFunction(phase.cancel)) {
22836
+ phase.cancel();
22837
+ }
22792
22838
  }
22793
- });
22839
+ }
22840
+ isComplete() {
22841
+ return !this.queue.length;
22842
+ }
22794
22843
  }
22795
- var adjustBadgesForResize = _.throttle(_adjustBadgesForResize, 10);
22796
- function pruneBadges(activeGuideIds) {
22797
- if (!activeGuideIds || !activeGuideIds.length)
22798
- return;
22799
- const lookup = _.indexBy(activeGuideIds);
22800
- _.each(badgesShown, function (badge, guideId) {
22801
- if (!lookup[guideId]) {
22802
- badge.hide();
22803
- delete badgesShown[guideId];
22804
- }
22805
- });
22844
+ var updateRunner = new UpdateRunner([
22845
+ new SimplePhase('guideShowing', guideShowingProc),
22846
+ new AutoDisplayPhase(),
22847
+ new BadgePhase(),
22848
+ new SimplePhase('resourceCenter', function () {
22849
+ resourceCenterProc(BuildingBlockResourceCenter.getResourceCenter());
22850
+ }),
22851
+ new SimplePhase('frameVisibility', function () {
22852
+ store.dispatch('frames/checkFrameVisibility');
22853
+ })
22854
+ ]);
22855
+ function addUpdatePhase() {
22856
+ updateRunner.addPhase(...arguments);
22857
+ }
22858
+ function removeUpdatePhase() {
22859
+ updateRunner.removePhase(...arguments);
22806
22860
  }
22807
22861
 
22808
22862
  /**
@@ -23517,7 +23571,7 @@ function registerPostrenderScripts(runtime) {
23517
23571
  });
23518
23572
  }
23519
23573
 
23520
- const pendoLocalStorage$3 = agentStorage.getLocal();
23574
+ const pendoLocalStorage$2 = agentStorage.getLocal();
23521
23575
  var guideEvtCache = [];
23522
23576
  var controlGuideLogMessage = 'Guide was not shown because this visitor is in a control group of an active experiment for the guide';
23523
23577
  /**
@@ -25315,7 +25369,7 @@ var handleResizeOnVideoFullScreenChange = function (event) {
25315
25369
  function shouldReloadGuides(url, visitorId, accountId, metadataHash) {
25316
25370
  if (!url || !pendo$1.apiKey)
25317
25371
  return false;
25318
- if (areGuidesDisabled() && !(pendoLocalStorage$3.getItem('pendo-designer-mode') === 'true'))
25372
+ if (areGuidesDisabled() && !(pendoLocalStorage$2.getItem('pendo-designer-mode') === 'true'))
25319
25373
  return false;
25320
25374
  return (url !== reloadGuides.lastUrl ||
25321
25375
  visitorId !== reloadGuides.lastVisitorId ||
@@ -25399,6 +25453,180 @@ function getActiveGuide(options) {
25399
25453
  };
25400
25454
  }
25401
25455
 
25456
+ var RELAY_FRAME_ID = 'pendo-live-preview-relay';
25457
+ var RELAY_FILE = 'live-preview.html';
25458
+ var MESSAGE_CONFIG = 'pendo-live-preview::config';
25459
+ var MESSAGE_READY = 'pendo-live-preview::ready';
25460
+ var MESSAGE_UPDATE = 'pendo-live-preview::update';
25461
+ var MESSAGE_END = 'pendo-live-preview::end';
25462
+ var MESSAGE_ENDED = 'pendo-live-preview::ended';
25463
+ var MESSAGE_END_ACK = 'pendo-live-preview::end-ack';
25464
+ var RELAY_END_MESSAGE_TIMEOUT = 2000;
25465
+ var activeRelay = null;
25466
+ function isRelayMessage(event, relay) {
25467
+ return event.source === relay.frame.contentWindow && !!event.data;
25468
+ }
25469
+ function sendConfig(relay) {
25470
+ if (!relay.frame.contentWindow)
25471
+ return;
25472
+ relay.frame.contentWindow.postMessage({
25473
+ type: MESSAGE_CONFIG,
25474
+ livePreview: relay.payload
25475
+ }, relay.origin);
25476
+ }
25477
+ function teardownRelay(relay) {
25478
+ window.removeEventListener('message', relay.listener, false);
25479
+ dom(relay.frame).remove();
25480
+ }
25481
+ function waitForRelayEndMessage(relay, onStopped) {
25482
+ var timer = setTimeout$1(finish, RELAY_END_MESSAGE_TIMEOUT);
25483
+ function finish() {
25484
+ clearTimeout(timer);
25485
+ window.removeEventListener('message', onMessage, false);
25486
+ onStopped();
25487
+ }
25488
+ function onMessage(event) {
25489
+ if (!isRelayMessage(event, relay))
25490
+ return;
25491
+ if (event.data.type === MESSAGE_END_ACK || event.data.type === MESSAGE_ENDED) {
25492
+ finish();
25493
+ }
25494
+ }
25495
+ window.addEventListener('message', onMessage, false);
25496
+ }
25497
+ function getRelayUrl() {
25498
+ // TODO: Support fetching the relay URL from designer lookasides
25499
+ var host = DESIGNER_SERVER || SERVER;
25500
+ var url = host + '/in-app-designer/latest/' + RELAY_FILE;
25501
+ var separator = url.indexOf('?') === -1 ? '?' : '&';
25502
+ return url + separator + 'parentOrigin=' + encodeURIComponent(window.location.origin);
25503
+ }
25504
+ function getRelayOrigin(url) {
25505
+ try {
25506
+ return new URL(url).origin;
25507
+ }
25508
+ catch (err) {
25509
+ return '*';
25510
+ }
25511
+ }
25512
+ function resolveSurvivingPreviewPosition(previousGuide, updatedGuide, savedPosition) {
25513
+ if (!savedPosition || !updatedGuide)
25514
+ return savedPosition;
25515
+ var updatedSteps = updatedGuide.steps || [];
25516
+ var updatedGuideId = updatedGuide.id;
25517
+ if (!updatedSteps.length || savedPosition.guideId !== updatedGuideId)
25518
+ return savedPosition;
25519
+ if (savedPosition.state !== 'active' && savedPosition.state !== 'advanced')
25520
+ return savedPosition;
25521
+ var targetStepId = savedPosition.state === 'advanced' && savedPosition.destinationStepId;
25522
+ if (!targetStepId)
25523
+ targetStepId = savedPosition.guideStepId;
25524
+ var savedGuideStepSurvives = _.some(updatedSteps, { id: savedPosition.guideStepId });
25525
+ var targetStepSurvives = _.some(updatedSteps, { id: targetStepId });
25526
+ if (savedGuideStepSurvives && targetStepSurvives)
25527
+ return savedPosition;
25528
+ var survivingStepId;
25529
+ if (targetStepSurvives) {
25530
+ survivingStepId = targetStepId;
25531
+ }
25532
+ else {
25533
+ const previousSteps = previousGuide && previousGuide.id === updatedGuideId
25534
+ ? previousGuide.steps || []
25535
+ : [];
25536
+ const targetStepIndex = _.findIndex(previousSteps, { id: targetStepId });
25537
+ const clampedStepIndex = Math.min(Math.max(targetStepIndex, 0), updatedSteps.length - 1);
25538
+ survivingStepId = updatedSteps[clampedStepIndex].id;
25539
+ }
25540
+ return {
25541
+ guideId: updatedGuideId,
25542
+ guideStepId: survivingStepId,
25543
+ state: 'active'
25544
+ };
25545
+ }
25546
+ function createRelayFrame(url) {
25547
+ var frame = document.createElement('iframe');
25548
+ frame.id = RELAY_FRAME_ID;
25549
+ frame.setAttribute('aria-hidden', 'true');
25550
+ frame.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-storage-access-by-user-activation');
25551
+ frame.style.display = 'none';
25552
+ frame.src = url;
25553
+ return frame;
25554
+ }
25555
+ function hasLivePreviewRelay() {
25556
+ return !!activeRelay;
25557
+ }
25558
+ function applyRelayUpdate(payload) {
25559
+ var previousGuide = _.get(store.state, 'preview.designerPreview.guide');
25560
+ var updatedGuide = payload.guide;
25561
+ var savedPosition = _.get(store.state, 'preview.preview.state') || getLastGuideStepSeen();
25562
+ var survivingPosition = resolveSurvivingPreviewPosition(previousGuide, updatedGuide, savedPosition);
25563
+ store.commit('preview/setDesignerPreview', payload);
25564
+ if (survivingPosition !== savedPosition) {
25565
+ survivingPosition.time = getNow();
25566
+ writeLastStepSeenCache(survivingPosition);
25567
+ store.dispatch('preview/update', survivingPosition);
25568
+ }
25569
+ forceGuideReload();
25570
+ }
25571
+ function startLivePreviewRelay(livePreview, onEnded) {
25572
+ if (!livePreview || activeRelay)
25573
+ return;
25574
+ var url = getRelayUrl();
25575
+ var frame = createRelayFrame(url);
25576
+ var relay = {
25577
+ frame,
25578
+ origin: getRelayOrigin(url),
25579
+ payload: livePreview,
25580
+ listener: onMessage
25581
+ };
25582
+ function onMessage(event) {
25583
+ if (!isRelayMessage(event, relay))
25584
+ return;
25585
+ if (event.data.type === MESSAGE_READY) {
25586
+ sendConfig(relay);
25587
+ return;
25588
+ }
25589
+ if (event.data.type === MESSAGE_ENDED) {
25590
+ teardownRelay(relay);
25591
+ activeRelay = null;
25592
+ if (onEnded)
25593
+ onEnded();
25594
+ return;
25595
+ }
25596
+ if (event.data.type !== MESSAGE_UPDATE)
25597
+ return;
25598
+ applyRelayUpdate(event.data.payload);
25599
+ }
25600
+ activeRelay = relay;
25601
+ window.addEventListener('message', onMessage, false);
25602
+ getBody().appendChild(frame);
25603
+ }
25604
+ function stopLivePreviewRelay(onStopped) {
25605
+ var relay = activeRelay;
25606
+ activeRelay = null;
25607
+ if (!relay) {
25608
+ if (onStopped)
25609
+ onStopped();
25610
+ return;
25611
+ }
25612
+ if (!relay.frame.contentWindow) {
25613
+ teardownRelay(relay);
25614
+ if (onStopped)
25615
+ onStopped();
25616
+ return;
25617
+ }
25618
+ window.removeEventListener('message', relay.listener, false);
25619
+ // Removing the frame now would destroy the ::end below, so the callback removes it once the relay answers
25620
+ waitForRelayEndMessage(relay, function () {
25621
+ dom(relay.frame).remove();
25622
+ if (onStopped)
25623
+ onStopped();
25624
+ });
25625
+ relay.frame.contentWindow.postMessage({
25626
+ type: MESSAGE_END
25627
+ }, relay.origin);
25628
+ }
25629
+
25402
25630
  var REMOVE_FRAME_ACTION = 'frames/removeFrame';
25403
25631
  function tryParseJson(messageData) {
25404
25632
  if (!_.isString(messageData)) {
@@ -25913,7 +26141,8 @@ function initializeCrossFrameChannel(store) {
25913
26141
  return () => crossFrameChannel.close();
25914
26142
  }
25915
26143
 
25916
- const pendoLocalStorage$2 = agentStorage.getLocal();
26144
+ const pendoLocalStorage$1 = agentStorage.getLocal();
26145
+ var pendoLivePreview$1 = 'pendo-live-preview';
25917
26146
  var pendoPreview$1 = 'pendo-preview';
25918
26147
  var designerPreview$1 = 'current-guide-preview';
25919
26148
  var previewFrame;
@@ -25927,6 +26156,11 @@ function startPreviewMode(window) {
25927
26156
  return;
25928
26157
  addPreviewUI(window);
25929
26158
  store.dispatch('preview/start', _.extend(config));
26159
+ if (config.livePreview) {
26160
+ startLivePreviewRelay(config.livePreview, function () {
26161
+ exitPreviewMode({ preventWindowClose: false });
26162
+ });
26163
+ }
25930
26164
  agentStorage.clear(LAST_STEP_ADVANCED_COOKIE);
25931
26165
  enableCookies(false);
25932
26166
  buffers.lock();
@@ -25990,7 +26224,7 @@ function restartPreview(activeGuides, lastGuideStepSeen, language) {
25990
26224
  var config = findStoredPreviewConfig();
25991
26225
  // Go back to 1st step of walkthrough on explicit restart
25992
26226
  if (config) {
25993
- var previewGuide = _.findWhere(activeGuides, { id: config.guideId });
26227
+ var previewGuide = _.findWhere(activeGuides, { id: getPreviewGuideId(config) });
25994
26228
  if (previewGuide) {
25995
26229
  var restartConfig = { stepId: previewGuide.steps[0].id };
25996
26230
  // language is used in `previewGuideRequest` to generate the proper urls for rendering translations
@@ -26114,6 +26348,7 @@ function prepareDesignerPreviewGuide() {
26114
26348
  currentPreviewGuideConfig.guide.expiresAfter = null;
26115
26349
  return [currentPreviewGuideConfig.guide];
26116
26350
  }
26351
+ return [];
26117
26352
  }
26118
26353
  function preparePreviewGuides(activeGuides) {
26119
26354
  var config = findStoredPreviewConfig();
@@ -26192,7 +26427,8 @@ function preparePreviewLastGuideStepSeen(activeGuides, lastGuideStepSeen) {
26192
26427
  var config = findStoredPreviewConfig();
26193
26428
  if (!config)
26194
26429
  return lastGuideStepSeen;
26195
- var previewGuide = _.findWhere(activeGuides, { id: config.guideId });
26430
+ var guideId = getPreviewGuideId(config);
26431
+ var previewGuide = _.findWhere(activeGuides, { id: guideId });
26196
26432
  if (!previewGuide)
26197
26433
  return lastGuideStepSeen;
26198
26434
  if (config.state)
@@ -26205,7 +26441,7 @@ function preparePreviewLastGuideStepSeen(activeGuides, lastGuideStepSeen) {
26205
26441
  return {};
26206
26442
  }
26207
26443
  return {
26208
- guideId: config.guideId,
26444
+ guideId,
26209
26445
  guideStepId: config.stepId,
26210
26446
  state: 'active'
26211
26447
  };
@@ -26253,9 +26489,13 @@ function updatePreview(document, activeGuides, lastGuideStepSeen) {
26253
26489
  }
26254
26490
  }, location.origin);
26255
26491
  }
26492
+ var previewGuideId = getPreviewGuideId(config);
26256
26493
  var guide = _.find(activeGuides, function (guide) {
26257
- return guide.id === config.guideId;
26494
+ return guide.id === previewGuideId;
26258
26495
  });
26496
+ // In live preview the guide may not have been relayed yet; skip this tick and retry on the next.
26497
+ if (!guide && config.livePreview)
26498
+ return;
26259
26499
  var currentStepIndex = 0;
26260
26500
  var stepCount = guide.steps.length;
26261
26501
  var lastSeen = lastGuideStepSeen || {};
@@ -26417,7 +26657,7 @@ function exitPreviewMode(designerConfig) {
26417
26657
  if (currentPreviewGuideConfig) {
26418
26658
  wasInDesignerSameTabPreview = currentPreviewGuideConfig.previewModeType === 'SAME_TAB';
26419
26659
  }
26420
- var storedNavigationMode = pendoLocalStorage$2.getItem('pendo-navigation-state');
26660
+ var storedNavigationMode = pendoLocalStorage$1.getItem('pendo-navigation-state');
26421
26661
  store.dispatch('preview/exit');
26422
26662
  hideGuides({ stayHidden: true });
26423
26663
  agentStorage.clear(LAST_STEP_ADVANCED_COOKIE);
@@ -26426,30 +26666,37 @@ function exitPreviewMode(designerConfig) {
26426
26666
  restoreGuideLoader();
26427
26667
  removeUpdatePhase(updatePreviewUI);
26428
26668
  enableCookies(true);
26429
- // Try to launch back into designer
26430
- if (wasInDesignerSameTabPreview && storedNavigationMode) {
26431
- pendoLocalStorage$2.setItem(designerPreview$1, JSON.stringify(_.extend(currentPreviewGuideConfig, { isExitBackToDesigner: true })));
26432
- try {
26433
- var message = JSON.parse(storedNavigationMode);
26434
- var launchOptions = {
26435
- target: message.baseUrl,
26436
- gcsBucket: message.gcsBucket,
26437
- preloader: true,
26438
- DADesigner: message.isDADesigner,
26439
- host: message.host,
26440
- forceNativeDesigner: currentPreviewGuideConfig.guide.appPlatform === 'web'
26441
- };
26442
- store.dispatch('preview/launchInAppDesigner', launchOptions);
26443
- }
26444
- catch (err) {
26445
- store.dispatch('preview/exitDesignerPreview');
26669
+ function finishExit() {
26670
+ // Try to launch back into designer
26671
+ if (wasInDesignerSameTabPreview && storedNavigationMode) {
26672
+ pendoLocalStorage$1.setItem(designerPreview$1, JSON.stringify(_.extend(currentPreviewGuideConfig, { isExitBackToDesigner: true })));
26673
+ try {
26674
+ var message = JSON.parse(storedNavigationMode);
26675
+ var launchOptions = {
26676
+ target: message.baseUrl,
26677
+ gcsBucket: message.gcsBucket,
26678
+ preloader: true,
26679
+ DADesigner: message.isDADesigner,
26680
+ host: message.host,
26681
+ forceNativeDesigner: currentPreviewGuideConfig.guide.appPlatform === 'web'
26682
+ };
26683
+ store.dispatch('preview/launchInAppDesigner', launchOptions);
26684
+ }
26685
+ catch (err) {
26686
+ store.dispatch('preview/exitDesignerPreview');
26687
+ }
26688
+ return;
26446
26689
  }
26690
+ if (designerConfig && !designerConfig.preventWindowClose)
26691
+ window.close();
26692
+ // For cases when window.close() does not work, as when exiting restored preview sessions in newly opened windows, resume normal guide display
26693
+ forceGuideReload();
26694
+ }
26695
+ if (hasLivePreviewRelay()) {
26696
+ stopLivePreviewRelay(finishExit);
26447
26697
  return;
26448
26698
  }
26449
- if (designerConfig && !designerConfig.preventWindowClose)
26450
- window.close();
26451
- // For cases when window.close() does not work, as when exiting restored preview sessions in newly opened windows, resume normal guide display
26452
- forceGuideReload();
26699
+ finishExit();
26453
26700
  }
26454
26701
  function clonePreviewConfig(getStore, path) {
26455
26702
  var previewConfig = _.get(getStore(), path);
@@ -26462,6 +26709,14 @@ function getStore() {
26462
26709
  }
26463
26710
  var findStoredPreviewConfig = _.partial(clonePreviewConfig, getStore, 'state.preview.preview');
26464
26711
  var findStoredDesignerPreviewConfig = _.partial(clonePreviewConfig, getStore, 'state.preview.designerPreview');
26712
+ function getPreviewGuideId(config) {
26713
+ if (config.guideId)
26714
+ return config.guideId;
26715
+ // config.guideId is empty for unsaved guides in live preview
26716
+ // Live preview relies on guide id from the websocket relay instead
26717
+ if (config.livePreview)
26718
+ return _.get(findStoredDesignerPreviewConfig(), 'guide.id');
26719
+ }
26465
26720
  function getGuideUrl(config) {
26466
26721
  if (!config.subscriptionId || !config.guideId)
26467
26722
  return;
@@ -26510,6 +26765,35 @@ function previewGuideRequest(config, id) {
26510
26765
  log.info('Invalid origin, exiting preview');
26511
26766
  return q.reject('invalid origin');
26512
26767
  }
26768
+ // In live preview, the guide comes from the websocket relay (see live-preview-relay.js) and
26769
+ // stored on state.preview.designerPreview. We render it directly and skip the backend fetch.
26770
+ if (config.livePreview) {
26771
+ var livePreviewConfig = findStoredDesignerPreviewConfig();
26772
+ var liveGuides = prepareDesignerPreviewGuide();
26773
+ if (!livePreviewConfig || !liveGuides.length || !liveGuides[0].steps.length) {
26774
+ pendo$1.guidesPayload({
26775
+ id,
26776
+ lastGuideStepSeen: {},
26777
+ guides: []
26778
+ });
26779
+ return q.resolve();
26780
+ }
26781
+ var liveGuide = liveGuides[0];
26782
+ coerceGuideForPreview(liveGuide);
26783
+ formatGuideSeenStateForPreviewGuide(liveGuide, config.stepId);
26784
+ pendo$1.guideWidget = {
26785
+ enabled: false,
26786
+ data: { guideCssUrl: livePreviewConfig.guideCssUrl }
26787
+ };
26788
+ pendo$1.guideCssUrl = livePreviewConfig.guideCssUrl;
26789
+ pendo$1.throttling = undefined;
26790
+ pendo$1.guidesPayload({
26791
+ id,
26792
+ lastGuideStepSeen: preparePreviewLastGuideStepSeen(liveGuides, pendo$1.lastGuideStepSeen),
26793
+ guides: liveGuides
26794
+ });
26795
+ return q.resolve();
26796
+ }
26513
26797
  var guideURL = getGuideUrl(config);
26514
26798
  if (!guideURL) {
26515
26799
  log.info('Invalid guide url, exiting preview');
@@ -26599,9 +26883,16 @@ function previewGuideRequest(config, id) {
26599
26883
  }
26600
26884
  function previewLoadGuideJs(url, callback) {
26601
26885
  var config = findStoredPreviewConfig();
26602
- if (!config || !getGuideUrl(config))
26886
+ if (!config)
26603
26887
  return q.reject();
26604
26888
  var match = /[?&]id=(\d+)/.exec(url);
26889
+ // Live preview has no backend guide URL, but the url still carries the internal guide-request id
26890
+ // that guidesPayload matches against; render the relayed guide directly.
26891
+ if (config.livePreview) {
26892
+ return previewGuideRequest(config, match && match[1]).then(callback);
26893
+ }
26894
+ if (!getGuideUrl(config))
26895
+ return q.reject();
26605
26896
  if (!match || match.length < 1)
26606
26897
  return q.reject();
26607
26898
  return previewGuideRequest(config, match[1]).then(callback);
@@ -26725,7 +27016,7 @@ const Keys = (function () {
26725
27016
  };
26726
27017
  })();
26727
27018
 
26728
- const pendoLocalStorage$1 = agentStorage.getLocal();
27019
+ const pendoLocalStorage = agentStorage.getLocal();
26729
27020
  var DesignerV2 = (function () {
26730
27021
  // Always use the production server for CNAME customers
26731
27022
  var host = DESIGNER_SERVER || SERVER;
@@ -26739,7 +27030,7 @@ var DesignerV2 = (function () {
26739
27030
  function localStorageNavigation(options) {
26740
27031
  if (options.ignoreLocalStorageNavigation)
26741
27032
  return;
26742
- var storedNavigationMode = pendoLocalStorage$1.getItem('pendo-navigation-state');
27033
+ var storedNavigationMode = pendoLocalStorage.getItem('pendo-navigation-state');
26743
27034
  if (!storedNavigationMode)
26744
27035
  return;
26745
27036
  // only launch from local storage if you are the leader frame
@@ -27456,6 +27747,16 @@ function launchDesignerOrPreview(options) {
27456
27747
  * @label designerPreview
27457
27748
  */
27458
27749
  agentStorage.registry.addLocal(designerPreview$1, { localStorageOnly: true });
27750
+ /**
27751
+ * Ephemeral live-preview session handoff. Scoped to the tab so it survives reloads
27752
+ * but is never restored in a freshly opened preview tab.
27753
+ *
27754
+ * @name pendo-live-preview
27755
+ * @category Cookies/sessionStorage
27756
+ * @access public
27757
+ * @label pendoLivePreview
27758
+ */
27759
+ agentStorage.registry.addSession(pendoLivePreview$1);
27459
27760
  // in order, try to launch:
27460
27761
  // 1) designer
27461
27762
  // 2) preview
@@ -28704,6 +29005,7 @@ const PluginAPI = {
28704
29005
  SERVER,
28705
29006
  DESIGNER_SERVER
28706
29007
  },
29008
+ isTrustedOrigin,
28707
29009
  NodeSerializer,
28708
29010
  q,
28709
29011
  SendQueue,
@@ -34447,7 +34749,16 @@ var FramesModule = (function () {
34447
34749
  testSelector(context, payload) {
34448
34750
  const { selector, sourceFrame, promiseId } = payload;
34449
34751
  const startTime = performance.now();
34450
- const count = pendo$1.dom(selector).length;
34752
+ let count = 0;
34753
+ // Sizzle only ever matches selectors. pendo.dom would parse a payload that
34754
+ // starts with '<' as HTML, through innerHTML on the live document (APP-165872).
34755
+ // It also swallowed invalid selectors, which Sizzle throws on.
34756
+ try {
34757
+ count = SizzleProxy(selector).length;
34758
+ }
34759
+ catch (e) {
34760
+ count = 0;
34761
+ }
34451
34762
  const timing = performance.now() - startTime;
34452
34763
  if (promiseId && sourceFrame) {
34453
34764
  crossFrameChannel.postMessage({
@@ -35035,7 +35346,7 @@ var MetadataModule = (function () {
35035
35346
  };
35036
35347
  })();
35037
35348
 
35038
- const pendoLocalStorage = agentStorage.getLocal();
35349
+ var pendoLivePreview = 'pendo-live-preview';
35039
35350
  var pendoPreview = 'pendo-preview';
35040
35351
  var designerPreview = 'current-guide-preview';
35041
35352
  var PreviewModule = (function () {
@@ -35059,11 +35370,15 @@ var PreviewModule = (function () {
35059
35370
  load(context) {
35060
35371
  try {
35061
35372
  var storage = context.getters.storage();
35062
- var config = JSON.parse(storage.getItem(pendoPreview));
35373
+ var config = JSON.parse(storage.getLocal().getItem(pendoPreview));
35063
35374
  if (config.apiKey === pendo$1.apiKey) {
35375
+ var livePreviewConfig = JSON.parse(storage.getSession().getItem(pendoLivePreview));
35376
+ if (livePreviewConfig) {
35377
+ config.livePreview = livePreviewConfig;
35378
+ }
35064
35379
  context.commit('setPreview', config);
35065
35380
  }
35066
- var designerPreviewConfig = storage.getItem(designerPreview);
35381
+ var designerPreviewConfig = storage.getLocal().getItem(designerPreview);
35067
35382
  if (designerPreviewConfig) {
35068
35383
  context.commit('setDesignerPreview', JSON.parse(designerPreviewConfig));
35069
35384
  }
@@ -35074,11 +35389,19 @@ var PreviewModule = (function () {
35074
35389
  write(context) {
35075
35390
  try {
35076
35391
  var storage = context.getters.storage();
35077
- if (context.state.preview) {
35078
- storage.setItem(pendoPreview, JSON.stringify(context.state.preview));
35392
+ var preview = context.state.preview;
35393
+ var livePreview = preview && preview.livePreview;
35394
+ if (preview) {
35395
+ storage.getLocal().setItem(pendoPreview, JSON.stringify(_.omit(preview, 'livePreview')));
35396
+ }
35397
+ else {
35398
+ storage.getLocal().removeItem(pendoPreview);
35399
+ }
35400
+ if (livePreview) {
35401
+ storage.getSession().setItem(pendoLivePreview, JSON.stringify(livePreview));
35079
35402
  }
35080
35403
  else {
35081
- storage.removeItem(pendoPreview);
35404
+ storage.getSession().removeItem(pendoLivePreview);
35082
35405
  }
35083
35406
  }
35084
35407
  catch (e) {
@@ -35090,8 +35413,7 @@ var PreviewModule = (function () {
35090
35413
  },
35091
35414
  exitDesignerPreview(context) {
35092
35415
  context.commit('setDesignerPreview', null);
35093
- var storage = context.getters.storage();
35094
- storage.removeItem(designerPreview);
35416
+ context.getters.storage().getLocal().removeItem(designerPreview);
35095
35417
  },
35096
35418
  launchInAppDesigner(context, launchOptions) {
35097
35419
  DesignerV2.launchInAppDesigner(launchOptions);
@@ -35118,7 +35440,7 @@ var PreviewModule = (function () {
35118
35440
  return !!state.designerPreview && getters.isInPreviewMode();
35119
35441
  },
35120
35442
  storage() {
35121
- return pendoLocalStorage;
35443
+ return agentStorage;
35122
35444
  }
35123
35445
  };
35124
35446
  return {
@@ -35274,13 +35596,10 @@ function highlight(dom, elements) {
35274
35596
  }
35275
35597
  function highlightElements(dom, selector) {
35276
35598
  try {
35277
- const query = dom(selector);
35278
- const total = query ? query.length : 0;
35279
- const elements = [];
35280
- for (let i = 0; i < total; i++) {
35281
- elements.push(query[i]);
35282
- }
35283
- return highlight(dom, elements);
35599
+ // Sizzle rather than dom() so a selector arriving over the cross-frame channel
35600
+ // can't reach dom()'s innerHTML branch (APP-165872). dom is still needed below
35601
+ // for the overlay elements it builds.
35602
+ return highlight(dom, SizzleProxy(selector));
35284
35603
  }
35285
35604
  catch (e) {
35286
35605
  clearHighlights();
@@ -40919,6 +41238,14 @@ class DOMPrompt {
40919
41238
  });
40920
41239
  }
40921
41240
  setupObserver() {
41241
+ if (!document.body) {
41242
+ this.observerReadyListener = () => {
41243
+ document.removeEventListener('DOMContentLoaded', this.observerReadyListener);
41244
+ this.setupObserver();
41245
+ };
41246
+ document.addEventListener('DOMContentLoaded', this.observerReadyListener);
41247
+ return;
41248
+ }
40922
41249
  this.promptContainer = new this.dom.Observer();
40923
41250
  const validInputEls = this._.filter(this.inputEls, el => el);
40924
41251
  const validSubmitEls = this._.filter(this.submitEls, el => el);
@@ -40994,6 +41321,9 @@ class DOMPrompt {
40994
41321
  });
40995
41322
  }
40996
41323
  teardown() {
41324
+ if (this.observerReadyListener) {
41325
+ document.removeEventListener('DOMContentLoaded', this.observerReadyListener);
41326
+ }
40997
41327
  if (this.promptContainer) {
40998
41328
  this.promptContainer.teardown();
40999
41329
  }
@@ -41038,7 +41368,8 @@ class PromptPlugin {
41038
41368
  // register config schema
41039
41369
  this.api.ConfigReader.addOption(this.configName, ['pendoconfig', 'snippet']);
41040
41370
  this.loadConfig(this.configName)
41041
- .then(config => this.onConfigLoaded(config)).catch(() => { });
41371
+ .then(config => this.onConfigLoaded(config))
41372
+ .catch(err => this.api.log.error('[PromptPlugin] Failed to load config:', err));
41042
41373
  // Listen for URL changes to re-evaluate page rules
41043
41374
  // guidesLoaded: normalizedUrl comes back from the guides payload
41044
41375
  // urlChanged: handles SPA navigation where guides may already be cached and guidesLoaded won't re-fire
@@ -41104,7 +41435,12 @@ class PromptPlugin {
41104
41435
  const isCurrentlyActive = this._.contains(currentlyActiveIds, aiAgent.id);
41105
41436
  const shouldBeActive = this.testPageRule(aiAgent.pageRule, currentUrl, aiAgent.id);
41106
41437
  if (shouldBeActive && !isCurrentlyActive) {
41107
- this.observePrompt(aiAgent);
41438
+ try {
41439
+ this.observePrompt(aiAgent);
41440
+ }
41441
+ catch (err) {
41442
+ this.api.log.error('[PromptPlugin] Failed to set up agent:', aiAgent.id, err);
41443
+ }
41108
41444
  }
41109
41445
  });
41110
41446
  }
@@ -41153,7 +41489,12 @@ class PromptPlugin {
41153
41489
  this._.each(domAgents, aiAgent => {
41154
41490
  const currentUrl = getNormalizedUrl();
41155
41491
  if (this.testPageRule(aiAgent.pageRule, currentUrl, aiAgent.id)) {
41156
- this.observePrompt(aiAgent);
41492
+ try {
41493
+ this.observePrompt(aiAgent);
41494
+ }
41495
+ catch (err) {
41496
+ this.api.log.error('[PromptPlugin] Failed to set up agent:', aiAgent.id, err);
41497
+ }
41157
41498
  }
41158
41499
  });
41159
41500
  }
@@ -43468,6 +43809,7 @@ const MESSAGE_PROMISE = 'promise';
43468
43809
  * @property {*} requestId - The id of the request. This is used to match responses to requests.
43469
43810
  * @property {*} sender - The name of the sender. This is used to determine if the message is from a trusted source.
43470
43811
  * @property {*} destination - The destination of the message. This is used to determine where to send the message.
43812
+ * @property {*} origin - The origin a received message arrived from. Responses are targeted back at it.
43471
43813
  */
43472
43814
  function Message(name, type, data) {
43473
43815
  this.name = name;
@@ -43496,6 +43838,7 @@ const fromEvent = (event) => {
43496
43838
  message.requestId = requestId;
43497
43839
  message.sender = sender;
43498
43840
  message.destination = event.source;
43841
+ message.origin = event.origin;
43499
43842
  message.isResponse = isResponse;
43500
43843
 
43501
43844
  return message;
@@ -43505,7 +43848,7 @@ const fromEvent = (event) => {
43505
43848
  };
43506
43849
 
43507
43850
  const serialize = (message) => {
43508
- const redacted = omit(message, ['destination', 'toString', 'data']);
43851
+ const redacted = omit(message, ['destination', 'origin', 'toString', 'data']);
43509
43852
  redacted.data = JSON.stringify(message.data, (key, value) => {
43510
43853
  if (typeof value === 'function' || value instanceof RegExp) {
43511
43854
  return value.toString ? value.toString() : undefined; // return undefined to omit
@@ -43621,14 +43964,24 @@ const state = {
43621
43964
  topics: {},
43622
43965
  senderIds: {},
43623
43966
  originProvider: undefined,
43967
+ isOriginAllowed: undefined,
43624
43968
  destinationProvider: undefined
43625
43969
  };
43626
43970
 
43627
43971
  const getListener = () => `${state.appName}${state.connectionName}(${state.frameId})`;
43628
43972
 
43973
+ // An opaque origin (sandboxed frame, file://) serializes to the string "null",
43974
+ // which postMessage rejects as a targetOrigin, so those fall back to the provider.
43975
+ const getTargetOrigin = (message) => {
43976
+ if (message.origin && message.origin !== 'null') return message.origin;
43977
+
43978
+ return state.originProvider();
43979
+ };
43980
+
43629
43981
  const _send = (message) => {
43630
43982
  if (!message.sender) message.sender = getListener();
43631
43983
  const win = message.destination || state.destinationProvider();
43984
+ const targetOrigin = getTargetOrigin(message);
43632
43985
  const redactedMessage = serialize(message);
43633
43986
 
43634
43987
  const port = getPortForSender(message.sender);
@@ -43638,11 +43991,11 @@ const _send = (message) => {
43638
43991
  // XXX Need to handle the scenario where MessageChannel is unavailable
43639
43992
  const channel = new MessageChannel();
43640
43993
  addPort(channel.port1, message.sender);
43641
- win.postMessage(redactedMessage, state.originProvider(), [channel.port2]);
43994
+ win.postMessage(redactedMessage, targetOrigin, [channel.port2]);
43642
43995
  } else if (port) {
43643
43996
  port.postMessage(redactedMessage);
43644
43997
  } else {
43645
- win.postMessage(redactedMessage, state.originProvider());
43998
+ win.postMessage(redactedMessage, targetOrigin);
43646
43999
  }
43647
44000
  };
43648
44001
 
@@ -43772,6 +44125,8 @@ const commonFns = pipe(
43772
44125
  );
43773
44126
 
43774
44127
  const filterWrongOrigin = (event) => {
44128
+ if (state.isOriginAllowed) return state.isOriginAllowed(event.origin) ? event : false;
44129
+
43775
44130
  if (state.originProvider() === '*') return event;
43776
44131
 
43777
44132
  return event.origin === state.originProvider() ? event : false;
@@ -43836,7 +44191,8 @@ const startListening = (
43836
44191
  originProvider,
43837
44192
  destinationProvider,
43838
44193
  usePingAutoReconnect = false,
43839
- connectionName
44194
+ connectionName,
44195
+ isOriginAllowed
43840
44196
  ) => {
43841
44197
  if (!window.addEventListener) return;
43842
44198
  state.appName = appName;
@@ -43845,6 +44201,7 @@ const startListening = (
43845
44201
  state.usePingAutoReconnect = usePingAutoReconnect;
43846
44202
  state.connectionName = connectionName || '';
43847
44203
 
44204
+ state.isOriginAllowed = typeof isOriginAllowed === 'function' ? isOriginAllowed : undefined;
43848
44205
  state.originProvider = originProvider || (() => window.location.origin || '*');
43849
44206
  state.destinationProvider = destinationProvider || (() => window.parent || window.top);
43850
44207
 
@@ -43920,7 +44277,11 @@ function VocPortal() {
43920
44277
  pluginAPI = PluginAPI;
43921
44278
  const { frameId } = PluginAPI.EventTracer.addTracerIds({});
43922
44279
  this.removeResizeEvent = pendoGlobal.attachEvent(window, 'resize', resizePortalIframe);
43923
- startListening('VocPortal', frameId, routeHandlerLookup, () => '*');
44280
+ startListening('VocPortal', frameId, routeHandlerLookup, undefined, // originProvider, unused: replies target the origin that asked
44281
+ undefined, // destinationProvider
44282
+ undefined, // usePingAutoReconnect
44283
+ undefined, // connectionName
44284
+ pluginAPI.isTrustedOrigin);
43924
44285
  try {
43925
44286
  observePortalContainer();
43926
44287
  }
@@ -50646,6 +51007,113 @@ class Transport {
50646
51007
  }
50647
51008
  }
50648
51009
 
51010
+ const CATCHALL_TYPES = ['load', 'click', 'track'];
51011
+ const EVENT_KINDS = {
51012
+ catchall: 'catchall',
51013
+ devlog: 'devlog',
51014
+ recording: 'recording'
51015
+ };
51016
+ class LiveReplayClient {
51017
+ constructor(pendo, api) {
51018
+ this.pendo = pendo;
51019
+ this.api = api;
51020
+ this.ws = null;
51021
+ this.sequence = 0;
51022
+ this.unsubscribeEventCaptured = null;
51023
+ }
51024
+ buildWsUrl() {
51025
+ const url = new URL$1(`${this.pendo.HOST}/data/live-replay/${this.pendo.apiKey}`);
51026
+ if (url.protocol === 'http:') {
51027
+ url.protocol = 'ws:';
51028
+ }
51029
+ else if (url.protocol === 'https:') {
51030
+ url.protocol = 'wss:';
51031
+ }
51032
+ return url.href;
51033
+ }
51034
+ start(visitorId, onOpen) {
51035
+ if (this.ws && this.ws.readyState !== WebSocket.CLOSED)
51036
+ return;
51037
+ this.sequence = 0;
51038
+ this.unsubscribeEventCaptured = this.api.attachEvent(this.api.Events, 'eventCaptured', this.pendo._.bind(this.forwardEvent, this));
51039
+ this.ws = new WebSocket(this.buildWsUrl());
51040
+ this.ws.onopen = this.pendo._.bind(this.handleOpen, this, visitorId, onOpen);
51041
+ const boundHandleClose = this.pendo._.bind(this.handleClose, this);
51042
+ this.ws.onclose = boundHandleClose;
51043
+ this.ws.onerror = boundHandleClose;
51044
+ }
51045
+ handleOpen(visitorId, onOpen) {
51046
+ this.sendAuth(visitorId);
51047
+ if (onOpen)
51048
+ onOpen();
51049
+ }
51050
+ handleClose() {
51051
+ this.unsubscribeFromEvents();
51052
+ }
51053
+ unsubscribeFromEvents() {
51054
+ if (!this.unsubscribeEventCaptured)
51055
+ return;
51056
+ this.unsubscribeEventCaptured();
51057
+ this.unsubscribeEventCaptured = null;
51058
+ }
51059
+ sendAuth(visitorId) {
51060
+ if (!this.isOpen())
51061
+ return;
51062
+ const jwtOptions = this.api.agent.getJwtInfoCopy();
51063
+ this.ws.send(JSON.stringify(this.pendo._.extend({
51064
+ type: 'auth',
51065
+ visitorId
51066
+ }, jwtOptions)));
51067
+ }
51068
+ stop() {
51069
+ if (this.ws) {
51070
+ this.ws.close();
51071
+ this.ws = null;
51072
+ }
51073
+ this.unsubscribeFromEvents();
51074
+ }
51075
+ isOpen() {
51076
+ return !!this.ws && this.ws.readyState === WebSocket.OPEN;
51077
+ }
51078
+ onLiveFlag(live, visitorId, onOpen) {
51079
+ if (live) {
51080
+ this.start(visitorId, onOpen);
51081
+ }
51082
+ else {
51083
+ this.stop();
51084
+ }
51085
+ }
51086
+ send(payload) {
51087
+ if (!this.isOpen())
51088
+ return;
51089
+ try {
51090
+ this.ws.send(this.pendo.compress(payload, 'binary'));
51091
+ }
51092
+ catch (_a) { }
51093
+ }
51094
+ sendRecordingEvent(fields) {
51095
+ this.send(Object.assign(Object.assign({}, fields), { kind: EVENT_KINDS.recording, sequence: this.sequence++ }));
51096
+ }
51097
+ forwardEvent(event) {
51098
+ if (!event || !event.data || !event.data.length)
51099
+ return;
51100
+ const capturedEvent = event.data[0];
51101
+ const config = { event: capturedEvent };
51102
+ if (this.pendo._.contains(CATCHALL_TYPES, capturedEvent.type)) {
51103
+ config.kind = EVENT_KINDS.catchall;
51104
+ }
51105
+ if (capturedEvent.type === EVENT_KINDS.devlog) {
51106
+ config.kind = EVENT_KINDS.devlog;
51107
+ }
51108
+ if (!config.kind)
51109
+ return;
51110
+ // other eventCaptured listeners still mutate capturedEvent in place; defer until they're done
51111
+ this.pendo._.defer(() => {
51112
+ this.send(config);
51113
+ });
51114
+ }
51115
+ }
51116
+
50649
51117
  const ELEMENT_NODE = 1;
50650
51118
  const INPUT_MASK = '*'.repeat(10);
50651
51119
  const NUMBER_INPUT_MASK = '0'.repeat(10);
@@ -50891,6 +51359,7 @@ class SessionRecorder {
50891
51359
  this.transport = new Transport(this.WorkerClass, this.pendo, this.api);
50892
51360
  this.transport.onError = bind(this.sendFailure, this, 'WORKER_ERROR');
50893
51361
  this.transport.onWorkerMessage = bind(this.onWorkerMessage, this);
51362
+ this.liveReplayClient = new LiveReplayClient(this.pendo, this.api);
50894
51363
  this.sendQueue = new this.api.SendQueue(bind(this.transport.send, this.transport));
50895
51364
  this.sendQueue.onTimeout = bind(this.sendFailure, this, 'SEND_TIMEOUT');
50896
51365
  this.errorHandler = this.pendo._.throttle(bind(this.logStopReason, this, 'RRWEB_ERROR'), ONE_MINUTE_IN_MILLISECONDS, { trailing: false });
@@ -50981,6 +51450,7 @@ class SessionRecorder {
50981
51450
  this.subscriptions.length = 0;
50982
51451
  this.stop();
50983
51452
  this.transport.stop();
51453
+ this.liveReplayClient.stop();
50984
51454
  }
50985
51455
  ready() {
50986
51456
  if (this.api.ConfigReader.get(RECORDING_CONFIG_AUTO_START) !== false) {
@@ -51055,6 +51525,7 @@ class SessionRecorder {
51055
51525
  this.stop();
51056
51526
  }
51057
51527
  if (visitorConfig && visitorConfig.enable && this.isRecording()) {
51528
+ this.liveReplayClient.onLiveFlag(visitorConfig.live, this.visitorId, () => this.snapshot());
51058
51529
  if (continuationCallback) {
51059
51530
  continuationCallback.call(this);
51060
51531
  }
@@ -51197,6 +51668,7 @@ class SessionRecorder {
51197
51668
  var bind = this.pendo._.bind;
51198
51669
  this.visitorConfig = visitorConfig;
51199
51670
  this.clearOldSessionId();
51671
+ this.liveReplayClient.onLiveFlag(visitorConfig.live, this.visitorId);
51200
51672
  this.sendQueue.start();
51201
51673
  this.interval = setInterval(() => {
51202
51674
  if (!this.sendQueue.failed()) {
@@ -51395,9 +51867,11 @@ class SessionRecorder {
51395
51867
  this.sessionId(this.recordingId);
51396
51868
  this.restartPtm();
51397
51869
  this._sendIds();
51870
+ this.pendo._.each(this.buffer.data, (bufferedEvent) => this._sendLiveReplayEvent(bufferedEvent));
51398
51871
  this.send({ keyframe: true });
51399
51872
  }
51400
51873
  else if (!isMeta) {
51874
+ this._sendLiveReplayEvent(event);
51401
51875
  this.eventsSinceLastKeyFrame += 1;
51402
51876
  const timeSinceLastKeyFrame = event.timestamp - this.lastKeyFrameTime;
51403
51877
  const exceeds24Hours = timeSinceLastKeyFrame >= ONE_DAY_IN_MILLISECONDS;
@@ -51421,6 +51895,23 @@ class SessionRecorder {
51421
51895
  updateCurrentRecordingSize(recordingPayloadSize) {
51422
51896
  this.currentRecordingSize += recordingPayloadSize;
51423
51897
  }
51898
+ _sendLiveReplayEvent(event) {
51899
+ if (!this.recordingId || !this.liveReplayClient.isOpen())
51900
+ return;
51901
+ this.liveReplayClient.sendRecordingEvent({
51902
+ type: 'recording',
51903
+ visitorId: this.visitorId,
51904
+ accountId: this.accountId,
51905
+ recordingId: this.recordingId,
51906
+ recordingSessionId: this.sessionId(this.recordingId),
51907
+ url: this.pendo.url.get(),
51908
+ keyFrame: event.type === EVENT_TYPES.FULL_SNAPSHOT,
51909
+ lastKeyFrame: this.lastKeyFrameTime,
51910
+ recordingPayload: [event],
51911
+ recordingPayloadCount: 1,
51912
+ browserTime: event.timestamp
51913
+ });
51914
+ }
51424
51915
  onWorkerMessage(messageData) {
51425
51916
  switch (messageData.type) {
51426
51917
  case 'workerDied': {
@@ -51455,6 +51946,13 @@ class SessionRecorder {
51455
51946
  this.logStopReason(`MISSING_${messageData.missingData}`);
51456
51947
  break;
51457
51948
  }
51949
+ case 'liveFlag': {
51950
+ // ignore stale responses from a since-changed identity
51951
+ if (messageData.visitorId !== this.visitorId)
51952
+ break;
51953
+ this.liveReplayClient.onLiveFlag(messageData.live, this.visitorId, () => this.snapshot());
51954
+ break;
51955
+ }
51458
51956
  }
51459
51957
  }
51460
51958
  addRecordingId(event) {
@@ -51493,6 +51991,7 @@ class SessionRecorder {
51493
51991
  }
51494
51992
  // send existing events
51495
51993
  this.send();
51994
+ this.liveReplayClient.stop();
51496
51995
  // now, finish stopping any more collection or sending
51497
51996
  this.sendQueue.stop();
51498
51997
  this.recordingId = null;
@@ -51562,7 +52061,8 @@ class SessionRecorder {
51562
52061
  this.api.Events.eventCaptured.trigger(payload);
51563
52062
  const params = {
51564
52063
  v: this.pendo.VERSION,
51565
- recordingId: this.recordingId
52064
+ recordingId: this.recordingId,
52065
+ vis: this.api.util.base64EncodeString(this.visitorId)
51566
52066
  };
51567
52067
  if (payload.keyFrame) {
51568
52068
  params.kf = 1;
@@ -51955,19 +52455,27 @@ var WorkerFactory = /*#__PURE__*/createInlineWorkerFactory(/* rollup-plugin-web-
51955
52455
  method: 'POST',
51956
52456
  body
51957
52457
  }).then(function (response) {
51958
- cancelResponseBody(response);
51959
52458
  if (response.status < 200 || response.status >= 300) {
52459
+ cancelResponseBody(response);
51960
52460
  postMessage({
51961
52461
  error: new Error(`received status code ${response.status}: ${response.statusText}`),
51962
52462
  status: response.status,
51963
52463
  sequence
51964
52464
  });
52465
+ return;
51965
52466
  }
51966
- else {
51967
- postMessage({
51968
- sequence
51969
- });
51970
- }
52467
+ postMessage({
52468
+ sequence
52469
+ });
52470
+ response.json().then(function (responseBody) {
52471
+ if (responseBody && responseBody.live !== undefined) {
52472
+ postMessage({
52473
+ type: 'liveFlag',
52474
+ live: responseBody.live,
52475
+ visitorId: payload.visitorId
52476
+ });
52477
+ }
52478
+ }).catch(function () { });
51971
52479
  }).catch(function (e) {
51972
52480
  postMessage({
51973
52481
  error: e,