@pendo/agent 2.338.0 → 2.339.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -657,10 +657,13 @@ function getPendoConfigValue(key) {
657
657
  }
658
658
 
659
659
  // Resolve the global object without relying on a bare `window` identifier.
660
- // `window` is only a bound global in a normal browser realm; when the agent is
661
- // loaded via the npm `loadAsModule` path (or inside a Web Worker) the module is
662
- // evaluated in a realm where `window` is undeclared, so referencing it bare
663
- // throws a ReferenceError at module-load. `globalThis` resolves in every realm.
660
+ // `window` is only a bound global in a normal browser realm; the compat builds
661
+ // bind it via their `(function(window, document){...})` wrapper, but the npm
662
+ // module build has no wrapper, so its module scope can be evaluated in realms
663
+ // where `window` is undeclared (a Web Worker, or a customer bundler re-chunking
664
+ // the module into worker-reachable code) and a bare reference throws a
665
+ // ReferenceError at module-load. `globalThis` resolves in every realm; the
666
+ // `self`/`window` fallbacks cover engines that predate it. APP-160669, APP-165809.
664
667
  function getGlobalScope() {
665
668
  if (typeof globalThis !== 'undefined') {
666
669
  return globalThis;
@@ -4043,8 +4046,8 @@ let SERVER = '';
4043
4046
  let ASSET_HOST = '';
4044
4047
  let ASSET_PATH = '';
4045
4048
  let DESIGNER_SERVER = '';
4046
- let VERSION = '2.338.0_';
4047
- let PACKAGE_VERSION = '2.338.0';
4049
+ let VERSION = '2.339.0_';
4050
+ let PACKAGE_VERSION = '2.339.0';
4048
4051
  let LOADER = 'xhr';
4049
4052
  /* eslint-enable web-sdk-eslint-rules/no-gulp-env-references */
4050
4053
  /**
@@ -4349,9 +4352,9 @@ function unwrapFirefoxObject(obj) {
4349
4352
  return obj;
4350
4353
  }
4351
4354
 
4352
- var decodeURIComponent = _.isFunction(window.decodeURIComponent) ? window.decodeURIComponent : _.identity;
4355
+ var decodeURIComponent = _.isFunction(globalScope.decodeURIComponent) ? globalScope.decodeURIComponent : _.identity;
4353
4356
 
4354
- var encodeURIComponent = _.isFunction(window.encodeURIComponent) ? window.encodeURIComponent : _.identity;
4357
+ var encodeURIComponent = _.isFunction(globalScope.encodeURIComponent) ? globalScope.encodeURIComponent : _.identity;
4355
4358
 
4356
4359
  class EventTarget {
4357
4360
  constructor() {
@@ -4525,7 +4528,7 @@ function createStorageShim(storageAccessor) {
4525
4528
  return fake;
4526
4529
  }
4527
4530
  }
4528
- var pendoLocalStorage$4 = createStorageShim(function () { return window.localStorage; });
4531
+ var pendoLocalStorage$3 = createStorageShim(function () { return window.localStorage; });
4529
4532
  var pendoSessionStorage$2 = createStorageShim(function () { return window.sessionStorage; });
4530
4533
 
4531
4534
  /**
@@ -4913,7 +4916,7 @@ var agentStorage = (function () {
4913
4916
  }
4914
4917
  const wrappedPendoSessionStorage = _.extend({}, pendoSessionStorage$2);
4915
4918
  wrappedPendoSessionStorage.setItem = wrapStorageWriteMethod(wrappedPendoSessionStorage.setItem, 'hasSession');
4916
- const wrappedPendoLocalStorage = _.extend({}, pendoLocalStorage$4);
4919
+ const wrappedPendoLocalStorage = _.extend({}, pendoLocalStorage$3);
4917
4920
  wrappedPendoLocalStorage.setItem = wrapStorageWriteMethod(wrappedPendoLocalStorage.setItem, 'hasLocal');
4918
4921
  return {
4919
4922
  /* API */
@@ -4927,18 +4930,6 @@ var agentStorage = (function () {
4927
4930
  };
4928
4931
  })();
4929
4932
 
4930
- /*
4931
- * @license MIT - https://github.com/taylorhakes/promise-polyfill
4932
- */
4933
- // Modified to not affect global environment: the `var` keeps the binding module-local
4934
- // so the polyfill never assigns to the real global `Promise`.
4935
- //
4936
- // Main-thread only. Reading `window` here is safe because rollup's `inject` is skipped
4937
- // for the nested worker build (see `skipPlugins` on webWorkerLoader in
4938
- // rollup/web-sdk.config.mjs): a worker realm has a native Promise and no `window`, so
4939
- // injecting this polyfill there was both unreachable and fatal — it threw
4940
- // `ReferenceError: window is not defined` at worker startup and stopped session replay
4941
- // with a WORKER_ERROR. APP-163474.
4942
4933
  var Promise$1;
4943
4934
 
4944
4935
  Promise$1 = (function (global, factory) {
@@ -4948,7 +4939,7 @@ Promise$1 = (function (global, factory) {
4948
4939
  }
4949
4940
 
4950
4941
  return nativePromise;
4951
- }(window, (function () {
4942
+ }(globalScope, (function () {
4952
4943
  /**
4953
4944
  * @this {Promise}
4954
4945
  */
@@ -6277,6 +6268,7 @@ function wasSessionCleared(evt) {
6277
6268
  //
6278
6269
  // NOTE: This, for now, at least is identified as an instanceof HTMLAnchorElement
6279
6270
 
6271
+
6280
6272
  function URLPolyfillFactory(doc) {
6281
6273
  return function URL(str) {
6282
6274
  var a = doc.createElement('a');
@@ -6308,11 +6300,11 @@ function URLPolyfillFactory(doc) {
6308
6300
  var URL = (function(global, factory) {
6309
6301
  var nativeURL = global.URL;
6310
6302
  if (typeof nativeURL !== 'function' || !/native/.test(nativeURL)) {
6311
- return factory(document);
6303
+ return factory(global.document);
6312
6304
  }
6313
6305
 
6314
6306
  return nativeURL;
6315
- })(window, URLPolyfillFactory);
6307
+ })(globalScope, URLPolyfillFactory);
6316
6308
 
6317
6309
  function hasComposedPath(evt) {
6318
6310
  return evt && typeof evt.composedPath === 'function';
@@ -11658,7 +11650,7 @@ var WeakRef = (function(global, factory) {
11658
11650
  }
11659
11651
 
11660
11652
  return nativeWeakRef;
11661
- })(window, WeakRefFactory);
11653
+ })(globalScope, WeakRefFactory);
11662
11654
 
11663
11655
  const trimString = (str, limit) => {
11664
11656
  const len = str.length;
@@ -20556,6 +20548,23 @@ var GuideStateModule = (function () {
20556
20548
  };
20557
20549
  })();
20558
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
+
20559
20568
  var AutoDisplay = (function () {
20560
20569
  return {
20561
20570
  canDisplay,
@@ -20639,24 +20648,6 @@ var AutoDisplay = (function () {
20639
20648
  var prioritizeAdopt = ConfigReader.get('adoptPrioritizeAdoptGuides');
20640
20649
  return prioritizeAdoptOrCoreGuides(sortedGuides, prioritizeAdopt);
20641
20650
  }
20642
- function prioritizeAdoptOrCoreGuides(guides, shouldPrioritizeAdopt) {
20643
- var adoptGuides = [];
20644
- var coreGuides = [];
20645
- _.each(guides, function (guide) {
20646
- if (guide.isTraining) {
20647
- adoptGuides.push(guide);
20648
- }
20649
- else {
20650
- coreGuides.push(guide);
20651
- }
20652
- });
20653
- if (shouldPrioritizeAdopt) {
20654
- return adoptGuides.concat(coreGuides);
20655
- }
20656
- else {
20657
- return coreGuides.concat(adoptGuides);
20658
- }
20659
- }
20660
20651
  function lastDismissedTime(pendo) {
20661
20652
  return Math.max(pendo.latestDismissedAutoAt || -Infinity, pendo.finalAdvancedAutoAt || -Infinity, pendo.latestSnoozedAutoAt || -Infinity);
20662
20653
  }
@@ -20866,642 +20857,6 @@ class AutoDisplayPhase {
20866
20857
  }
20867
20858
  }
20868
20859
 
20869
- /*
20870
- * Guide Loop
20871
- *
20872
- * The function that gets called to process and handle the ever
20873
- * changing landscape of a web app to best handle showing / hiding of
20874
- * guides as needed.
20875
- */
20876
- /*
20877
- * Holds the timeout handle for the startGuides process.
20878
- * This should work as a simple monitor on that process
20879
- * to prevent it from being run too often.
20880
- */
20881
- var guidesProcessingThreadHandle = null;
20882
- var DEFAULT_TIMER_LENGTH = 500;
20883
- var waitThenLoop = function (delayAmount) {
20884
- delayAmount = delayAmount || DEFAULT_TIMER_LENGTH;
20885
- guidesProcessingThreadHandle = _.delay(function () {
20886
- guidesProcessingThreadHandle = null;
20887
- startGuides();
20888
- }, delayAmount);
20889
- pendo$1.guidesProcessingThreadHandle = guidesProcessingThreadHandle;
20890
- };
20891
- var clearLoopTimer = function () {
20892
- if (guidesProcessingThreadHandle) {
20893
- clearTimeout(guidesProcessingThreadHandle);
20894
- guidesProcessingThreadHandle = null;
20895
- pendo$1.guidesProcessingThreadHandle = guidesProcessingThreadHandle;
20896
- }
20897
- store.dispatch('guideUpdate/stopScheduledUpdate');
20898
- };
20899
- /**
20900
- * Clears any showing guides and prevents any loaded guides from rendering.
20901
- * Loaded guides remain in memory, so calling startGuides will work just fine
20902
- * after a stopGuides call.
20903
- *
20904
- * @access public
20905
- * @category Guides
20906
- * @example
20907
- * pendo.stopGuides()
20908
- */
20909
- var stopGuides = function () {
20910
- hideGuides();
20911
- updateRunner.cancel();
20912
- // Remove other stuff.
20913
- for (var i = 0; i < activeElements.length; i++) {
20914
- var elem = activeElements[i];
20915
- elem.parentNode.removeChild(elem);
20916
- }
20917
- activeElements.length = 0;
20918
- store.dispatch('guideUpdate/stopObserver');
20919
- Events.guideLoopStopped.trigger();
20920
- };
20921
- /**
20922
- * Starts the process that loops over all currently loaded guides and
20923
- * determines what to show. Checks multi-step guide continuation,
20924
- * auto-display guides, launcher guides, and badges.
20925
- *
20926
- * @access public
20927
- * @category Guides
20928
- * @example
20929
- * pendo.startGuides()
20930
- */
20931
- var startGuides = function () {
20932
- clearLoopTimer();
20933
- if (isGuideRequestPending()) {
20934
- log.info('guides are loading.', { contexts: ['guides', 'loading'] });
20935
- return;
20936
- }
20937
- if (areGuidesDisabled()) {
20938
- log.info('guides are disabled.', { contexts: ['guides', 'disabled'] });
20939
- return;
20940
- }
20941
- if (areGuidesDelayed()) {
20942
- log.info('guides are delayed.', { contexts: ['guides', 'delayed'] });
20943
- return;
20944
- }
20945
- if (store.getters['guideState/tabLostFocus']())
20946
- return;
20947
- try {
20948
- const activeGuides = getActiveGuides();
20949
- if (!activeGuides || activeGuides.length === 0)
20950
- return;
20951
- store.dispatch('guideUpdate/startObserverIfNeeded');
20952
- if (updateRunner.isComplete()) {
20953
- updateRunner.markDirty();
20954
- }
20955
- if (leaderExists()) {
20956
- updateRunner.process();
20957
- }
20958
- }
20959
- catch (e) {
20960
- updateRunner.cancel();
20961
- log.critical('ERROR in guide-loop', { error: e });
20962
- }
20963
- finally {
20964
- if (!updateRunner.paused) {
20965
- continueOrCompleteUpdate();
20966
- }
20967
- }
20968
- };
20969
- function continueOrCompleteUpdate() {
20970
- if (!updateRunner.isComplete()) {
20971
- waitThenLoop(50);
20972
- }
20973
- else {
20974
- if (!store.state.guideUpdate.useObserver || store.state.guideUpdate.needsUpdate) {
20975
- waitThenLoop();
20976
- }
20977
- Events.guideUpdateComplete.trigger();
20978
- }
20979
- }
20980
- /*
20981
- * If delayGuides is true, removes the delay and starts guides.
20982
- * Otherwise, it is the same as calling startGuides.
20983
- */
20984
- var manuallyStartGuides = function () {
20985
- setGuidesDelayed(false);
20986
- startGuides();
20987
- };
20988
- var manuallyStopGuides = function () {
20989
- if (!areGuidesDisabled()) {
20990
- setGuidesDelayed(true);
20991
- }
20992
- resetPendoUI();
20993
- };
20994
- // a phase that only has one unit of work per update
20995
- class SimplePhase {
20996
- constructor(name, processFn) {
20997
- this.dirty = false;
20998
- this.name = name;
20999
- this.processFn = _.isFunction(processFn) ? processFn : _.noop;
21000
- }
21001
- markDirty() {
21002
- this.dirty = true;
21003
- }
21004
- process() {
21005
- this.processFn();
21006
- this.dirty = false;
21007
- }
21008
- cancel() {
21009
- this.dirty = false;
21010
- }
21011
- isComplete() {
21012
- return !this.dirty;
21013
- }
21014
- }
21015
- /**
21016
- * Runs on a timer whenever a guide is displayed.
21017
- *
21018
- * @access private
21019
- * @param {Guide} guide The currently visible guide
21020
- * @param {GuideStep} step The currently visible step
21021
- */
21022
- function guideShowingProc() {
21023
- const shownGuides = _.compact(_.map(_.filter(frameLocalGuides(getActiveGuides()), (guide) => guide.isShown()), (guide) => {
21024
- if (BuildingBlockResourceCenter.isModule(guide)) {
21025
- return BuildingBlockResourceCenter.getResourceCenter();
21026
- }
21027
- else {
21028
- return guide;
21029
- }
21030
- }));
21031
- _.each(shownGuides, function (guide) {
21032
- _.each(guide.steps, function (step) {
21033
- if (step.isShown()) {
21034
- stepShowingProc(guide, step);
21035
- }
21036
- });
21037
- guide.checkForHiddenGroupSteps();
21038
- });
21039
- }
21040
- function stepShowingProc(guide, step) {
21041
- if (step.isLocked()) {
21042
- return;
21043
- }
21044
- if (store.getters['frames/isShownInAnotherFrame']()(step)) {
21045
- return;
21046
- }
21047
- var element = step.element;
21048
- var targetElement = step.targetElement;
21049
- var guideElement = step.guideElement;
21050
- var guideElm = dom('.' + GUIDE_CSS_NAME);
21051
- // As part of the guides accessibility work, we need to make sure that all
21052
- // guide content remain visible and/or accessible during text-only (font) zoom.
21053
- // Since text zoom does NOT trigger our existing resize listener, the
21054
- // alternative is to track text zoom via font-size changes.
21055
- var guideContainer = guideElement.find('#pendo-guide-container')[0];
21056
- var guideHasTextContent = guideContainer && guideContainer.textContent;
21057
- if (guideHasTextContent) {
21058
- var currentBrowserFontSize = getComputedStyle_safe(guideContainer).fontSize;
21059
- var hasBrowserFontSizeChanged = step.attributes.currentTextZoomFontSize !== currentBrowserFontSize;
21060
- if (hasBrowserFontSizeChanged) {
21061
- if (step.attributes.currentTextZoomFontSize) {
21062
- step.redraw();
21063
- }
21064
- step.attributes.currentTextZoomFontSize = currentBrowserFontSize;
21065
- }
21066
- }
21067
- syncColorMode(step, true);
21068
- if (step.elementPathRule && targetElement && !SizzleProxy.matchesSelector(targetElement, step.elementPathRule)) {
21069
- step.hide();
21070
- return;
21071
- }
21072
- const embedSelector = _.get(guide, 'attributes.embedConfig.selector');
21073
- if (embedSelector && !dom(embedSelector).length) {
21074
- step.hide();
21075
- return;
21076
- }
21077
- var elementIsStillValidTarget = isElementVisible(element);
21078
- var elementMatchesContainsRules = true;
21079
- // targetElement is specifically the elementPathRule dom node
21080
- // element is either the element path rule OR the badge element
21081
- // we can rely on the isElementVisible check as a shortcut for having to call isElementVisible twice
21082
- if (targetElement && elementIsStillValidTarget && step.elementContainsRulesList) {
21083
- // Only bother trying to check element contains rules if the element is still visible
21084
- elementMatchesContainsRules = doesElementMatchContainsRules(targetElement, step.elementContainsRulesList);
21085
- elementIsStillValidTarget = elementMatchesContainsRules;
21086
- }
21087
- if (element && (elementIsStillValidTarget || guideElm.hasClass('mouseover'))) {
21088
- step.checkPlacement();
21089
- return;
21090
- }
21091
- var isTooltip = (step.type === 'tooltip' || step.attributes.calculatedType === 'tooltip');
21092
- if (isTooltip && wouldBeVisibleAfterAutoScroll(element) && elementMatchesContainsRules) {
21093
- // the element was scrolled out of view, the guide should be invisible for now, but still "active"
21094
- return;
21095
- }
21096
- step.hide();
21097
- }
21098
- class BadgePhase {
21099
- constructor() {
21100
- this.name = 'badge';
21101
- this.queue = [];
21102
- }
21103
- markDirty() {
21104
- this.queue.push.apply(this.queue, _.filter(frameLocalGuides(getActiveGuides()), isBadge));
21105
- }
21106
- process(monitor) {
21107
- var badge = this.queue.shift();
21108
- monitor.saveContext(badge);
21109
- if (badge) {
21110
- badge.placeBadge();
21111
- }
21112
- }
21113
- handleProcessTimeExceeded(monitor) {
21114
- const badge = monitor.getContext();
21115
- log.warn('Badge (' + badge.id + ') timeout exceeded');
21116
- }
21117
- cancel() {
21118
- this.queue.length = 0;
21119
- }
21120
- isComplete() {
21121
- return !this.queue.length;
21122
- }
21123
- }
21124
- function resourceCenterShowingProc(resourceCenter, resourceCenterBadgeProc) {
21125
- var reEvaluateModuleContent = false;
21126
- // Find eligible modules
21127
- var controlGuidesInAllModules = [];
21128
- var eligibleModules = _.filter(resourceCenter.modules, function (module) {
21129
- if (!module)
21130
- return false;
21131
- // Iframe modules can not dynamically show/hide.
21132
- if (BuildingBlockResourceCenter.isIframeModule(module))
21133
- return true;
21134
- // Sandbox modules can not dynamically show/hide.
21135
- if (BuildingBlockResourceCenter.isSandboxModule(module))
21136
- return true;
21137
- if (BuildingBlockResourceCenter.isIntegrationModule(module)) {
21138
- if (!BuildingBlockResourceCenter.integrationModuleHasContent(module)) {
21139
- module.hasResourceCenterContent = false;
21140
- return false;
21141
- }
21142
- else {
21143
- return true;
21144
- }
21145
- }
21146
- var eligibleGuidesInModule = _.filter(module.guidesInModule, function (guide) {
21147
- if (!guide.shouldBeAddedToResourceCenter()) {
21148
- guide.ineligibleForRC = true;
21149
- if (guide.control) {
21150
- controlGuidesInAllModules.push(guide);
21151
- }
21152
- return false;
21153
- }
21154
- guide.ineligibleForRC = false;
21155
- return true;
21156
- });
21157
- var isEligible = eligibleGuidesInModule.length;
21158
- module.hasResourceCenterContent = !!isEligible;
21159
- // We only need to hash the active module, no other module has content rendered
21160
- if (resourceCenter.activeModule && module.id === resourceCenter.activeModule.id) {
21161
- var moduleContentHash = crc32(_.map(eligibleGuidesInModule, function (guide) {
21162
- // don't change content hash when announcements update their seen state, as that will cause
21163
- // the RC to re-render (and lose scroll position)
21164
- var isAnnouncementOrWhatsNew = guide.isAnnouncement() || guide.isWhatsNew();
21165
- var seenState = isAnnouncementOrWhatsNew ? null : _.pluck(guide.steps, 'seenState');
21166
- return {
21167
- id: guide.id,
21168
- seenState
21169
- };
21170
- }));
21171
- if (module.eligibleGuidesInModuleHash && moduleContentHash !== module.eligibleGuidesInModuleHash)
21172
- reEvaluateModuleContent = true;
21173
- module.eligibleGuidesInModuleHash = moduleContentHash;
21174
- }
21175
- return isEligible;
21176
- });
21177
- // If we are rendering the home view and its modules change re-render it
21178
- var reEvaluateHomeViewContent = false;
21179
- if (!resourceCenter.activeModule) {
21180
- var eligibleModulesHash = crc32(_.map(eligibleModules, function (module) {
21181
- return {
21182
- id: module.id
21183
- };
21184
- }));
21185
- if (resourceCenter.eligibleModulesHash && resourceCenter.eligibleModulesHash !== eligibleModulesHash) {
21186
- reEvaluateHomeViewContent = true;
21187
- BuildingBlockResourceCenter.replaceResourceCenterContent(resourceCenter.id, undefined, true);
21188
- }
21189
- resourceCenter.eligibleModulesHash = eligibleModulesHash;
21190
- }
21191
- resourceCenterBadgeProc(resourceCenter, eligibleModules, controlGuidesInAllModules);
21192
- // Skip directly to the one eligible module
21193
- if (eligibleModules.length === 1) {
21194
- resourceCenter.skipResourceCenterHomeView = true;
21195
- resourceCenter.moduleIdToReplaceHomeViewWith = eligibleModules[0].id;
21196
- var noActiveModule = !resourceCenter.activeModule && reEvaluateHomeViewContent;
21197
- var differentActiveModule = resourceCenter.activeModule && resourceCenter.activeModule.id !== eligibleModules[0].id;
21198
- // If the one module is different or content needs to be reevaluated, reload
21199
- // or if we are on the home view and the modules on the home view have changed
21200
- if (noActiveModule || differentActiveModule || reEvaluateModuleContent) {
21201
- BuildingBlockResourceCenter.replaceResourceCenterContent(resourceCenter.moduleIdToReplaceHomeViewWith, undefined, true);
21202
- }
21203
- }
21204
- if (eligibleModules) {
21205
- // find the in-progress native integration module and set continueToNativeModule on both the
21206
- // RC container and the module since both will try to render
21207
- var continuedModule = _.find(eligibleModules, function (module) {
21208
- return module.isContinuation(getLastGuideStepSeen());
21209
- });
21210
- var shouldNotResume = _.get(continuedModule, 'attributes.doNotResume');
21211
- var integrationProvider = _.get(continuedModule, 'attributes.resourceCenter.integrationProvider');
21212
- var isNativeModule = BuildingBlockResourceCenter.isSupportedNativeIntegration(integrationProvider);
21213
- if (isNativeModule && !shouldNotResume && resourceCenter) {
21214
- resourceCenter.continueToNativeModule = true;
21215
- continuedModule.continueToNativeModule = true;
21216
- }
21217
- }
21218
- // Ensure the active module is still eligible and re-evaluate its content if need be.
21219
- // Go to Home View if active module is no longer eligible
21220
- var activeModuleisEligible;
21221
- if (resourceCenter.activeModule) {
21222
- activeModuleisEligible = _.find(eligibleModules, function (module) {
21223
- return module.id === resourceCenter.activeModule.id;
21224
- });
21225
- if (activeModuleisEligible && reEvaluateModuleContent) {
21226
- BuildingBlockResourceCenter.replaceResourceCenterContent(resourceCenter.activeModule.id, undefined, true);
21227
- }
21228
- if (!activeModuleisEligible) {
21229
- BuildingBlockResourceCenter.replaceResourceCenterContent(resourceCenter.id);
21230
- }
21231
- }
21232
- BuildingBlockResourceCenter.repositionResourceCenter();
21233
- }
21234
- function resourceCenterNotShowingProc(resourceCenter, resourceCenterBadgeProc) {
21235
- var eligibleModules = [];
21236
- var controlGuidesInAllModules = [];
21237
- var staticModule = _.find(resourceCenter.modules, function (module) {
21238
- if (BuildingBlockResourceCenter.isIframeModule(module))
21239
- return true;
21240
- if (BuildingBlockResourceCenter.isSandboxModule(module))
21241
- return true;
21242
- if (BuildingBlockResourceCenter.isIntegrationModule(module)) {
21243
- if (!BuildingBlockResourceCenter.integrationModuleHasContent(module)) {
21244
- module.hasResourceCenterContent = false;
21245
- return false;
21246
- }
21247
- else {
21248
- return true;
21249
- }
21250
- }
21251
- return false;
21252
- });
21253
- if (staticModule) {
21254
- eligibleModules.push(staticModule);
21255
- }
21256
- var allGuidesInModules = _.flatten(_.pluck(resourceCenter.modules, 'guidesInModule'));
21257
- controlGuidesInAllModules = _.filter(_.compact(allGuidesInModules), function (guide) {
21258
- return guide.control;
21259
- });
21260
- if (!eligibleModules.length) {
21261
- var moduleLookup = _.reduce(resourceCenter.modules, function (lookup, module) {
21262
- _.each(module.guidesInModule, function (guide) {
21263
- lookup[guide.id] = module;
21264
- });
21265
- return lookup;
21266
- }, {});
21267
- var guidesWithSteps = _.filter(allGuidesInModules, function (guide) {
21268
- return guide && guide.steps && guide.steps.length;
21269
- });
21270
- // Sort guides so that untargeted guides are first
21271
- var sortedGuides = _.sortBy(guidesWithSteps, function (guide) {
21272
- return guide.steps[0].elementPathRule ? 1 : 0;
21273
- });
21274
- var firstEligibleGuide = _.find(sortedGuides, function (guide) {
21275
- return guide.shouldBeAddedToResourceCenter();
21276
- });
21277
- var firstEligibleModule = moduleLookup[firstEligibleGuide && firstEligibleGuide.id];
21278
- if (firstEligibleModule) {
21279
- eligibleModules.push(firstEligibleModule);
21280
- }
21281
- }
21282
- resourceCenterBadgeProc(resourceCenter, eligibleModules, controlGuidesInAllModules);
21283
- }
21284
- function resourceCenterBadgeProc(resourceCenter, eligibleModules, controlGuidesInAllModules) {
21285
- var RCBadge = badgesShown[resourceCenter.id];
21286
- // If we get no modules back, hide the RC and RC Badge, clear active module
21287
- if (!eligibleModules || !eligibleModules.length) {
21288
- var isBadgeActivated = resourceCenter.launchMethod.indexOf('badge') !== -1;
21289
- var isElementActivated = resourceCenter.launchMethod.indexOf('dom') !== -1;
21290
- resourceCenter.hasResourceCenterContent = false;
21291
- // If the RC is badge activated, hide it from the dom completely. Otherwise, leave it alone
21292
- // This is important for element-activated RC, which should still display with an empty state
21293
- // even if it has no content
21294
- if (isBadgeActivated) {
21295
- if (resourceCenter.isShown())
21296
- resourceCenter.hide();
21297
- if (RCBadge) {
21298
- BuildingBlockResourceCenter.hideRCBadge(RCBadge, resourceCenter);
21299
- }
21300
- // If all modules are not eligible because of control group guides
21301
- // Send guideNotSeen events where appropriate
21302
- _.each(controlGuidesInAllModules, function (guide) {
21303
- if (!guide.hasGuideBeenControlled()) {
21304
- guide.steps[0].onControlGuide('control');
21305
- }
21306
- });
21307
- resourceCenter.badgeHidden = true;
21308
- delete resourceCenter.activeModule;
21309
- }
21310
- else if (isElementActivated) {
21311
- resourceCenter.showEmptyState = true;
21312
- resourceCenter.controlGuidesInAllModules = controlGuidesInAllModules;
21313
- }
21314
- }
21315
- // Show the badge if it was hidden but now has content
21316
- if (resourceCenter.badgeHidden && resourceCenter.hasResourceCenterContent && !resourceCenter.isNativeModuleActive) {
21317
- if (!RCBadge) {
21318
- if (_.isFunction(resourceCenter.placeBadge))
21319
- resourceCenter.placeBadge();
21320
- RCBadge = badgesShown[resourceCenter.id];
21321
- }
21322
- if (RCBadge)
21323
- RCBadge.show();
21324
- resourceCenter.badgeHidden = false;
21325
- }
21326
- }
21327
- function resourceCenterProc(resourceCenter) {
21328
- if (!resourceCenter || !resourceCenter.launchMethod)
21329
- return;
21330
- // Reset state to start each loop
21331
- resourceCenter.skipResourceCenterHomeView = false;
21332
- resourceCenter.hasResourceCenterContent = true;
21333
- delete resourceCenter.moduleIdToReplaceHomeViewWith;
21334
- var activeModule = resourceCenter.activeModule;
21335
- var activeIntegrationProvider = _.get(activeModule, 'attributes.resourceCenter.integrationProvider');
21336
- resourceCenter.isNativeModuleActive = activeModule && BuildingBlockResourceCenter.isSupportedNativeIntegration(activeIntegrationProvider);
21337
- if (resourceCenter.isShown() || !store.getters['frames/isLeader']()) {
21338
- resourceCenterShowingProc(resourceCenter, resourceCenterBadgeProc);
21339
- }
21340
- else {
21341
- resourceCenterNotShowingProc(resourceCenter, resourceCenterBadgeProc);
21342
- }
21343
- // Hide the badge if we're displaying a native integration module
21344
- if (resourceCenter.isNativeModuleActive) {
21345
- if (resourceCenter.isShown())
21346
- resourceCenter.hide();
21347
- var RCBadge = badgesShown[resourceCenter.id];
21348
- if (RCBadge) {
21349
- BuildingBlockResourceCenter.hideRCBadge(RCBadge, resourceCenter);
21350
- }
21351
- }
21352
- // Place Element Activated Notification Badge
21353
- if (resourceCenter.launchMethod === 'dom' && resourceCenter.canShowOnDevice()) {
21354
- BuildingBlockResourceCenter.updateNotificationBubbles();
21355
- BuildingBlockResourceCenter.adjustNotificationBubbleForResize();
21356
- }
21357
- }
21358
- class PhaseProcessTimeMonitor {
21359
- constructor(timeLimit = 50) {
21360
- this.startTime = -1;
21361
- this.timeLimit = timeLimit;
21362
- this.context = null;
21363
- }
21364
- start() {
21365
- this.context = null;
21366
- this.startTime = getNow();
21367
- }
21368
- isTimeExceeded() {
21369
- const currTime = getNow();
21370
- return (currTime - this.startTime) > this.timeLimit;
21371
- }
21372
- saveContext(context) {
21373
- this.context = context;
21374
- }
21375
- getContext() {
21376
- return this.context;
21377
- }
21378
- }
21379
- class UpdateRunner {
21380
- constructor(phases) {
21381
- this.phases = phases;
21382
- this.queue = [];
21383
- this.paused = false;
21384
- }
21385
- markDirty() {
21386
- for (let phase of this.phases) {
21387
- phase.markDirty();
21388
- }
21389
- this.queue = this.phases.slice();
21390
- }
21391
- runGuidePhase() {
21392
- const currentPhase = this.queue[0];
21393
- var promise;
21394
- const monitor = new PhaseProcessTimeMonitor(50);
21395
- while (!this.timedOut() && !currentPhase.isComplete() && !promise) {
21396
- monitor.start();
21397
- promise = currentPhase.process(monitor);
21398
- if (!promise && monitor.isTimeExceeded()) {
21399
- performanceMonitor.count(GUIDE_LOOP_TIMEOUT);
21400
- if (currentPhase.handleProcessTimeExceeded) {
21401
- currentPhase.handleProcessTimeExceeded(monitor);
21402
- }
21403
- this.ejectGuide(monitor.getContext());
21404
- }
21405
- }
21406
- if (isPromise(promise)) {
21407
- var id = this.updateId;
21408
- var resume = _.bind(function () {
21409
- if (this.updateId === id) {
21410
- this.paused = false;
21411
- startGuides();
21412
- }
21413
- }, this);
21414
- this.paused = true;
21415
- promise.then(resume).catch(resume);
21416
- }
21417
- else if (currentPhase.isComplete()) {
21418
- this.queue.shift();
21419
- }
21420
- }
21421
- addPhase(phaseFn, phaseName) {
21422
- phaseName = phaseName || phaseFn.name || this.phases.length;
21423
- if (_.isFunction(phaseFn)) {
21424
- phaseFn = new SimplePhase(phaseName, phaseFn);
21425
- }
21426
- this.phases.push(phaseFn);
21427
- }
21428
- removePhase(phaseName) {
21429
- var i = _.findIndex(this.phases, function (phase) {
21430
- return phase.name === phaseName;
21431
- });
21432
- if (i >= 0) {
21433
- this.phases.splice(i, 1);
21434
- }
21435
- }
21436
- timedOut() {
21437
- if (!this.iterationStartTime)
21438
- return false;
21439
- var elapsed = getNow() - this.iterationStartTime;
21440
- return elapsed > store.state.guideUpdate.iterationTimeLimit;
21441
- }
21442
- ejectGuide(guide) {
21443
- if (this.ejectOnTimeout === undefined) {
21444
- this.ejectOnTimeout = ConfigReader.get('guides.ejectOnTimeout');
21445
- }
21446
- if (this.ejectOnTimeout && guide && !guide.isShown()) {
21447
- Tombstone.addGuide(guide);
21448
- }
21449
- }
21450
- process() {
21451
- var now = getNow();
21452
- if (!this.updateId) {
21453
- this.updateId = now;
21454
- this.updateTotalTime = 0;
21455
- store.commit('guideUpdate/startUpdate', now);
21456
- }
21457
- this.iterationStartTime = now;
21458
- while (!this.paused &&
21459
- this.queue.length &&
21460
- !this.timedOut()) {
21461
- this.runGuidePhase();
21462
- }
21463
- now = getNow();
21464
- this.updateTotalTime += now - this.iterationStartTime;
21465
- if (this.isComplete()) {
21466
- this.complete(now);
21467
- }
21468
- }
21469
- complete(time) {
21470
- store.commit('guideUpdate/completeUpdate', time);
21471
- this.cancel();
21472
- }
21473
- cancel() {
21474
- this.updateId = null;
21475
- this.paused = false;
21476
- this.queue.length = 0;
21477
- for (let phase of this.phases) {
21478
- if (_.isFunction(phase.cancel)) {
21479
- phase.cancel();
21480
- }
21481
- }
21482
- }
21483
- isComplete() {
21484
- return !this.queue.length;
21485
- }
21486
- }
21487
- var updateRunner = new UpdateRunner([
21488
- new SimplePhase('guideShowing', guideShowingProc),
21489
- new AutoDisplayPhase(),
21490
- new BadgePhase(),
21491
- new SimplePhase('resourceCenter', function () {
21492
- resourceCenterProc(BuildingBlockResourceCenter.getResourceCenter());
21493
- }),
21494
- new SimplePhase('frameVisibility', function () {
21495
- store.dispatch('frames/checkFrameVisibility');
21496
- })
21497
- ]);
21498
- function addUpdatePhase() {
21499
- updateRunner.addPhase(...arguments);
21500
- }
21501
- function removeUpdatePhase() {
21502
- updateRunner.removePhase(...arguments);
21503
- }
21504
-
21505
20860
  function Wrappable() {
21506
20861
  var wrappers = {};
21507
20862
  var wrap = function (wrappedMethod, before, after) {
@@ -22699,118 +22054,809 @@ function ShowOnHover() {
22699
22054
  mouseout();
22700
22055
  }
22701
22056
  });
22702
- badge.after('hide', function () {
22703
- if (attached) {
22704
- detachEvent(target, 'mouseover', mouseover);
22705
- attached = false;
22706
- }
22707
- mouseout();
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;
22606
+ }
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();
22708
22633
  });
22634
+ var firstEligibleModule = moduleLookup[firstEligibleGuide && firstEligibleGuide.id];
22635
+ if (firstEligibleModule) {
22636
+ eligibleModules.push(firstEligibleModule);
22637
+ }
22709
22638
  }
22710
- return badge;
22639
+ resourceCenterBadgeProc(resourceCenter, eligibleModules, controlGuidesInAllModules);
22711
22640
  }
22712
- /*
22713
- * Maintains the DOM presence of the badge for the specified guide
22714
- * where in the badge is either added or removed depending on if
22715
- * the appropiate DOM element is found.
22716
- *
22717
- * For this reason, when Badges are in the play the setTimeout to
22718
- * keep showing the startGuides needs to also continue running.
22719
- *
22720
- * input: guide obj, and Map of ids to badges being shown currently
22721
- */
22722
- var placeBadge = function (guide, badgesShownObj) {
22723
- badgesShownObj = badgesShownObj || badgesShown;
22724
- var badge = badgesShownObj[guide.id];
22725
- var step = badge ? badge.step() : Badge.findStep(guide);
22726
- if (!step)
22727
- return;
22728
- var target = badge ? badge.target() : getElementForTargeting(step);
22729
- var isRcBadge = _.get(guide, 'attributes.resourceCenter');
22730
- if (isRcBadge && !store.getters['frames/canShowRCBadge']()) {
22731
- if (badge && _.isFunction(badge.hide)) {
22732
- // If top frame comes in late and RC badge showing in inner frame bc of delay, hide it here
22733
- badgesShownObj[guide.id] = undefined;
22734
- 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;
22735
22670
  }
22736
- return;
22737
22671
  }
22738
- // Don't render the resource center badge if the resource center itself has nothing to show
22739
- 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)
22740
22686
  return;
22741
- BuildingBlockResourceCenter.updateNotificationBubbles();
22742
- var passesElementContainsRules = true;
22743
- if (step.elementContainsRulesList) {
22744
- 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);
22745
22696
  }
22746
- const badgeCanShow = step.elementPathRule &&
22747
- isElementVisible(target) &&
22748
- SizzleProxy.matchesSelector(target, step.elementPathRule) &&
22749
- passesElementContainsRules;
22750
- if (badgeCanShow) {
22751
- if (!badge) {
22752
- 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);
22753
22707
  }
22754
- badge.show();
22755
- badgesShownObj[guide.id] = badge;
22756
22708
  }
22757
- else if (badge) {
22758
- // if the step is being show for this badge then
22759
- // don't remove it.
22760
- if (!step.isShown()) {
22761
- // 3. If the element is not found, remove it if it's currently shown.
22762
- badgesShownObj[guide.id] = undefined;
22763
- 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();
22764
22745
  }
22746
+ this.queue = this.phases.slice();
22765
22747
  }
22766
- if (isDebuggingEnabled()) {
22767
- Events.stepEligibility.trigger({
22768
- stepId: step.id,
22769
- guideId: guide.id,
22770
- eligible: badgeCanShow,
22771
- 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;
22772
22788
  });
22789
+ if (i >= 0) {
22790
+ this.phases.splice(i, 1);
22791
+ }
22773
22792
  }
22774
- BuildingBlockResourceCenter.repositionDoubleResourceCenterBadge();
22775
- };
22776
- var removeAllBadges = function () {
22777
- _.map(badgesShown, removeBadge);
22778
- setBadgesShown({});
22779
- };
22780
- var removeBadge = function (badge, guide) {
22781
- if (!badge)
22782
- return;
22783
- if (_.isFunction(badge.hide)) {
22784
- badge.hide();
22793
+ timedOut() {
22794
+ if (!this.iterationStartTime)
22795
+ return false;
22796
+ var elapsed = getNow() - this.iterationStartTime;
22797
+ return elapsed > store.state.guideUpdate.iterationTimeLimit;
22785
22798
  }
22786
- if (_.isFunction(badge.shouldShow) && !badge.shouldShow() && guide) {
22787
- 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
+ }
22788
22806
  }
22789
- };
22790
- var removeBadgeForGuide = function (guideGroup) {
22791
- var badge = badgesShown[guideGroup.id];
22792
- if (badge) {
22793
- 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
+ }
22794
22825
  }
22795
- };
22796
- function _adjustBadgesForResize() {
22797
- _.map(badgesShown, function (badge) {
22798
- if (badge && badge.show) {
22799
- 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
+ }
22800
22838
  }
22801
- });
22839
+ }
22840
+ isComplete() {
22841
+ return !this.queue.length;
22842
+ }
22802
22843
  }
22803
- var adjustBadgesForResize = _.throttle(_adjustBadgesForResize, 10);
22804
- function pruneBadges(activeGuideIds) {
22805
- if (!activeGuideIds || !activeGuideIds.length)
22806
- return;
22807
- const lookup = _.indexBy(activeGuideIds);
22808
- _.each(badgesShown, function (badge, guideId) {
22809
- if (!lookup[guideId]) {
22810
- badge.hide();
22811
- delete badgesShown[guideId];
22812
- }
22813
- });
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);
22814
22860
  }
22815
22861
 
22816
22862
  /**
@@ -23525,7 +23571,7 @@ function registerPostrenderScripts(runtime) {
23525
23571
  });
23526
23572
  }
23527
23573
 
23528
- const pendoLocalStorage$3 = agentStorage.getLocal();
23574
+ const pendoLocalStorage$2 = agentStorage.getLocal();
23529
23575
  var guideEvtCache = [];
23530
23576
  var controlGuideLogMessage = 'Guide was not shown because this visitor is in a control group of an active experiment for the guide';
23531
23577
  /**
@@ -25323,7 +25369,7 @@ var handleResizeOnVideoFullScreenChange = function (event) {
25323
25369
  function shouldReloadGuides(url, visitorId, accountId, metadataHash) {
25324
25370
  if (!url || !pendo$1.apiKey)
25325
25371
  return false;
25326
- if (areGuidesDisabled() && !(pendoLocalStorage$3.getItem('pendo-designer-mode') === 'true'))
25372
+ if (areGuidesDisabled() && !(pendoLocalStorage$2.getItem('pendo-designer-mode') === 'true'))
25327
25373
  return false;
25328
25374
  return (url !== reloadGuides.lastUrl ||
25329
25375
  visitorId !== reloadGuides.lastVisitorId ||
@@ -25407,6 +25453,180 @@ function getActiveGuide(options) {
25407
25453
  };
25408
25454
  }
25409
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
+
25410
25630
  var REMOVE_FRAME_ACTION = 'frames/removeFrame';
25411
25631
  function tryParseJson(messageData) {
25412
25632
  if (!_.isString(messageData)) {
@@ -25921,7 +26141,8 @@ function initializeCrossFrameChannel(store) {
25921
26141
  return () => crossFrameChannel.close();
25922
26142
  }
25923
26143
 
25924
- const pendoLocalStorage$2 = agentStorage.getLocal();
26144
+ const pendoLocalStorage$1 = agentStorage.getLocal();
26145
+ var pendoLivePreview$1 = 'pendo-live-preview';
25925
26146
  var pendoPreview$1 = 'pendo-preview';
25926
26147
  var designerPreview$1 = 'current-guide-preview';
25927
26148
  var previewFrame;
@@ -25935,6 +26156,11 @@ function startPreviewMode(window) {
25935
26156
  return;
25936
26157
  addPreviewUI(window);
25937
26158
  store.dispatch('preview/start', _.extend(config));
26159
+ if (config.livePreview) {
26160
+ startLivePreviewRelay(config.livePreview, function () {
26161
+ exitPreviewMode({ preventWindowClose: false });
26162
+ });
26163
+ }
25938
26164
  agentStorage.clear(LAST_STEP_ADVANCED_COOKIE);
25939
26165
  enableCookies(false);
25940
26166
  buffers.lock();
@@ -25998,7 +26224,7 @@ function restartPreview(activeGuides, lastGuideStepSeen, language) {
25998
26224
  var config = findStoredPreviewConfig();
25999
26225
  // Go back to 1st step of walkthrough on explicit restart
26000
26226
  if (config) {
26001
- var previewGuide = _.findWhere(activeGuides, { id: config.guideId });
26227
+ var previewGuide = _.findWhere(activeGuides, { id: getPreviewGuideId(config) });
26002
26228
  if (previewGuide) {
26003
26229
  var restartConfig = { stepId: previewGuide.steps[0].id };
26004
26230
  // language is used in `previewGuideRequest` to generate the proper urls for rendering translations
@@ -26122,6 +26348,7 @@ function prepareDesignerPreviewGuide() {
26122
26348
  currentPreviewGuideConfig.guide.expiresAfter = null;
26123
26349
  return [currentPreviewGuideConfig.guide];
26124
26350
  }
26351
+ return [];
26125
26352
  }
26126
26353
  function preparePreviewGuides(activeGuides) {
26127
26354
  var config = findStoredPreviewConfig();
@@ -26200,7 +26427,8 @@ function preparePreviewLastGuideStepSeen(activeGuides, lastGuideStepSeen) {
26200
26427
  var config = findStoredPreviewConfig();
26201
26428
  if (!config)
26202
26429
  return lastGuideStepSeen;
26203
- var previewGuide = _.findWhere(activeGuides, { id: config.guideId });
26430
+ var guideId = getPreviewGuideId(config);
26431
+ var previewGuide = _.findWhere(activeGuides, { id: guideId });
26204
26432
  if (!previewGuide)
26205
26433
  return lastGuideStepSeen;
26206
26434
  if (config.state)
@@ -26213,7 +26441,7 @@ function preparePreviewLastGuideStepSeen(activeGuides, lastGuideStepSeen) {
26213
26441
  return {};
26214
26442
  }
26215
26443
  return {
26216
- guideId: config.guideId,
26444
+ guideId,
26217
26445
  guideStepId: config.stepId,
26218
26446
  state: 'active'
26219
26447
  };
@@ -26261,9 +26489,13 @@ function updatePreview(document, activeGuides, lastGuideStepSeen) {
26261
26489
  }
26262
26490
  }, location.origin);
26263
26491
  }
26492
+ var previewGuideId = getPreviewGuideId(config);
26264
26493
  var guide = _.find(activeGuides, function (guide) {
26265
- return guide.id === config.guideId;
26494
+ return guide.id === previewGuideId;
26266
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;
26267
26499
  var currentStepIndex = 0;
26268
26500
  var stepCount = guide.steps.length;
26269
26501
  var lastSeen = lastGuideStepSeen || {};
@@ -26425,7 +26657,7 @@ function exitPreviewMode(designerConfig) {
26425
26657
  if (currentPreviewGuideConfig) {
26426
26658
  wasInDesignerSameTabPreview = currentPreviewGuideConfig.previewModeType === 'SAME_TAB';
26427
26659
  }
26428
- var storedNavigationMode = pendoLocalStorage$2.getItem('pendo-navigation-state');
26660
+ var storedNavigationMode = pendoLocalStorage$1.getItem('pendo-navigation-state');
26429
26661
  store.dispatch('preview/exit');
26430
26662
  hideGuides({ stayHidden: true });
26431
26663
  agentStorage.clear(LAST_STEP_ADVANCED_COOKIE);
@@ -26434,30 +26666,37 @@ function exitPreviewMode(designerConfig) {
26434
26666
  restoreGuideLoader();
26435
26667
  removeUpdatePhase(updatePreviewUI);
26436
26668
  enableCookies(true);
26437
- // Try to launch back into designer
26438
- if (wasInDesignerSameTabPreview && storedNavigationMode) {
26439
- pendoLocalStorage$2.setItem(designerPreview$1, JSON.stringify(_.extend(currentPreviewGuideConfig, { isExitBackToDesigner: true })));
26440
- try {
26441
- var message = JSON.parse(storedNavigationMode);
26442
- var launchOptions = {
26443
- target: message.baseUrl,
26444
- gcsBucket: message.gcsBucket,
26445
- preloader: true,
26446
- DADesigner: message.isDADesigner,
26447
- host: message.host,
26448
- forceNativeDesigner: currentPreviewGuideConfig.guide.appPlatform === 'web'
26449
- };
26450
- store.dispatch('preview/launchInAppDesigner', launchOptions);
26451
- }
26452
- catch (err) {
26453
- 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;
26454
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);
26455
26697
  return;
26456
26698
  }
26457
- if (designerConfig && !designerConfig.preventWindowClose)
26458
- window.close();
26459
- // For cases when window.close() does not work, as when exiting restored preview sessions in newly opened windows, resume normal guide display
26460
- forceGuideReload();
26699
+ finishExit();
26461
26700
  }
26462
26701
  function clonePreviewConfig(getStore, path) {
26463
26702
  var previewConfig = _.get(getStore(), path);
@@ -26470,6 +26709,14 @@ function getStore() {
26470
26709
  }
26471
26710
  var findStoredPreviewConfig = _.partial(clonePreviewConfig, getStore, 'state.preview.preview');
26472
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
+ }
26473
26720
  function getGuideUrl(config) {
26474
26721
  if (!config.subscriptionId || !config.guideId)
26475
26722
  return;
@@ -26518,6 +26765,35 @@ function previewGuideRequest(config, id) {
26518
26765
  log.info('Invalid origin, exiting preview');
26519
26766
  return q.reject('invalid origin');
26520
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
+ }
26521
26797
  var guideURL = getGuideUrl(config);
26522
26798
  if (!guideURL) {
26523
26799
  log.info('Invalid guide url, exiting preview');
@@ -26607,9 +26883,16 @@ function previewGuideRequest(config, id) {
26607
26883
  }
26608
26884
  function previewLoadGuideJs(url, callback) {
26609
26885
  var config = findStoredPreviewConfig();
26610
- if (!config || !getGuideUrl(config))
26886
+ if (!config)
26611
26887
  return q.reject();
26612
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();
26613
26896
  if (!match || match.length < 1)
26614
26897
  return q.reject();
26615
26898
  return previewGuideRequest(config, match[1]).then(callback);
@@ -26733,7 +27016,7 @@ const Keys = (function () {
26733
27016
  };
26734
27017
  })();
26735
27018
 
26736
- const pendoLocalStorage$1 = agentStorage.getLocal();
27019
+ const pendoLocalStorage = agentStorage.getLocal();
26737
27020
  var DesignerV2 = (function () {
26738
27021
  // Always use the production server for CNAME customers
26739
27022
  var host = DESIGNER_SERVER || SERVER;
@@ -26747,7 +27030,7 @@ var DesignerV2 = (function () {
26747
27030
  function localStorageNavigation(options) {
26748
27031
  if (options.ignoreLocalStorageNavigation)
26749
27032
  return;
26750
- var storedNavigationMode = pendoLocalStorage$1.getItem('pendo-navigation-state');
27033
+ var storedNavigationMode = pendoLocalStorage.getItem('pendo-navigation-state');
26751
27034
  if (!storedNavigationMode)
26752
27035
  return;
26753
27036
  // only launch from local storage if you are the leader frame
@@ -27464,6 +27747,16 @@ function launchDesignerOrPreview(options) {
27464
27747
  * @label designerPreview
27465
27748
  */
27466
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);
27467
27760
  // in order, try to launch:
27468
27761
  // 1) designer
27469
27762
  // 2) preview
@@ -28712,6 +29005,7 @@ const PluginAPI = {
28712
29005
  SERVER,
28713
29006
  DESIGNER_SERVER
28714
29007
  },
29008
+ isTrustedOrigin,
28715
29009
  NodeSerializer,
28716
29010
  q,
28717
29011
  SendQueue,
@@ -34455,7 +34749,16 @@ var FramesModule = (function () {
34455
34749
  testSelector(context, payload) {
34456
34750
  const { selector, sourceFrame, promiseId } = payload;
34457
34751
  const startTime = performance.now();
34458
- 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
+ }
34459
34762
  const timing = performance.now() - startTime;
34460
34763
  if (promiseId && sourceFrame) {
34461
34764
  crossFrameChannel.postMessage({
@@ -35043,7 +35346,7 @@ var MetadataModule = (function () {
35043
35346
  };
35044
35347
  })();
35045
35348
 
35046
- const pendoLocalStorage = agentStorage.getLocal();
35349
+ var pendoLivePreview = 'pendo-live-preview';
35047
35350
  var pendoPreview = 'pendo-preview';
35048
35351
  var designerPreview = 'current-guide-preview';
35049
35352
  var PreviewModule = (function () {
@@ -35067,11 +35370,15 @@ var PreviewModule = (function () {
35067
35370
  load(context) {
35068
35371
  try {
35069
35372
  var storage = context.getters.storage();
35070
- var config = JSON.parse(storage.getItem(pendoPreview));
35373
+ var config = JSON.parse(storage.getLocal().getItem(pendoPreview));
35071
35374
  if (config.apiKey === pendo$1.apiKey) {
35375
+ var livePreviewConfig = JSON.parse(storage.getSession().getItem(pendoLivePreview));
35376
+ if (livePreviewConfig) {
35377
+ config.livePreview = livePreviewConfig;
35378
+ }
35072
35379
  context.commit('setPreview', config);
35073
35380
  }
35074
- var designerPreviewConfig = storage.getItem(designerPreview);
35381
+ var designerPreviewConfig = storage.getLocal().getItem(designerPreview);
35075
35382
  if (designerPreviewConfig) {
35076
35383
  context.commit('setDesignerPreview', JSON.parse(designerPreviewConfig));
35077
35384
  }
@@ -35082,11 +35389,19 @@ var PreviewModule = (function () {
35082
35389
  write(context) {
35083
35390
  try {
35084
35391
  var storage = context.getters.storage();
35085
- if (context.state.preview) {
35086
- 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));
35087
35402
  }
35088
35403
  else {
35089
- storage.removeItem(pendoPreview);
35404
+ storage.getSession().removeItem(pendoLivePreview);
35090
35405
  }
35091
35406
  }
35092
35407
  catch (e) {
@@ -35098,8 +35413,7 @@ var PreviewModule = (function () {
35098
35413
  },
35099
35414
  exitDesignerPreview(context) {
35100
35415
  context.commit('setDesignerPreview', null);
35101
- var storage = context.getters.storage();
35102
- storage.removeItem(designerPreview);
35416
+ context.getters.storage().getLocal().removeItem(designerPreview);
35103
35417
  },
35104
35418
  launchInAppDesigner(context, launchOptions) {
35105
35419
  DesignerV2.launchInAppDesigner(launchOptions);
@@ -35126,7 +35440,7 @@ var PreviewModule = (function () {
35126
35440
  return !!state.designerPreview && getters.isInPreviewMode();
35127
35441
  },
35128
35442
  storage() {
35129
- return pendoLocalStorage;
35443
+ return agentStorage;
35130
35444
  }
35131
35445
  };
35132
35446
  return {
@@ -35282,13 +35596,10 @@ function highlight(dom, elements) {
35282
35596
  }
35283
35597
  function highlightElements(dom, selector) {
35284
35598
  try {
35285
- const query = dom(selector);
35286
- const total = query ? query.length : 0;
35287
- const elements = [];
35288
- for (let i = 0; i < total; i++) {
35289
- elements.push(query[i]);
35290
- }
35291
- 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));
35292
35603
  }
35293
35604
  catch (e) {
35294
35605
  clearHighlights();
@@ -40927,6 +41238,14 @@ class DOMPrompt {
40927
41238
  });
40928
41239
  }
40929
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
+ }
40930
41249
  this.promptContainer = new this.dom.Observer();
40931
41250
  const validInputEls = this._.filter(this.inputEls, el => el);
40932
41251
  const validSubmitEls = this._.filter(this.submitEls, el => el);
@@ -41002,6 +41321,9 @@ class DOMPrompt {
41002
41321
  });
41003
41322
  }
41004
41323
  teardown() {
41324
+ if (this.observerReadyListener) {
41325
+ document.removeEventListener('DOMContentLoaded', this.observerReadyListener);
41326
+ }
41005
41327
  if (this.promptContainer) {
41006
41328
  this.promptContainer.teardown();
41007
41329
  }
@@ -41046,7 +41368,8 @@ class PromptPlugin {
41046
41368
  // register config schema
41047
41369
  this.api.ConfigReader.addOption(this.configName, ['pendoconfig', 'snippet']);
41048
41370
  this.loadConfig(this.configName)
41049
- .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));
41050
41373
  // Listen for URL changes to re-evaluate page rules
41051
41374
  // guidesLoaded: normalizedUrl comes back from the guides payload
41052
41375
  // urlChanged: handles SPA navigation where guides may already be cached and guidesLoaded won't re-fire
@@ -41112,7 +41435,12 @@ class PromptPlugin {
41112
41435
  const isCurrentlyActive = this._.contains(currentlyActiveIds, aiAgent.id);
41113
41436
  const shouldBeActive = this.testPageRule(aiAgent.pageRule, currentUrl, aiAgent.id);
41114
41437
  if (shouldBeActive && !isCurrentlyActive) {
41115
- 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
+ }
41116
41444
  }
41117
41445
  });
41118
41446
  }
@@ -41161,7 +41489,12 @@ class PromptPlugin {
41161
41489
  this._.each(domAgents, aiAgent => {
41162
41490
  const currentUrl = getNormalizedUrl();
41163
41491
  if (this.testPageRule(aiAgent.pageRule, currentUrl, aiAgent.id)) {
41164
- 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
+ }
41165
41498
  }
41166
41499
  });
41167
41500
  }
@@ -42910,10 +43243,13 @@ function GuideMarkdown() {
42910
43243
  }
42911
43244
 
42912
43245
  // Resolve the global object without relying on a bare `window` identifier.
42913
- // `window` is only a bound global in a normal browser realm; when the agent is
42914
- // loaded via the npm `loadAsModule` path (or inside a Web Worker) the module is
42915
- // evaluated in a realm where `window` is undeclared, so referencing it bare
42916
- // throws a ReferenceError at module-load. `globalThis` resolves in every realm.
43246
+ // `window` is only a bound global in a normal browser realm; the compat builds
43247
+ // bind it via their `(function(window, document){...})` wrapper, but the npm
43248
+ // module build has no wrapper, so its module scope can be evaluated in realms
43249
+ // where `window` is undeclared (a Web Worker, or a customer bundler re-chunking
43250
+ // the module into worker-reachable code) and a bare reference throws a
43251
+ // ReferenceError at module-load. `globalThis` resolves in every realm; the
43252
+ // `self`/`window` fallbacks cover engines that predate it. APP-160669, APP-165809.
42917
43253
  function getGlobalScope() {
42918
43254
  if (typeof globalThis !== 'undefined') {
42919
43255
  return globalThis;
@@ -42961,6 +43297,7 @@ var setTimeout$1 = setTimeout;
42961
43297
  //
42962
43298
  // NOTE: This, for now, at least is identified as an instanceof HTMLAnchorElement
42963
43299
 
43300
+
42964
43301
  function URLPolyfillFactory(doc) {
42965
43302
  return function URL(str) {
42966
43303
  var a = doc.createElement('a');
@@ -42992,24 +43329,12 @@ function URLPolyfillFactory(doc) {
42992
43329
  var URL$1 = (function(global, factory) {
42993
43330
  var nativeURL = global.URL;
42994
43331
  if (typeof nativeURL !== 'function' || !/native/.test(nativeURL)) {
42995
- return factory(document);
43332
+ return factory(global.document);
42996
43333
  }
42997
43334
 
42998
43335
  return nativeURL;
42999
- })(window, URLPolyfillFactory);
43336
+ })(globalScope, URLPolyfillFactory);
43000
43337
 
43001
- /*
43002
- * @license MIT - https://github.com/taylorhakes/promise-polyfill
43003
- */
43004
- // Modified to not affect global environment: the `var` keeps the binding module-local
43005
- // so the polyfill never assigns to the real global `Promise`.
43006
- //
43007
- // Main-thread only. Reading `window` here is safe because rollup's `inject` is skipped
43008
- // for the nested worker build (see `skipPlugins` on webWorkerLoader in
43009
- // rollup/web-sdk.config.mjs): a worker realm has a native Promise and no `window`, so
43010
- // injecting this polyfill there was both unreachable and fatal — it threw
43011
- // `ReferenceError: window is not defined` at worker startup and stopped session replay
43012
- // with a WORKER_ERROR. APP-163474.
43013
43338
  var Promise$1;
43014
43339
 
43015
43340
  Promise$1 = (function (global, factory) {
@@ -43019,7 +43344,7 @@ Promise$1 = (function (global, factory) {
43019
43344
  }
43020
43345
 
43021
43346
  return nativePromise;
43022
- }(window, (function () {
43347
+ }(globalScope, (function () {
43023
43348
  /**
43024
43349
  * @this {Promise}
43025
43350
  */
@@ -43484,6 +43809,7 @@ const MESSAGE_PROMISE = 'promise';
43484
43809
  * @property {*} requestId - The id of the request. This is used to match responses to requests.
43485
43810
  * @property {*} sender - The name of the sender. This is used to determine if the message is from a trusted source.
43486
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.
43487
43813
  */
43488
43814
  function Message(name, type, data) {
43489
43815
  this.name = name;
@@ -43512,6 +43838,7 @@ const fromEvent = (event) => {
43512
43838
  message.requestId = requestId;
43513
43839
  message.sender = sender;
43514
43840
  message.destination = event.source;
43841
+ message.origin = event.origin;
43515
43842
  message.isResponse = isResponse;
43516
43843
 
43517
43844
  return message;
@@ -43521,7 +43848,7 @@ const fromEvent = (event) => {
43521
43848
  };
43522
43849
 
43523
43850
  const serialize = (message) => {
43524
- const redacted = omit(message, ['destination', 'toString', 'data']);
43851
+ const redacted = omit(message, ['destination', 'origin', 'toString', 'data']);
43525
43852
  redacted.data = JSON.stringify(message.data, (key, value) => {
43526
43853
  if (typeof value === 'function' || value instanceof RegExp) {
43527
43854
  return value.toString ? value.toString() : undefined; // return undefined to omit
@@ -43637,14 +43964,24 @@ const state = {
43637
43964
  topics: {},
43638
43965
  senderIds: {},
43639
43966
  originProvider: undefined,
43967
+ isOriginAllowed: undefined,
43640
43968
  destinationProvider: undefined
43641
43969
  };
43642
43970
 
43643
43971
  const getListener = () => `${state.appName}${state.connectionName}(${state.frameId})`;
43644
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
+
43645
43981
  const _send = (message) => {
43646
43982
  if (!message.sender) message.sender = getListener();
43647
43983
  const win = message.destination || state.destinationProvider();
43984
+ const targetOrigin = getTargetOrigin(message);
43648
43985
  const redactedMessage = serialize(message);
43649
43986
 
43650
43987
  const port = getPortForSender(message.sender);
@@ -43654,11 +43991,11 @@ const _send = (message) => {
43654
43991
  // XXX Need to handle the scenario where MessageChannel is unavailable
43655
43992
  const channel = new MessageChannel();
43656
43993
  addPort(channel.port1, message.sender);
43657
- win.postMessage(redactedMessage, state.originProvider(), [channel.port2]);
43994
+ win.postMessage(redactedMessage, targetOrigin, [channel.port2]);
43658
43995
  } else if (port) {
43659
43996
  port.postMessage(redactedMessage);
43660
43997
  } else {
43661
- win.postMessage(redactedMessage, state.originProvider());
43998
+ win.postMessage(redactedMessage, targetOrigin);
43662
43999
  }
43663
44000
  };
43664
44001
 
@@ -43788,6 +44125,8 @@ const commonFns = pipe(
43788
44125
  );
43789
44126
 
43790
44127
  const filterWrongOrigin = (event) => {
44128
+ if (state.isOriginAllowed) return state.isOriginAllowed(event.origin) ? event : false;
44129
+
43791
44130
  if (state.originProvider() === '*') return event;
43792
44131
 
43793
44132
  return event.origin === state.originProvider() ? event : false;
@@ -43852,7 +44191,8 @@ const startListening = (
43852
44191
  originProvider,
43853
44192
  destinationProvider,
43854
44193
  usePingAutoReconnect = false,
43855
- connectionName
44194
+ connectionName,
44195
+ isOriginAllowed
43856
44196
  ) => {
43857
44197
  if (!window.addEventListener) return;
43858
44198
  state.appName = appName;
@@ -43861,6 +44201,7 @@ const startListening = (
43861
44201
  state.usePingAutoReconnect = usePingAutoReconnect;
43862
44202
  state.connectionName = connectionName || '';
43863
44203
 
44204
+ state.isOriginAllowed = typeof isOriginAllowed === 'function' ? isOriginAllowed : undefined;
43864
44205
  state.originProvider = originProvider || (() => window.location.origin || '*');
43865
44206
  state.destinationProvider = destinationProvider || (() => window.parent || window.top);
43866
44207
 
@@ -43936,7 +44277,11 @@ function VocPortal() {
43936
44277
  pluginAPI = PluginAPI;
43937
44278
  const { frameId } = PluginAPI.EventTracer.addTracerIds({});
43938
44279
  this.removeResizeEvent = pendoGlobal.attachEvent(window, 'resize', resizePortalIframe);
43939
- 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);
43940
44285
  try {
43941
44286
  observePortalContainer();
43942
44287
  }
@@ -50662,6 +51007,113 @@ class Transport {
50662
51007
  }
50663
51008
  }
50664
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
+
50665
51117
  const ELEMENT_NODE = 1;
50666
51118
  const INPUT_MASK = '*'.repeat(10);
50667
51119
  const NUMBER_INPUT_MASK = '0'.repeat(10);
@@ -50907,6 +51359,7 @@ class SessionRecorder {
50907
51359
  this.transport = new Transport(this.WorkerClass, this.pendo, this.api);
50908
51360
  this.transport.onError = bind(this.sendFailure, this, 'WORKER_ERROR');
50909
51361
  this.transport.onWorkerMessage = bind(this.onWorkerMessage, this);
51362
+ this.liveReplayClient = new LiveReplayClient(this.pendo, this.api);
50910
51363
  this.sendQueue = new this.api.SendQueue(bind(this.transport.send, this.transport));
50911
51364
  this.sendQueue.onTimeout = bind(this.sendFailure, this, 'SEND_TIMEOUT');
50912
51365
  this.errorHandler = this.pendo._.throttle(bind(this.logStopReason, this, 'RRWEB_ERROR'), ONE_MINUTE_IN_MILLISECONDS, { trailing: false });
@@ -50997,6 +51450,7 @@ class SessionRecorder {
50997
51450
  this.subscriptions.length = 0;
50998
51451
  this.stop();
50999
51452
  this.transport.stop();
51453
+ this.liveReplayClient.stop();
51000
51454
  }
51001
51455
  ready() {
51002
51456
  if (this.api.ConfigReader.get(RECORDING_CONFIG_AUTO_START) !== false) {
@@ -51071,6 +51525,7 @@ class SessionRecorder {
51071
51525
  this.stop();
51072
51526
  }
51073
51527
  if (visitorConfig && visitorConfig.enable && this.isRecording()) {
51528
+ this.liveReplayClient.onLiveFlag(visitorConfig.live, this.visitorId, () => this.snapshot());
51074
51529
  if (continuationCallback) {
51075
51530
  continuationCallback.call(this);
51076
51531
  }
@@ -51213,6 +51668,7 @@ class SessionRecorder {
51213
51668
  var bind = this.pendo._.bind;
51214
51669
  this.visitorConfig = visitorConfig;
51215
51670
  this.clearOldSessionId();
51671
+ this.liveReplayClient.onLiveFlag(visitorConfig.live, this.visitorId);
51216
51672
  this.sendQueue.start();
51217
51673
  this.interval = setInterval(() => {
51218
51674
  if (!this.sendQueue.failed()) {
@@ -51411,9 +51867,11 @@ class SessionRecorder {
51411
51867
  this.sessionId(this.recordingId);
51412
51868
  this.restartPtm();
51413
51869
  this._sendIds();
51870
+ this.pendo._.each(this.buffer.data, (bufferedEvent) => this._sendLiveReplayEvent(bufferedEvent));
51414
51871
  this.send({ keyframe: true });
51415
51872
  }
51416
51873
  else if (!isMeta) {
51874
+ this._sendLiveReplayEvent(event);
51417
51875
  this.eventsSinceLastKeyFrame += 1;
51418
51876
  const timeSinceLastKeyFrame = event.timestamp - this.lastKeyFrameTime;
51419
51877
  const exceeds24Hours = timeSinceLastKeyFrame >= ONE_DAY_IN_MILLISECONDS;
@@ -51437,6 +51895,23 @@ class SessionRecorder {
51437
51895
  updateCurrentRecordingSize(recordingPayloadSize) {
51438
51896
  this.currentRecordingSize += recordingPayloadSize;
51439
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
+ }
51440
51915
  onWorkerMessage(messageData) {
51441
51916
  switch (messageData.type) {
51442
51917
  case 'workerDied': {
@@ -51471,6 +51946,13 @@ class SessionRecorder {
51471
51946
  this.logStopReason(`MISSING_${messageData.missingData}`);
51472
51947
  break;
51473
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
+ }
51474
51956
  }
51475
51957
  }
51476
51958
  addRecordingId(event) {
@@ -51509,6 +51991,7 @@ class SessionRecorder {
51509
51991
  }
51510
51992
  // send existing events
51511
51993
  this.send();
51994
+ this.liveReplayClient.stop();
51512
51995
  // now, finish stopping any more collection or sending
51513
51996
  this.sendQueue.stop();
51514
51997
  this.recordingId = null;
@@ -51578,7 +52061,8 @@ class SessionRecorder {
51578
52061
  this.api.Events.eventCaptured.trigger(payload);
51579
52062
  const params = {
51580
52063
  v: this.pendo.VERSION,
51581
- recordingId: this.recordingId
52064
+ recordingId: this.recordingId,
52065
+ vis: this.api.util.base64EncodeString(this.visitorId)
51582
52066
  };
51583
52067
  if (payload.keyFrame) {
51584
52068
  params.kf = 1;
@@ -51971,19 +52455,27 @@ var WorkerFactory = /*#__PURE__*/createInlineWorkerFactory(/* rollup-plugin-web-
51971
52455
  method: 'POST',
51972
52456
  body
51973
52457
  }).then(function (response) {
51974
- cancelResponseBody(response);
51975
52458
  if (response.status < 200 || response.status >= 300) {
52459
+ cancelResponseBody(response);
51976
52460
  postMessage({
51977
52461
  error: new Error(`received status code ${response.status}: ${response.statusText}`),
51978
52462
  status: response.status,
51979
52463
  sequence
51980
52464
  });
52465
+ return;
51981
52466
  }
51982
- else {
51983
- postMessage({
51984
- sequence
51985
- });
51986
- }
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 () { });
51987
52479
  }).catch(function (e) {
51988
52480
  postMessage({
51989
52481
  error: e,
@@ -55467,7 +55959,7 @@ var underscore = {exports: {}};
55467
55959
  var underscoreExports = underscore.exports;
55468
55960
  var _ = /*@__PURE__*/getDefaultExportFromCjs(underscoreExports);
55469
55961
 
55470
- var encodeURIComponent = _.isFunction(window.encodeURIComponent) ? window.encodeURIComponent : _.identity;
55962
+ var encodeURIComponent = _.isFunction(globalScope.encodeURIComponent) ? globalScope.encodeURIComponent : _.identity;
55471
55963
 
55472
55964
  const WIDGET_SETTINGS_API = '/external/widget/settings';
55473
55965
  class PredictApiService {