@pendo/agent 2.334.0 → 2.335.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.
@@ -4029,8 +4029,8 @@ let SERVER = '';
4029
4029
  let ASSET_HOST = '';
4030
4030
  let ASSET_PATH = '';
4031
4031
  let DESIGNER_SERVER = '';
4032
- let VERSION = '2.334.0_';
4033
- let PACKAGE_VERSION = '2.334.0';
4032
+ let VERSION = '2.335.0_';
4033
+ let PACKAGE_VERSION = '2.335.0';
4034
4034
  let LOADER = 'xhr';
4035
4035
  /* eslint-enable web-sdk-eslint-rules/no-gulp-env-references */
4036
4036
  /**
@@ -11704,7 +11704,7 @@ function trimSurrogate(s) {
11704
11704
  class ElementGetter {
11705
11705
  constructor(cssSelector) {
11706
11706
  this.listeners = {}; // callbacks to be called when an event of type occurs
11707
- this.events = []; // event types this element listens for
11707
+ this.events = []; // [{ type, capture }] events this element listens for
11708
11708
  this.cssSelector = cssSelector;
11709
11709
  }
11710
11710
  get() {
@@ -11719,8 +11719,8 @@ class ElementGetter {
11719
11719
  if (!el) {
11720
11720
  return undefined;
11721
11721
  }
11722
- _.each(this.events, (evtType) => {
11723
- el.addEventListener(evtType, (e) => this.onEvent(e));
11722
+ _.each(this.events, ({ type, capture }) => {
11723
+ el.addEventListener(type, (e) => this.onEvent(e), capture);
11724
11724
  });
11725
11725
  this.elRef = new WeakRef(el);
11726
11726
  return el;
@@ -11729,12 +11729,13 @@ class ElementGetter {
11729
11729
  // XXX not sure about size limit
11730
11730
  return getTextValue(this.get(), limit);
11731
11731
  }
11732
- addEventListener(event, callback) {
11732
+ addEventListener(event, callback, capture = false) {
11733
11733
  const el = this.get();
11734
- if (this.events.indexOf(event) < 0) {
11735
- this.events.push(event);
11734
+ const alreadyRegistered = _.find(this.events, (e) => e.type === event && e.capture === capture);
11735
+ if (!alreadyRegistered) {
11736
+ this.events.push({ type: event, capture });
11736
11737
  if (el) {
11737
- el.addEventListener(event, (e) => this.onEvent(e));
11738
+ el.addEventListener(event, (e) => this.onEvent(e), capture);
11738
11739
  }
11739
11740
  }
11740
11741
  this.listeners[event] = this.listeners[event] || [];
@@ -11746,7 +11747,7 @@ class ElementGetter {
11746
11747
  }
11747
11748
  teardown(el = this.get()) {
11748
11749
  if (el) {
11749
- _.each(this.events, (evtType) => el.removeEventListener(evtType, this.onEvent));
11750
+ _.each(this.events, ({ type, capture }) => el.removeEventListener(type, this.onEvent, capture));
11750
11751
  }
11751
11752
  }
11752
11753
  }
@@ -11787,7 +11788,7 @@ function isTrustedOrigin(host) {
11787
11788
  return true;
11788
11789
  // Domains that Pendo owns, will be swapped in by build patches
11789
11790
  const patterns = [
11790
- /^https:\/\/(adopt\.)?((us1)\.)?(app|via|adopt|cdn|oem|novus-api|novus)(\.(au|eu|gov|hsbc|jpn|tv))?\.pendo\.io$/,
11791
+ /^https:\/\/((adopt|oem)\.)?((us1)\.)?(app|via|adopt|cdn|oem|novus-api|novus)(\.(au|eu|gov|hsbc|jpn|tv))?\.pendo\.io$/,
11791
11792
  /^https:\/\/([0-9]{8}t[0-9]{4}-dot-)(pendo-au|pendo-eu|pendo-govramp|pendo-hsbc|pendo-io|pendo-jp-prod|pendo-tv|pendo-us1)\.appspot\.com$/,
11792
11793
  /^https:\/\/hotfix-(ops|app)-([0-9]+-dot-)(pendo-au|pendo-eu|pendo-govramp|pendo-hsbc|pendo-io|pendo-jp-prod|pendo-tv|pendo-us1)\.appspot\.com$/,
11793
11794
  /^https:\/\/(pendo-au|pendo-eu|pendo-govramp|pendo-hsbc|pendo-io|pendo-jp-prod|pendo-tv|pendo-us1)-static\.storage\.googleapis\.com$/,
@@ -13925,6 +13926,27 @@ function calculateScrollAmount(combinedPos, screenDim, tooltipLayoutDir, bodyOve
13925
13926
  }
13926
13927
  return scrollAmt;
13927
13928
  }
13929
+ var VALID_UI_ELEMENT_TYPES = ['BUTTON', 'A', 'IMG'];
13930
+ var VALID_TRACK_ACTIONS = [
13931
+ 'advanceGuide',
13932
+ 'previousStep',
13933
+ 'dismissGuide',
13934
+ 'openLink',
13935
+ 'showGuide',
13936
+ 'launchGuide',
13937
+ 'guideSnoozed',
13938
+ 'goToStep',
13939
+ 'submitPoll',
13940
+ 'submitPollAndGoToStep',
13941
+ 'confirmation'
13942
+ ];
13943
+ function normalizeUiElementType(elementType) {
13944
+ var type = _.isString(elementType) ? elementType.toUpperCase() : '';
13945
+ return _.contains(VALID_UI_ELEMENT_TYPES, type) ? type : 'BUTTON';
13946
+ }
13947
+ function isValidTrackedAction(action) {
13948
+ return _.isObject(action) && _.isString(action.action) && _.contains(VALID_TRACK_ACTIONS, action.action);
13949
+ }
13928
13950
  function sizzleAttachPointInterceptor(Sizzle, selection, context, results, seed) {
13929
13951
  var args = _.toArray(arguments).slice(1);
13930
13952
  var resultList = Sizzle.apply(this, args);
@@ -14464,8 +14486,12 @@ function replaceWithContentHost(str) {
14464
14486
  return str;
14465
14487
  var subBucketPattern = new RegExp(`(https:)?\\/\\/${SUBSCRIPTION_BUCKET}-\\d+\\.storage\\.googleapis\\.com`, 'g');
14466
14488
  var globalBucketPattern = new RegExp(`(https:)?\\/\\/${GLOBAL_BUCKET}\\.storage\\.googleapis\\.com`, 'g');
14489
+ // Content proxy hosts look like content-<subId>.static.<envDomain>, where envDomain is ASSET_HOST minus its cdn. prefix
14490
+ var envDomain = ASSET_HOST.replace(/^cdn\./, '');
14491
+ var contentProxyPattern = new RegExp(`(https:)?\\/\\/content-\\d+\\.static\\.${escapeRegExp(envDomain)}`, 'g');
14467
14492
  return str.replace(subBucketPattern, contentHost)
14468
14493
  .replace(globalBucketPattern, contentHost)
14494
+ .replace(contentProxyPattern, contentHost)
14469
14495
  .replace(new RegExp(`(https:)?\\/\\/${escapeRegExp(ASSET_HOST)}`, 'g'), contentHost);
14470
14496
  }
14471
14497
  function replaceObjectWithContentHost(obj) {
@@ -15274,7 +15300,7 @@ var BuildingBlockTemplates = (function () {
15274
15300
  function generateResourceCenterModuleList(templateJson, step, guides, buildNodeFromJSON) {
15275
15301
  var children = templateJson.templateChildren;
15276
15302
  return _.reduce(children, function (acc, childConfig, index) {
15277
- var homeView = BuildingBlockResourceCenter.findResourceCenterHomeView(guides);
15303
+ var homeView = pendo$1.BuildingBlocks.BuildingBlockResourceCenter.findResourceCenterHomeView(guides);
15278
15304
  var guide = _.find(guides, function (singleGuide) {
15279
15305
  return singleGuide.id === childConfig.id;
15280
15306
  });
@@ -15459,7 +15485,7 @@ var BuildingBlockTemplates = (function () {
15459
15485
  });
15460
15486
  recursivelyPopulateTemplateChildrenForTaskList(templateJson, children);
15461
15487
  }
15462
- BuildingBlockResourceCenter.updatePseudoStyleSelectors(templateJson);
15488
+ pendo$1.BuildingBlocks.BuildingBlockResourceCenter.updatePseudoStyleSelectors(templateJson);
15463
15489
  const taskListBlocks = buildNodeFromJSON(templateJson, step, guides);
15464
15490
  return [taskListBlocks];
15465
15491
  }
@@ -15579,7 +15605,7 @@ var BuildingBlockTemplates = (function () {
15579
15605
  });
15580
15606
  if (!allAnnouncementsHaveContent)
15581
15607
  return [];
15582
- var resourceCenter = BuildingBlockResourceCenter.getResourceCenter();
15608
+ var resourceCenter = pendo$1.BuildingBlocks.BuildingBlockResourceCenter.getResourceCenter();
15583
15609
  var bubbleConfig = _.get(resourceCenter, 'attributes.notificationBubble');
15584
15610
  if (bubbleConfig) {
15585
15611
  delete bubbleConfig.dir;
@@ -15637,7 +15663,7 @@ var BuildingBlockTemplates = (function () {
15637
15663
  var guideTopPadding = parseInt(containerDomJson.props.style['padding-top'], 10);
15638
15664
  miniBubble.props.style.top = guideTopPadding + containerTopPadding + 5 + 'px';
15639
15665
  }
15640
- if (!BuildingBlockResourceCenter.hasAnnouncementBeenSeen(guide) && miniBubble) {
15666
+ if (!pendo$1.BuildingBlocks.BuildingBlockResourceCenter.hasAnnouncementBeenSeen(guide) && miniBubble) {
15641
15667
  listItem.props.class += ' pendo-unseen-announcement';
15642
15668
  listItem.children.unshift(miniBubble);
15643
15669
  }
@@ -15663,7 +15689,7 @@ var BuildingBlockTemplates = (function () {
15663
15689
  miniBubble = generateUnreadAnnouncementMiniBubble(bubbleConfig);
15664
15690
  miniBubble.props.style.top = '20px';
15665
15691
  }
15666
- if (!BuildingBlockResourceCenter.hasAnnouncementBeenSeen(guide) && miniBubble) {
15692
+ if (!pendo$1.BuildingBlocks.BuildingBlockResourceCenter.hasAnnouncementBeenSeen(guide) && miniBubble) {
15667
15693
  listItem.props.class += ' pendo-unseen-announcement';
15668
15694
  listItem.children = [miniBubble];
15669
15695
  }
@@ -19825,13 +19851,13 @@ var BuildingBlockResourceCenter = (function () {
19825
19851
  // If a module is currently shown, dismiss that first.
19826
19852
  if (shownModule) {
19827
19853
  var firstModuleStep = _.first(shownModule.steps);
19828
- onGuideDismissed(firstModuleStep);
19854
+ pendo$1.onGuideDismissed(firstModuleStep);
19829
19855
  }
19830
19856
  // Finally, dismiss the homeView
19831
19857
  var resourceCenterHomeView = _.get(guideToDismiss, 'attributes.resourceCenter.isTopLevel', false)
19832
19858
  ? guideToDismiss : BuildingBlockResourceCenter.findResourceCenterHomeView(getLocalActiveGuides());
19833
19859
  var homeViewFirstStep = resourceCenterHomeView.steps[0];
19834
- return onGuideDismissed(homeViewFirstStep);
19860
+ return pendo$1.onGuideDismissed(homeViewFirstStep);
19835
19861
  }
19836
19862
  function repositionResourceCenter() {
19837
19863
  var resourceCenterContainer = BuildingBlockResourceCenter.getResourceCenterContainer();
@@ -24504,7 +24530,9 @@ var loadGuideJs = function (apiKey, params) {
24504
24530
  if (treatAsAdoptPartner()) {
24505
24531
  addAccountIdParams(queryString, params.accountId, ConfigReader.get('oemAccountId'));
24506
24532
  }
24507
- if (isDebuggingEnabled()) {
24533
+ // Also check persisted storage: the store's debuggingEnabled flag is only
24534
+ // committed after leader election, which the first guide request usually beats.
24535
+ if (isDebuggingEnabled() || getDebuggingStorage().enabled === true) {
24508
24536
  // Include debug info from server
24509
24537
  queryString.debug = true;
24510
24538
  }
@@ -25663,10 +25691,13 @@ function connectChannelToStore(channel, store) {
25663
25691
  var closed = false;
25664
25692
  const waitingRequests = [];
25665
25693
  channel.addEventListener('message', function (message) {
25694
+ // connect-channel only routes inbound messages to the store; it never
25695
+ // re-runs the guide loop. Anything that changes what guides display
25696
+ // re-arms the loop from the specific store action that makes the change
25697
+ // (see scheduleGuideUpdate in frames.module). Re-running on every
25698
+ // inbound message instead made two framed guide loops wake each other
25699
+ // forever even when idle (APP-162302).
25666
25700
  messageHandler(message.data);
25667
- if (store.state.guideUpdate.useObserver) {
25668
- store.dispatch('guideUpdate/documentChanged');
25669
- }
25670
25701
  });
25671
25702
  return {
25672
25703
  postMessage,
@@ -33298,6 +33329,59 @@ function GuideStep(guide) {
33298
33329
  return;
33299
33330
  onGuideDismissed(this);
33300
33331
  };
33332
+ /**
33333
+ * Records a `guideActivity` analytics event for this Step. Intended for
33334
+ * code-block guides to report UI interactions (button clicks,
33335
+ * link opens, poll submissions) that the automatic click-capture path cannot
33336
+ * resolve from `domJson`. This only reports analytics;
33337
+ *
33338
+ * Because the details originate from customer-authored guide content, they are
33339
+ * validated before staging: a missing or non-string `elementId` drops the event,
33340
+ * an unrecognized `elementType` falls back to `'BUTTON'`, and malformed action
33341
+ * entries are filtered out (no event when none remain valid).
33342
+ *
33343
+ * @access public
33344
+ * @category Guides
33345
+ * @example
33346
+ * step.trackAction({
33347
+ * elementId: 'pendo-button-advance-btn',
33348
+ * elementType: 'BUTTON',
33349
+ * actions: [{ action: 'advanceGuide' }]
33350
+ * })
33351
+ * @param {Object} details
33352
+ * @param {string} details.elementId DOM id of the interacted element. Required; a missing or
33353
+ * non-string value drops the event.
33354
+ * @param {string} [details.elementType] Element type/tag. Coerced to one of BUTTON/A/IMG,
33355
+ * defaulting to 'BUTTON' when missing or unrecognized.
33356
+ * @param {Array<Object>} details.actions Action objects, e.g. `[{ action: 'advanceGuide' }]`. Entries
33357
+ * whose `action` is not a recognized guideActivity action name are dropped.
33358
+ * @returns {void}
33359
+ */
33360
+ this.trackAction = function (details) {
33361
+ details = details || {};
33362
+ if (!details.elementId || !_.isString(details.elementId))
33363
+ return;
33364
+ var actions = _.isArray(details.actions)
33365
+ ? details.actions
33366
+ : (details.action ? [_.omit(details, 'elementId', 'elementType', 'actions')] : []);
33367
+ actions = _.filter(actions, isValidTrackedAction);
33368
+ actions = _.map(actions, function (action) {
33369
+ if (action.action === 'openLink' && action.url) {
33370
+ return _.extend({}, action, { url: sanitizeUrl(action.url) });
33371
+ }
33372
+ return action;
33373
+ });
33374
+ var eventProps = {
33375
+ type: 'guideActivity',
33376
+ guideId: guide.id,
33377
+ stepId: this.id,
33378
+ ui_element_id: details.elementId,
33379
+ ui_element_type: normalizeUiElementType(details.elementType),
33380
+ ui_element_actions: actions,
33381
+ language: guide.language
33382
+ };
33383
+ stageGuideEvent(createGuideEvent(eventProps));
33384
+ };
33301
33385
  this.isPoweredByEnabled = function () {
33302
33386
  return this.hideCredits !== true;
33303
33387
  };
@@ -33651,6 +33735,7 @@ var FramesModule = (function () {
33651
33735
  context.commit('setGuidesLoaded');
33652
33736
  }
33653
33737
  updateMasterGuideList(context.state);
33738
+ scheduleGuideUpdate();
33654
33739
  },
33655
33740
  join(context) {
33656
33741
  if (context.state.disabled)
@@ -33894,6 +33979,7 @@ var FramesModule = (function () {
33894
33979
  stepId: payload.stepId,
33895
33980
  shown: payload.isShown
33896
33981
  });
33982
+ scheduleGuideUpdate();
33897
33983
  },
33898
33984
  hideGuides(context, guideIdsToHide) {
33899
33985
  guideIdsToHide = _.isArray(guideIdsToHide) ? guideIdsToHide : [];
@@ -33914,6 +34000,7 @@ var FramesModule = (function () {
33914
34000
  hideGuides();
33915
34001
  }
33916
34002
  context.commit('hideGuides');
34003
+ scheduleGuideUpdate();
33917
34004
  },
33918
34005
  guideStepHiddenInFrame(context, payload) {
33919
34006
  var wasShown = context.getters.isShownInFrame()(context.state.frameId, { id: payload.stepId });
@@ -33940,6 +34027,7 @@ var FramesModule = (function () {
33940
34027
  stepId: payload.stepId,
33941
34028
  shown: false
33942
34029
  });
34030
+ scheduleGuideUpdate();
33943
34031
  },
33944
34032
  fixDoubleDisplay(context, payload) {
33945
34033
  _.each(context.state.frames, function (frame, frameId) {
@@ -33963,6 +34051,7 @@ var FramesModule = (function () {
33963
34051
  if (!step)
33964
34052
  return;
33965
34053
  step.hide();
34054
+ scheduleGuideUpdate();
33966
34055
  },
33967
34056
  changeLastGuideStepSeen(context, lastGuideStepSeen) {
33968
34057
  crossFrameChannel.postMessage({
@@ -34095,6 +34184,7 @@ var FramesModule = (function () {
34095
34184
  },
34096
34185
  receiveFrameVisibility(context, message) {
34097
34186
  context.commit('setFrameVisibility', message);
34187
+ scheduleGuideUpdate();
34098
34188
  },
34099
34189
  addSelectionModeToFrames(context, message) {
34100
34190
  var options = message.options;
@@ -34500,6 +34590,18 @@ var FramesModule = (function () {
34500
34590
  return _.map(guides, cloneGuide);
34501
34591
  }
34502
34592
  })();
34593
+ // Nudge the guide loop after a cross-frame change that affects what can
34594
+ // display. Guarded on useObserver: when the mutation observer drives the loop
34595
+ // it idles while the page is quiet and needs an explicit nudge to react to
34596
+ // cross-frame state; when the observer is off the loop already polls on its
34597
+ // own. This is called only from the frames actions that actually change
34598
+ // display state (not from every inbound message), which is what lets two
34599
+ // framed guide loops go fully idle instead of pinging each other (APP-162302).
34600
+ function scheduleGuideUpdate() {
34601
+ if (store.state.guideUpdate.useObserver) {
34602
+ store.dispatch('guideUpdate/documentChanged');
34603
+ }
34604
+ }
34503
34605
  function updateMasterGuideList(state) {
34504
34606
  var guidesInThisFrame = _.indexBy(_.filter(getActiveGuides(), function (guide) {
34505
34607
  return !guide.isFrameProxy;
@@ -35914,6 +36016,11 @@ function startDebuggingModuleIfEnabled() {
35914
36016
  if (!debugging.enabled || !isLeader())
35915
36017
  return;
35916
36018
  store.commit('debugger/debuggingEnabled', true);
36019
+ if (pendo$1.guides && _.isUndefined(pendo$1.excludedGuides)) {
36020
+ // The current guide payload was requested without debug=true (e.g. debugging
36021
+ // was just enabled), so it lacks exclusion data; re-fetch to include it.
36022
+ forceGuideReload();
36023
+ }
35917
36024
  addDebuggingFunctions();
35918
36025
  if (dom('#pendo-client-debugger').length)
35919
36026
  return;
@@ -36244,10 +36351,19 @@ var DesignerConnect = (function () {
36244
36351
  }
36245
36352
  function launchDesignerListener(data, msg) {
36246
36353
  var token = msg.data.token;
36247
- if (!token)
36354
+ if (!token) {
36355
+ globalPendo.log('Designer launch request received with no token. Ignoring launch request.');
36356
+ postLaunchError(msg, 'missingToken');
36248
36357
  return;
36358
+ }
36249
36359
  if (token.publicAppId && token.publicAppId !== globalPendo.publicAppId) {
36250
36360
  globalPendo.log(`Designer launch information received was for public app ID ${token.publicAppId}, which does not match the current app ID ${globalPendo.publicAppId}. Ignoring launch request.`);
36361
+ postLaunchError(msg, 'appIdMismatch');
36362
+ return;
36363
+ }
36364
+ if (token.preferredAgentInstallType && token.preferredAgentInstallType !== getInstallType()) {
36365
+ globalPendo.log(`Designer launch information received was for install type ${token.preferredAgentInstallType}, which does not match the current install type ${getInstallType()}. Ignoring launch request.`);
36366
+ postLaunchError(msg, 'installTypeMismatch');
36251
36367
  return;
36252
36368
  }
36253
36369
  tempLaunchToken = token;
@@ -36289,6 +36405,11 @@ var DesignerConnect = (function () {
36289
36405
  pluginAPI.store.dispatch('frames/startPreview');
36290
36406
  }
36291
36407
  }
36408
+ function postLaunchError(msg, reason) {
36409
+ if (msg.source && globalPendo._.isFunction(msg.source.postMessage)) {
36410
+ msg.source.postMessage({ type: 'pendo-designer-launch::error', reason }, msg.origin);
36411
+ }
36412
+ }
36292
36413
  function removeListeners() {
36293
36414
  if (window.removeEventListener) {
36294
36415
  window.removeEventListener('message', messageReceiver, false);
@@ -39124,30 +39245,6 @@ function doSearch(guides, srchTxt) {
39124
39245
  .value();
39125
39246
  }
39126
39247
 
39127
- var lastLauncherHash;
39128
- var launcherProc = function () {
39129
- const guidesList = getActiveGuides();
39130
- if (isLeader()) {
39131
- if (!pendo$1.guideWidget || !pendo$1.guideWidget.enabled)
39132
- return;
39133
- // filter list down to launcher based guides
39134
- var launcherGuides = getLauncherGuideList(guidesList);
39135
- // add all launcher based guides
39136
- updateLauncher(launcherGuides, true);
39137
- }
39138
- else {
39139
- hideLauncher();
39140
- // Check for changes to what should display in the launcher
39141
- var frameGuides = frameLocalGuides(guidesList);
39142
- var launchers = getLauncherGuideList(frameGuides);
39143
- var launcherHash = computeLauncherHash(launchers);
39144
- if (launcherHash !== lastLauncherHash) {
39145
- store.dispatch('frames/changeGuideList', frameGuides);
39146
- }
39147
- lastLauncherHash = launcherHash;
39148
- }
39149
- };
39150
-
39151
39248
  var LAUNCHER_DEFAULT_WIDTH = 330;
39152
39249
  var LAUNCHER_DEFAULT_HEIGHT = 310;
39153
39250
  var LAUNCHER_CLOSED = 'launcher-closed';
@@ -39950,6 +40047,29 @@ var debuggerLaunched = function () {
39950
40047
  var loadLauncherContentHandler = function (activeGuides) {
39951
40048
  return loadLauncherContent(upgradeLauncher(pendo$1.guideWidget, activeGuides));
39952
40049
  };
40050
+ var lastLauncherHash;
40051
+ var launcherProc = function () {
40052
+ const guidesList = getActiveGuides();
40053
+ if (isLeader()) {
40054
+ if (!pendo$1.guideWidget || !pendo$1.guideWidget.enabled)
40055
+ return;
40056
+ // filter list down to launcher based guides
40057
+ var launcherGuides = getLauncherGuideList(guidesList);
40058
+ // add all launcher based guides
40059
+ updateLauncher(launcherGuides, true);
40060
+ }
40061
+ else {
40062
+ hideLauncher();
40063
+ // Check for changes to what should display in the launcher
40064
+ var frameGuides = frameLocalGuides(guidesList);
40065
+ var launchers = getLauncherGuideList(frameGuides);
40066
+ var launcherHash = computeLauncherHash(launchers);
40067
+ if (launcherHash !== lastLauncherHash) {
40068
+ store.dispatch('frames/changeGuideList', frameGuides);
40069
+ }
40070
+ lastLauncherHash = launcherHash;
40071
+ }
40072
+ };
39953
40073
  var initLauncherPlugin = function (pendo, PluginAPI) {
39954
40074
  loadLauncherPublicFunctions(pendo);
39955
40075
  PluginAPI.Events.on('guidesLoaded', guidesLoaded);
@@ -40580,7 +40700,7 @@ class DOMPrompt {
40580
40700
  return evt.isComposing !== undefined ? evt.isComposing : isComposingFromState;
40581
40701
  }
40582
40702
  isEnterKey(evt) {
40583
- return evt.code === 'Enter' || evt.key === 'Enter' || evt.keyCode === 13;
40703
+ return (evt.code === 'Enter' || evt.key === 'Enter' || evt.keyCode === 13) && !evt.shiftKey;
40584
40704
  }
40585
40705
  setupInputListeners() {
40586
40706
  this._.each(this.inputEls, (inputEl) => {
@@ -52344,6 +52464,7 @@ function maskSensitiveFields({ string, contentType, _ }) {
52344
52464
  }
52345
52465
 
52346
52466
  const DEV_LOG_TYPE = 'devlog';
52467
+ const DEV_LOG_SEND_INTERVAL = 5000;
52347
52468
  function createDevLogEnvelope(pluginAPI, globalPendo) {
52348
52469
  return {
52349
52470
  browser_time: pluginAPI.util.getNow(),
@@ -52572,7 +52693,7 @@ function ConsoleCapture() {
52572
52693
  if (!sendQueue.failed()) {
52573
52694
  send();
52574
52695
  }
52575
- }, pluginAPI.constants.SEND_INTERVAL);
52696
+ }, DEV_LOG_SEND_INTERVAL);
52576
52697
  sendQueue.start();
52577
52698
  pluginAPI.Events.ready.on(readyHandler);
52578
52699
  pluginAPI.Events['recording:unpaused'].on(recordingStarted);
@@ -52860,7 +52981,7 @@ function NetworkCapture() {
52860
52981
  if (!sendQueue.failed()) {
52861
52982
  send();
52862
52983
  }
52863
- }, pluginAPI.constants.SEND_INTERVAL);
52984
+ }, DEV_LOG_SEND_INTERVAL);
52864
52985
  sendQueue.start();
52865
52986
  pluginAPI.Events.ready.on(startCapture);
52866
52987
  pluginAPI.Events['recording:unpaused'].on(recordingStarted);
@@ -55210,6 +55331,46 @@ var _ = /*@__PURE__*/getDefaultExportFromCjs(underscoreExports);
55210
55331
 
55211
55332
  var encodeURIComponent = _.isFunction(window.encodeURIComponent) ? window.encodeURIComponent : _.identity;
55212
55333
 
55334
+ const WIDGET_SETTINGS_API = '/external/widget/settings';
55335
+ class PredictApiService {
55336
+ constructor({ baseUrl, token }) {
55337
+ this.performRequest = (url, options) => __awaiter(this, void 0, void 0, function* () {
55338
+ const response = yield fetch(url, options);
55339
+ const body = response.ok ? yield response.json() : null;
55340
+ return { ok: response.ok, status: response.status, body };
55341
+ });
55342
+ this.getWidgetSettingsById = (id, log) => __awaiter(this, void 0, void 0, function* () {
55343
+ var _a, _b;
55344
+ if (!id) {
55345
+ log('[predict] no id provided');
55346
+ return null;
55347
+ }
55348
+ try {
55349
+ const { ok, status, body } = yield this.performRequest(`${this.baseUrl}${WIDGET_SETTINGS_API}/${id}`, {
55350
+ method: 'GET',
55351
+ headers: { 'Content-Type': 'application/json', 'Authorization': this.token }
55352
+ });
55353
+ if (!ok) {
55354
+ log(`[predict] error getting widget settings by id: ${status}`);
55355
+ return null;
55356
+ }
55357
+ const data = body;
55358
+ if ((_a = data === null || data === void 0 ? void 0 : data.errors) === null || _a === void 0 ? void 0 : _a.length) {
55359
+ log(`[predict] error getting widget settings by id: ${data.errors}`);
55360
+ return null;
55361
+ }
55362
+ return (_b = data === null || data === void 0 ? void 0 : data.data) !== null && _b !== void 0 ? _b : null;
55363
+ }
55364
+ catch (error) {
55365
+ log(`[predict] error getting widget settings by id: ${error}`);
55366
+ return null;
55367
+ }
55368
+ });
55369
+ this.baseUrl = baseUrl;
55370
+ this.token = token;
55371
+ }
55372
+ }
55373
+
55213
55374
  const PREDICT_STEP_REGEX = /\$\$predict\$\$/;
55214
55375
  const DRAG_THRESHOLD = 5;
55215
55376
  const STYLE_ID = 'pendo-predict-plugin-styles';
@@ -55217,6 +55378,7 @@ const GUIDE_BUTTON_IDENTIFIER = 'button:contains("token")';
55217
55378
  const pluginVersion = '1.0.1';
55218
55379
  const PREDICT_SANDBOX_PAGE = 'predict-sandbox.html';
55219
55380
  const getPredictBaseUrl = (designerServer) => designerServer.replace('app', 'predict');
55381
+ const getPredictApiBaseUrl = (predictBaseUrl) => predictBaseUrl.replace('predict', 'predict-api');
55220
55382
  const EXTENSION_ORIGIN_REGEX = /^(chrome|moz|safari-web)-extension:\/\//;
55221
55383
  /**
55222
55384
  * When running inside the Pendo Launcher extension, a host page's CSP `frame-src`
@@ -55320,6 +55482,50 @@ const getRecordIdFromUrl = (recordRegex, log = () => { }) => {
55320
55482
  log(`[predict] recordId found: ${recordId}`);
55321
55483
  return recordId;
55322
55484
  };
55485
+ const escapeRegexKeyword = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
55486
+ // Builds the generalized keyword matcher: a simple keyword (single- or
55487
+ // multi-segment, e.g. "Company" or "lightning/r/Opportunity") that works
55488
+ // whether the id lives in the path or the query string. The keyword is escaped
55489
+ // so metacharacters are treated literally, a boundary avoids substring hijacks,
55490
+ // and the id ends naturally at any delimiter.
55491
+ const buildRecordRegex = (keyword) => new RegExp(`(?:^|[/?&=.#])${escapeRegexKeyword(keyword)}[/.=:_-]([a-zA-Z0-9-]+)`, 'i');
55492
+ // Resolves the record id from the current URL using three tiers, returning the
55493
+ // first non-null result:
55494
+ // 1. recordRegexPattern - an advanced, author-supplied full regex.
55495
+ // 2. recordRegexName - the generalized keyword matcher (path + query).
55496
+ // 3. legacy fallback - the original strict `/name/id` template, so every
55497
+ // pre-existing config keeps working with no migration.
55498
+ const resolveRecordId = (configuration, log = () => { }) => {
55499
+ var _a, _b, _c, _d;
55500
+ const { recordRegexPattern, recordRegexName } = configuration;
55501
+ // Tier 1: advanced full pattern (matched against the full href).
55502
+ if (recordRegexPattern) {
55503
+ try {
55504
+ const advancedId = (_b = (_a = window.location.href.match(new RegExp(recordRegexPattern))) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : null;
55505
+ if (advancedId) {
55506
+ log(`[predict] recordId found via recordRegexPattern: ${advancedId}`);
55507
+ return advancedId;
55508
+ }
55509
+ }
55510
+ catch (error) {
55511
+ log(`[predict] invalid recordRegexPattern, skipping: ${error}`);
55512
+ }
55513
+ }
55514
+ if (!recordRegexName) {
55515
+ return null;
55516
+ }
55517
+ // Tier 2: generalized keyword matcher against path + query + hash only
55518
+ // (host excluded to avoid subdomain false matches).
55519
+ const { pathname, search, hash } = window.location;
55520
+ const keywordId = (_d = (_c = `${pathname}${search}${hash}`.match(buildRecordRegex(recordRegexName))) === null || _c === void 0 ? void 0 : _c[1]) !== null && _d !== void 0 ? _d : null;
55521
+ if (keywordId) {
55522
+ log(`[predict] recordId found via recordRegexName: ${keywordId}`);
55523
+ return keywordId;
55524
+ }
55525
+ // Tier 3: legacy template fallback (preserves pre-existing configs).
55526
+ const legacyRegex = new RegExp(`/${recordRegexName}/([a-zA-Z0-9]+)(?:/|$)`);
55527
+ return getRecordIdFromUrl(legacyRegex, log);
55528
+ };
55323
55529
  const safeStringify = (value, fallback) => {
55324
55530
  try {
55325
55531
  return JSON.stringify(value);
@@ -55337,13 +55543,25 @@ const safeParse = (value, fallback, log = () => { }) => {
55337
55543
  return fallback;
55338
55544
  }
55339
55545
  };
55546
+ const getAvailableParams = (params) => {
55547
+ // No need to pass the full configuration when widgetId is present
55548
+ if (params.widgetId) {
55549
+ return {
55550
+ widgetId: params.widgetId,
55551
+ pipeVersion: params.pipeVersion,
55552
+ forceAccountId: params.forceAccountId
55553
+ };
55554
+ }
55555
+ return params;
55556
+ };
55340
55557
  const buildQueryParams = (params) => {
55341
55558
  const urlParams = new URLSearchParams();
55342
- const entries = Object.entries(params);
55559
+ const availableParams = getAvailableParams(params);
55560
+ const entries = Object.entries(availableParams);
55343
55561
  urlParams.set('pluginVersion', pluginVersion);
55344
55562
  for (let i = 0; i < entries.length; i++) {
55345
55563
  const [key, value] = entries[i];
55346
- if (value != null) {
55564
+ if (value !== undefined && value !== null) {
55347
55565
  urlParams.set(key, typeof value === 'object' ? safeStringify(value) : String(value));
55348
55566
  }
55349
55567
  }
@@ -55449,10 +55667,16 @@ const createExplanationIframe = ({ analysisId, recordId, configuration, predictB
55449
55667
  };
55450
55668
  // ----- Floating Modal Creation -----
55451
55669
  const createFloatingModal = ({ recordId, configuration, predictBaseUrl, wrapperBase = '' }) => {
55452
- var _a;
55453
- const flatIds = (_a = configuration.analysisIds) === null || _a === void 0 ? void 0 : _a.flat(Infinity);
55670
+ var _a, _b;
55671
+ const flatIds = ((_b = (_a = configuration.analysisIds) === null || _a === void 0 ? void 0 : _a.flat(Infinity)) !== null && _b !== void 0 ? _b : []);
55454
55672
  for (let i = 0; i < flatIds.length; i++) {
55455
- createExplanationIframe({ analysisId: flatIds[i], recordId, configuration, predictBaseUrl, wrapperBase });
55673
+ createExplanationIframe({
55674
+ analysisId: flatIds[i],
55675
+ recordId,
55676
+ configuration,
55677
+ predictBaseUrl,
55678
+ wrapperBase
55679
+ });
55456
55680
  }
55457
55681
  const container = createElement('div', { id: 'floatingModalContainer', className: 'floating-modal-container' }, document.body);
55458
55682
  const dragHandle = createElement('div', {
@@ -55526,7 +55750,14 @@ const setupMessageListener = ({ token, forceAccountId, cleanup, pendo, predictBa
55526
55750
  }
55527
55751
  const visitorId = (_a = pendo === null || pendo === void 0 ? void 0 : pendo.getVisitorId) === null || _a === void 0 ? void 0 : _a.call(pendo);
55528
55752
  const accountId = forceAccountId !== null && forceAccountId !== void 0 ? forceAccountId : (_b = pendo === null || pendo === void 0 ? void 0 : pendo.getAccountId) === null || _b === void 0 ? void 0 : _b.call(pendo);
55529
- (_c = iframe.contentWindow) === null || _c === void 0 ? void 0 : _c.postMessage({ type: 'TOKEN_RECEIVED', token, idToken, extensionAPIKeys, visitorId, accountId }, frameTargetOrigin);
55753
+ (_c = iframe.contentWindow) === null || _c === void 0 ? void 0 : _c.postMessage({
55754
+ type: 'TOKEN_RECEIVED',
55755
+ token,
55756
+ idToken,
55757
+ extensionAPIKeys,
55758
+ visitorId,
55759
+ accountId
55760
+ }, frameTargetOrigin);
55530
55761
  }
55531
55762
  },
55532
55763
  OPEN_EXPLANATION: () => {
@@ -55574,8 +55805,24 @@ const setupMessageListener = ({ token, forceAccountId, cleanup, pendo, predictBa
55574
55805
  window.addEventListener('message', messageHandler);
55575
55806
  return () => window.removeEventListener('message', messageHandler);
55576
55807
  };
55577
- const predictGuidesScript = ({ step, pendo, cleanupArray, cleanup, log, designerServer }) => {
55578
- var _a;
55808
+ const mapAnalysisIds = (analysesHierarchy) => analysesHierarchy === null || analysesHierarchy === void 0 ? void 0 : analysesHierarchy.map(hierarchy => hierarchy.map(item => item.analysisId));
55809
+ // Loads the widget's stored settings from the predict API, which override
55810
+ // whatever was inlined in the guide button. Any failure logs and returns the inline configuration.
55811
+ const fetchWidgetConfiguration = (_a) => __awaiter(void 0, void 0, void 0, function* () {
55812
+ var _b;
55813
+ var { token, predictBaseUrl, log } = _a, inlineConfiguration = __rest(_a, ["token", "predictBaseUrl", "log"]);
55814
+ const predictApiService = new PredictApiService({
55815
+ baseUrl: getPredictApiBaseUrl(predictBaseUrl),
55816
+ token
55817
+ });
55818
+ const widgetSettings = yield predictApiService.getWidgetSettingsById((_b = inlineConfiguration.widgetId) !== null && _b !== void 0 ? _b : '', log);
55819
+ if (!widgetSettings) {
55820
+ return inlineConfiguration;
55821
+ }
55822
+ return Object.assign(Object.assign({}, inlineConfiguration), { analysisIds: mapAnalysisIds(widgetSettings.analysesHierarchy), recordRegexPattern: widgetSettings.recordRegexPattern, recordRegexName: widgetSettings.recordRegexKeyword });
55823
+ });
55824
+ const predictGuidesScript = ({ step, pendo, cleanupArray, cleanup, log, designerServer }) => __awaiter(void 0, void 0, void 0, function* () {
55825
+ var _c;
55579
55826
  log('[predict] initializing');
55580
55827
  // Before anything else, inject styles so that the guide will be hidden
55581
55828
  const pendoContainerId = step === null || step === void 0 ? void 0 : step.containerId;
@@ -55584,33 +55831,47 @@ const predictGuidesScript = ({ step, pendo, cleanupArray, cleanup, log, designer
55584
55831
  log('[predict] cleanupArray is not empty');
55585
55832
  return;
55586
55833
  }
55834
+ if (!designerServer) {
55835
+ log('[predict] no designerServer found, aborting');
55836
+ return;
55837
+ }
55838
+ const predictBaseUrl = getPredictBaseUrl(designerServer);
55587
55839
  // ----- Extract Configuration -----
55588
- const guideButton = (_a = step.guideElement.find(GUIDE_BUTTON_IDENTIFIER)) === null || _a === void 0 ? void 0 : _a[0];
55840
+ const guideButton = (_c = step.guideElement.find(GUIDE_BUTTON_IDENTIFIER)) === null || _c === void 0 ? void 0 : _c[0];
55589
55841
  if (!guideButton) {
55590
55842
  log('[predict] no guide button found, aborting');
55591
55843
  return;
55592
55844
  }
55593
- const _b = safeParse(guideButton.textContent, {}, log), { token } = _b, configuration = __rest(_b, ["token"]);
55594
- if (!configuration || !token) {
55845
+ const _d = safeParse(guideButton.textContent, {}, log), { token } = _d, inlineConfiguration = __rest(_d, ["token"]);
55846
+ if (!inlineConfiguration || !token) {
55595
55847
  log('[predict] no configuration found, aborting');
55596
55848
  return;
55597
55849
  }
55598
- const recordRegex = new RegExp(`/${configuration.recordRegexName}/([a-zA-Z0-9]+)(?:/|$)`);
55599
- const recordId = getRecordIdFromUrl(recordRegex, log);
55850
+ // Widget-backed guides load their settings from the predict API; those
55851
+ // override whatever was inlined in the guide button. Non-widget guides use
55852
+ // the inlined configuration as-is.
55853
+ const configuration = (inlineConfiguration === null || inlineConfiguration === void 0 ? void 0 : inlineConfiguration.widgetId)
55854
+ ? yield fetchWidgetConfiguration(Object.assign(Object.assign({}, inlineConfiguration), { token,
55855
+ predictBaseUrl,
55856
+ log }))
55857
+ : inlineConfiguration;
55858
+ const recordId = resolveRecordId(configuration, log);
55600
55859
  if (!recordId) {
55601
55860
  log('[predict] no recordId found in URL, aborting');
55602
55861
  return;
55603
55862
  }
55604
- if (!designerServer) {
55605
- log('[predict] no designerServer found, aborting');
55606
- return;
55607
- }
55608
- const predictBaseUrl = getPredictBaseUrl(designerServer);
55609
55863
  const wrapperBase = getFrameWrapperBase(pendo);
55610
55864
  log(`[predict] predictBaseUrl: ${predictBaseUrl}${wrapperBase ? `, wrapping via ${wrapperBase}` : ''}`);
55611
- cleanupArray.push(setupMessageListener({ token, forceAccountId: configuration === null || configuration === void 0 ? void 0 : configuration.forceAccountId, cleanup, pendo, predictBaseUrl, wrapperBase }));
55865
+ cleanupArray.push(setupMessageListener({
55866
+ token,
55867
+ forceAccountId: configuration.forceAccountId,
55868
+ cleanup,
55869
+ pendo,
55870
+ predictBaseUrl,
55871
+ wrapperBase
55872
+ }));
55612
55873
  cleanupArray.push(createFloatingModal({ recordId, configuration, predictBaseUrl, wrapperBase }));
55613
- };
55874
+ });
55614
55875
 
55615
55876
  const PredictGuides = () => {
55616
55877
  let pluginApiRef = null;
@@ -55645,7 +55906,10 @@ const PredictGuides = () => {
55645
55906
  var _a, _b;
55646
55907
  const designerServer = ((_a = pendo === null || pendo === void 0 ? void 0 : pendo.getConfigValue) === null || _a === void 0 ? void 0 : _a.call(pendo, 'designerServer')) ||
55647
55908
  ((_b = pendo === null || pendo === void 0 ? void 0 : pendo.getConfigValue) === null || _b === void 0 ? void 0 : _b.call(pendo, 'server'));
55648
- predictGuidesScript({ step, pendo, cleanupArray, cleanup, log, designerServer });
55909
+ Promise$2.resolve(predictGuidesScript({ step, pendo, cleanupArray, cleanup, log, designerServer })).catch((error) => {
55910
+ log(`[predict] predictGuidesScript crashed, tearing down: ${error}`);
55911
+ cleanup();
55912
+ });
55649
55913
  this.on('unmounted', (evt) => {
55650
55914
  if (evt.reason !== 'hidden')
55651
55915
  cleanup();