@pendo/agent 2.298.2 → 2.300.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.
@@ -3151,7 +3151,6 @@ var ConfigReader = (function () {
3151
3151
  * @type {boolean}
3152
3152
  */
3153
3153
  addOption('enableDebugEvents', [SNIPPET_SRC, PENDO_CONFIG_SRC]);
3154
- addOption('freeNPSData');
3155
3154
  addOption('eventPropertyConfigurations');
3156
3155
  /**
3157
3156
  * By default, a click event property rule can be matched against the element that is clicked on or its
@@ -3539,7 +3538,9 @@ var ConfigReader = (function () {
3539
3538
  addOption('enableAllEmbeddedGuideEvents', [PENDO_CONFIG_SRC], false);
3540
3539
  // Form Validation
3541
3540
  addOption('formValidation', [PENDO_CONFIG_SRC], false);
3541
+ // Performance Metrics
3542
3542
  addOption('performanceMetricsEnabled', [SNIPPET_SRC, PENDO_CONFIG_SRC], true);
3543
+ addOption('sendPerformanceMetrics', [SNIPPET_SRC, PENDO_CONFIG_SRC], false);
3543
3544
  }
3544
3545
  initializeOptions();
3545
3546
  var sourceGetters = {};
@@ -3904,8 +3905,8 @@ let SERVER = '';
3904
3905
  let ASSET_HOST = '';
3905
3906
  let ASSET_PATH = '';
3906
3907
  let DESIGNER_SERVER = '';
3907
- let VERSION = '2.298.2_';
3908
- let PACKAGE_VERSION = '2.298.2';
3908
+ let VERSION = '2.300.0_';
3909
+ let PACKAGE_VERSION = '2.300.0';
3909
3910
  let LOADER = 'xhr';
3910
3911
  /* eslint-enable agent-eslint-rules/no-gulp-env-references */
3911
3912
  /**
@@ -9792,18 +9793,18 @@ function writeErrorPOST(msg) {
9792
9793
  */
9793
9794
  function writeMetricsPOST(payload) {
9794
9795
  try {
9795
- const url = `${HOST}/data/agentmetrics?apiKey=${pendo$1.apiKey}`;
9796
- log.debug(`Will send ${JSON.stringify(payload)} to ${url} using ${fetchKeepalive.supported() ? 'fetch' : 'xhr'} once the endpoint exists`);
9796
+ const url = `${HOST}/data/agentmetrics/${pendo$1.apiKey}`;
9797
+ log.debug(`Sending ${JSON.stringify(payload)} to ${url} using ${fetchKeepalive.supported() ? 'fetch' : 'xhr'}`);
9797
9798
  if (fetchKeepalive.supported()) {
9798
- // fetch(url, {
9799
- // method: 'POST',
9800
- // keepalive: true,
9801
- // body: JSON.stringify(payload),
9802
- // headers: { 'Content-Type': 'application/json' }
9803
- // });
9799
+ fetch(url, {
9800
+ method: 'POST',
9801
+ keepalive: true,
9802
+ body: JSON.stringify(payload),
9803
+ headers: { 'Content-Type': 'application/json' }
9804
+ });
9804
9805
  }
9805
9806
  else {
9806
- // ajax.postJSON(url, payload);
9807
+ ajax.postJSON(url, payload);
9807
9808
  }
9808
9809
  }
9809
9810
  catch (e) {
@@ -11275,7 +11276,7 @@ const trimString = (str, limit) => {
11275
11276
  return trimSurrogate(str.substring(0, limit));
11276
11277
  };
11277
11278
  function getTextValue(elem, limit) {
11278
- if (elem.tagName && ['textarea', 'input'].indexOf(elem.tagName.toLowerCase()) > -1) {
11279
+ if (elem.tagName && _.contains(['textarea', 'input'], elem.tagName.toLowerCase())) {
11279
11280
  return trimString(elem.value, limit);
11280
11281
  }
11281
11282
  return getText(elem, limit);
@@ -11559,7 +11560,7 @@ var loadResource = function (options, callback, sendErrorToCallback = false) {
11559
11560
  script.src = getPolicy(pendo$1).createScriptURL(options.url);
11560
11561
  addIntegrityAttribute(script, options.url);
11561
11562
  document.body.appendChild(script);
11562
- return {};
11563
+ return script;
11563
11564
  }
11564
11565
  else { // Assume JS file.
11565
11566
  script = document.createElement('script');
@@ -11573,7 +11574,7 @@ var loadResource = function (options, callback, sendErrorToCallback = false) {
11573
11574
  if (err) {
11574
11575
  if (sendErrorToCallback)
11575
11576
  originalCallback(err);
11576
- return;
11577
+ return {};
11577
11578
  }
11578
11579
  originalCallback.apply(this, _.toArray(arguments).slice(1));
11579
11580
  });
@@ -11627,7 +11628,7 @@ var loadWatcher = function (target, url, callback) {
11627
11628
  setTimeout$1(function () {
11628
11629
  if (!isLoaded) {
11629
11630
  // Log a warning if we fail to load the resource within 10 seconds
11630
- writeMessage('Failed to load ' + url + ' within 10 seconds');
11631
+ log.critical('Failed to load ' + url + ' within 10 seconds');
11631
11632
  }
11632
11633
  }, 10000);
11633
11634
  }
@@ -12225,27 +12226,50 @@ var localStorageEventBuffer = new LocalStorageEventBuffer();
12225
12226
  *
12226
12227
  * See https://opentelemetry.io/docs/concepts/signals/metrics/#metric-instruments
12227
12228
  */
12228
- const count = (entries) => {
12229
- if (!entries)
12230
- return 0;
12231
- if (entries instanceof Array) {
12232
- return _.reduce(entries, (count, e) => {
12233
- const incr = e.detail && e.detail.incr ? e.detail.incr : 1;
12234
- return count + incr;
12235
- }, 0);
12236
- }
12237
- return 0;
12229
+ const counter = (entries) => {
12230
+ return _.reduce(entries, (count, entry) => {
12231
+ return count + _.get(entry, 'detail.increment', 1);
12232
+ }, 0);
12238
12233
  };
12239
12234
 
12240
- const METRIC_GUIDELOOP_TIMEOUT = 'agent-guideloop-timeout';
12241
- const Metrics = {};
12242
- Metrics[METRIC_GUIDELOOP_TIMEOUT] = {
12243
- displayName: 'Guide Loop Timeouts',
12244
- name: METRIC_GUIDELOOP_TIMEOUT,
12245
- instrument: count
12235
+ const GUIDE_LOOP_TIMEOUT = 'pendo-guide-loop-timeout';
12236
+ const BEACON_GIF_FAILURES = {
12237
+ ptm: 'pendo-ptm-gif-failure',
12238
+ guide: 'pendo-guide-gif-failure',
12239
+ poll: 'pendo-poll-gif-failure',
12240
+ agentic: 'pendo-agentic-gif-failure'
12241
+ };
12242
+ const GUIDE_LOOP_TIMER = 'pendo-guide-loop';
12243
+ const EVENT_CAPTURED_TIMER = 'pendo-event-captured';
12244
+ const INITIALIZE = 'pendo-initialize';
12245
+ const TEARDOWN = 'pendo-teardown';
12246
+ const METRICS = {};
12247
+ METRICS[GUIDE_LOOP_TIMEOUT] = {
12248
+ name: GUIDE_LOOP_TIMEOUT,
12249
+ type: 'counter',
12250
+ instrument: counter
12251
+ };
12252
+ _.each(BEACON_GIF_FAILURES, (name) => {
12253
+ METRICS[name] = {
12254
+ name,
12255
+ type: 'counter',
12256
+ instrument: counter
12257
+ };
12258
+ });
12259
+ METRICS[GUIDE_LOOP_TIMER] = {
12260
+ name: GUIDE_LOOP_TIMER
12261
+ };
12262
+ METRICS[EVENT_CAPTURED_TIMER] = {
12263
+ name: EVENT_CAPTURED_TIMER
12264
+ };
12265
+ METRICS[INITIALIZE] = {
12266
+ name: INITIALIZE
12267
+ };
12268
+ METRICS[TEARDOWN] = {
12269
+ name: TEARDOWN
12246
12270
  };
12247
12271
 
12248
- const PERF_INTERVAL = 1000 * 60 * 10;
12272
+ const PERFORMANCE_SEND_INTERVAL = 1000 * 60 * 10; // 10 minutes
12249
12273
  class PerformanceMonitor {
12250
12274
  constructor() {
12251
12275
  this._isPerformanceApiAvailable = this._checkPerformanceApi();
@@ -12254,50 +12278,59 @@ class PerformanceMonitor {
12254
12278
  this.measures = new Set();
12255
12279
  }
12256
12280
  initialize() {
12281
+ if (!ConfigReader.get('performanceMetricsEnabled'))
12282
+ return _.noop;
12257
12283
  this._measuringPerformance = true;
12258
- this._markPerformance('initialize');
12284
+ this._markPerformance(INITIALIZE);
12259
12285
  this.interval = setInterval(() => {
12260
12286
  this.send();
12261
- }, PERF_INTERVAL);
12287
+ }, PERFORMANCE_SEND_INTERVAL);
12262
12288
  return () => {
12263
12289
  this.teardown();
12264
12290
  };
12265
12291
  }
12266
12292
  teardown() {
12293
+ this._markPerformance(TEARDOWN);
12294
+ this.send();
12267
12295
  this._measuringPerformance = false;
12268
12296
  this._clearMarksAndMeasures();
12269
12297
  clearInterval(this.interval);
12270
12298
  }
12271
- startTimer(name) {
12272
- this._markPerformance(`${name}-start`);
12299
+ startTimer(name, detail = null) {
12300
+ this._markPerformance(`${name}-start`, { detail });
12273
12301
  }
12274
- stopTimer(name) {
12275
- this._markPerformance(`${name}-stop`);
12276
- this._measurePerformance(name);
12302
+ stopTimer(name, detail = null) {
12303
+ this._markPerformance(`${name}-stop`, { detail });
12304
+ this._measurePerformance(name, { detail });
12277
12305
  }
12278
- count(name, incr = 1) {
12279
- this._markPerformance(name, {
12280
- detail: { type: 'count', incr }
12281
- });
12306
+ count(name, increment = 1) {
12307
+ const detail = increment > 1 ? { type: 'counter', increment } : null;
12308
+ this._markPerformance(name, { detail });
12282
12309
  }
12283
12310
  send() {
12284
- const metrics = _.reduce(_.keys(Metrics), (acc, name) => {
12311
+ const payload = _.reduce(METRICS, (acc, metric) => {
12312
+ const { name, type, instrument } = metric;
12313
+ if (!instrument)
12314
+ return acc;
12285
12315
  const entries = performance.getEntriesByName(name);
12286
- const value = Metrics[name].instrument(entries);
12287
- acc.push({ name, value });
12316
+ const value = instrument(entries);
12317
+ acc.push({
12318
+ name,
12319
+ type,
12320
+ value,
12321
+ timestamp: Date.now()
12322
+ });
12288
12323
  return acc;
12289
12324
  }, []);
12290
- const payload = _.filter(metrics, (metric) => {
12291
- return metric.value > 0;
12292
- });
12293
12325
  this._clearMarksAndMeasures();
12326
+ if (!ConfigReader.get('sendPerformanceMetrics'))
12327
+ return;
12294
12328
  if (_.size(payload)) {
12295
12329
  writeMetricsPOST(payload);
12296
12330
  }
12297
12331
  }
12298
12332
  _checkPerformanceApi() {
12299
- return ConfigReader.get('performanceMetricsEnabled') &&
12300
- detectNativeBrowserAPI('performance.mark') &&
12333
+ return detectNativeBrowserAPI('performance.mark') &&
12301
12334
  detectNativeBrowserAPI('performance.measure') &&
12302
12335
  detectNativeBrowserAPI('performance.getEntries') &&
12303
12336
  detectNativeBrowserAPI('performance.getEntriesByName') &&
@@ -12307,39 +12340,34 @@ class PerformanceMonitor {
12307
12340
  _shouldMeasurePerformance() {
12308
12341
  return this._isPerformanceApiAvailable && this._measuringPerformance;
12309
12342
  }
12310
- _markPerformance(name, attr) {
12343
+ _markPerformance(name, metadata) {
12311
12344
  if (!this._shouldMeasurePerformance())
12312
12345
  return;
12313
- name = `pendo-${name}`;
12314
- performance.mark(name);
12346
+ performance.mark(name, metadata);
12315
12347
  this.marks.add(name);
12316
12348
  }
12317
- _measurePerformance(name) {
12349
+ _measurePerformance(name, metadata) {
12318
12350
  if (!this._shouldMeasurePerformance())
12319
12351
  return;
12320
- name = `pendo-${name}`;
12321
12352
  const startMark = `${name}-start`;
12322
12353
  const stopMark = `${name}-stop`;
12323
12354
  if (performance.getEntriesByName(startMark).length && performance.getEntriesByName(stopMark).length) {
12324
- performance.measure(name, startMark, stopMark);
12355
+ performance.measure(name, startMark, stopMark, metadata);
12325
12356
  this.measures.add(name);
12326
12357
  }
12327
12358
  }
12328
12359
  _clearMarksAndMeasures() {
12329
- // TODO: fix custom lint rule to handle sets
12330
- // eslint-disable-next-line agent-eslint-rules/no-array-foreach
12331
- this.marks.forEach(name => {
12360
+ for (const name of this.marks) {
12332
12361
  performance.clearMarks(name);
12333
- });
12362
+ }
12334
12363
  this.marks.clear();
12335
- // eslint-disable-next-line agent-eslint-rules/no-array-foreach
12336
- this.measures.forEach(name => {
12364
+ for (const name of this.measures) {
12337
12365
  performance.clearMeasures(name);
12338
- });
12366
+ }
12339
12367
  this.measures.clear();
12340
12368
  }
12341
12369
  }
12342
- const _PerformanceMonitor = new PerformanceMonitor();
12370
+ const performanceMonitor = new PerformanceMonitor();
12343
12371
 
12344
12372
  var defaultTrackName = '_PENDO_UNNAMED_';
12345
12373
  var SILO_AVG_COMPRESSION_RATIO = 5;
@@ -12391,9 +12419,6 @@ function collectEvent(type, props, url, name, eventProperties, context) {
12391
12419
  if (!isURLValid(event.url)) {
12392
12420
  return;
12393
12421
  }
12394
- if (!eventIsWhitelisted(event)) {
12395
- return;
12396
- }
12397
12422
  if (type === 'track') {
12398
12423
  trackEventQueue.push(event);
12399
12424
  return;
@@ -12404,19 +12429,6 @@ function collectEvent(type, props, url, name, eventProperties, context) {
12404
12429
  }
12405
12430
  eventQueue.push(event);
12406
12431
  }
12407
- // @const {Event.type[]}
12408
- var WHITELIST_FREE_NPS = ['load', 'meta', 'identify'];
12409
- /**
12410
- * @access private
12411
- * @param {Event} event to consider
12412
- * @returns {boolean} whether {event} is allowed
12413
- */
12414
- function eventIsWhitelisted(event) {
12415
- if (ConfigReader.get('freeNPSData')) {
12416
- return _.contains(WHITELIST_FREE_NPS, event.type);
12417
- }
12418
- return true;
12419
- }
12420
12432
  function pipeline() {
12421
12433
  var args = _.toArray(arguments);
12422
12434
  return function generatedPipeline(obj, next) {
@@ -12841,7 +12853,7 @@ function createSendQueue(options, send, guaranteedSend) {
12841
12853
  }
12842
12854
  });
12843
12855
  queue.onTimeout = function () {
12844
- _PerformanceMonitor.count(`${options.beacon}-gif-failure`);
12856
+ performanceMonitor.count(BEACON_GIF_FAILURES[options.beacon]);
12845
12857
  };
12846
12858
  queue.retryPending = true;
12847
12859
  return queue;
@@ -13240,11 +13252,12 @@ function attributeSerializer(context, node) {
13240
13252
 
13241
13253
  function childIndexSerializer(context, node) {
13242
13254
  if (node.parentNode && node.parentNode.childNodes) {
13243
- var nodes = _.chain(node.parentNode.childNodes);
13255
+ const nodes = _.chain(node.parentNode.childNodes);
13244
13256
  context.myIndex = nodes.indexOf(node).value();
13245
- context.childIndex = nodes.filter(function (n) {
13257
+ const childNodes = nodes.filter(function (n) {
13246
13258
  return n.nodeType == ELEMENT;
13247
- }).indexOf(node).value();
13259
+ });
13260
+ context.childIndex = childNodes.indexOf(node).value();
13248
13261
  }
13249
13262
  return context;
13250
13263
  }
@@ -13373,7 +13386,7 @@ var getValidTarget = function (node) {
13373
13386
  */
13374
13387
  var handle_event = function (evt) {
13375
13388
  try {
13376
- _PerformanceMonitor.startTimer('event-captured');
13389
+ performanceMonitor.startTimer(EVENT_CAPTURED_TIMER);
13377
13390
  if (dom.data.get(evt, 'counted'))
13378
13391
  return;
13379
13392
  dom.data.set(evt, 'counted', true);
@@ -13414,7 +13427,7 @@ var handle_event = function (evt) {
13414
13427
  log.critical('pendo.io while handling event', { error: e });
13415
13428
  }
13416
13429
  finally {
13417
- _PerformanceMonitor.stopTimer('event-captured');
13430
+ performanceMonitor.stopTimer(EVENT_CAPTURED_TIMER);
13418
13431
  }
13419
13432
  };
13420
13433
  function getClickEventProperties(target) {
@@ -19408,6 +19421,9 @@ var BuildingBlockResourceCenter = (function () {
19408
19421
  webWidgetWrapper(provider, 'messenger:on', 'unreadMessages', function (unreadCount) {
19409
19422
  updateNotificationBubbleCount(unreadCount, 'chat');
19410
19423
  });
19424
+ if (resourceCenter.isInProgress() && !resourceCenter.attributes.doNotResume) {
19425
+ handleNativeIntegrationContinuation(resourceCenter.activeModule);
19426
+ }
19411
19427
  }
19412
19428
  // Zendesk Native Chat API - https://developer.zendesk.com/embeddables/docs/widget/chat_api_migration
19413
19429
  function configureZendeskChatSettings(integrationObj) {
@@ -20399,17 +20415,18 @@ var GuideStateModule = (function () {
20399
20415
  updateLastGuideStepSeen(context, lastGuideStepSeen) {
20400
20416
  if (lastGuideStepSeen.visitorId && lastGuideStepSeen.visitorId !== context.getters.visitorId())
20401
20417
  return;
20402
- const isValidGuide = _.some(context.getters.guideList(), function ({ id }) {
20418
+ // Embedded guides should not update the lastGuideStepSeen in order to not interfere with auto guide display
20419
+ // Embedded guides are not included in getters.guideList
20420
+ const shouldUpdateLastGuideStepSeen = _.some(context.getters.guideList(), function ({ id }) {
20403
20421
  return id === lastGuideStepSeen.guideId;
20404
20422
  });
20405
20423
  if (lastGuideStepSeen.guideStepId) {
20406
20424
  context.commit('setStepState', lastGuideStepSeen);
20407
20425
  }
20408
- if (!isValidGuide) {
20409
- return;
20426
+ if (shouldUpdateLastGuideStepSeen) {
20427
+ context.commit('setLastGuideStepSeen', lastGuideStepSeen);
20428
+ pendo$1.lastGuideStepSeen = lastGuideStepSeen;
20410
20429
  }
20411
- context.commit('setLastGuideStepSeen', lastGuideStepSeen);
20412
- pendo$1.lastGuideStepSeen = lastGuideStepSeen;
20413
20430
  if (guideCache) {
20414
20431
  guideCache.update(lastGuideStepSeen);
20415
20432
  }
@@ -20967,22 +20984,13 @@ function continueOrCompleteUpdate() {
20967
20984
  * Otherwise, it is the same as calling startGuides.
20968
20985
  */
20969
20986
  var manuallyStartGuides = function () {
20970
- var originalOptions = ConfigReader.getLocalConfig();
20971
- if (ConfigReader.get('delayGuides')) {
20972
- delete originalOptions.delayGuides;
20973
- ConfigReader.setLocalConfig(originalOptions);
20974
- }
20975
- if (ConfigReader.get('guides.delay')) {
20976
- delete originalOptions.guides.delay;
20977
- ConfigReader.setLocalConfig(originalOptions);
20978
- }
20987
+ setGuidesDelayed(false);
20979
20988
  startGuides();
20980
20989
  };
20981
20990
  var manuallyStopGuides = function () {
20982
20991
  if (!areGuidesDisabled()) {
20983
20992
  setGuidesDelayed(true);
20984
20993
  }
20985
- stopGuides();
20986
20994
  resetPendoUI();
20987
20995
  };
20988
20996
  // a phase that only has one unit of work per update
@@ -21385,7 +21393,7 @@ class UpdateRunner {
21385
21393
  monitor.start();
21386
21394
  promise = currentPhase.process(monitor);
21387
21395
  if (!promise && monitor.isTimeExceeded()) {
21388
- _PerformanceMonitor.count(METRIC_GUIDELOOP_TIMEOUT);
21396
+ performanceMonitor.count(GUIDE_LOOP_TIMEOUT);
21389
21397
  if (currentPhase.handleProcessTimeExceeded) {
21390
21398
  currentPhase.handleProcessTimeExceeded(monitor);
21391
21399
  }
@@ -24247,30 +24255,40 @@ class GuideCache {
24247
24255
  if (this.db)
24248
24256
  return q.resolve(this.db);
24249
24257
  const deferred = q.defer();
24250
- const request = indexedDB.open(`pendo-${apiKey}`, 1);
24251
- request.onerror = () => {
24252
- log.warn('Error opening guide database');
24253
- this.cacheGuidesPersistent = false;
24254
- deferred.resolve();
24255
- };
24256
- request.onsuccess = (event) => {
24257
- const db = event.target.result;
24258
- this.db = db;
24259
- db.onclose = () => {
24260
- this.db = null;
24258
+ try {
24259
+ const request = indexedDB.open(`pendo-${apiKey}`, 1);
24260
+ request.onerror = () => {
24261
+ this.dbError(new Error('open.request.onerror'));
24262
+ deferred.resolve();
24261
24263
  };
24262
- deferred.resolve(db);
24263
- };
24264
- request.onupgradeneeded = (event) => {
24265
- const db = event.target.result;
24266
- const objectStore = db.createObjectStore('guides', { keyPath: 'guide.id' });
24267
- objectStore.transaction.oncomplete = () => {
24264
+ request.onsuccess = (event) => {
24265
+ const db = event.target.result;
24268
24266
  this.db = db;
24267
+ db.onclose = () => {
24268
+ this.db = null;
24269
+ };
24269
24270
  deferred.resolve(db);
24270
24271
  };
24271
- };
24272
+ request.onupgradeneeded = (event) => {
24273
+ const db = event.target.result;
24274
+ const objectStore = db.createObjectStore('guides', { keyPath: 'guide.id' });
24275
+ objectStore.transaction.oncomplete = () => {
24276
+ this.db = db;
24277
+ deferred.resolve(db);
24278
+ };
24279
+ };
24280
+ }
24281
+ catch (error) {
24282
+ this.dbError(error);
24283
+ deferred.resolve();
24284
+ }
24272
24285
  return deferred.promise;
24273
24286
  }
24287
+ dbError(error) {
24288
+ log.critical('indexedDB cache error', { error });
24289
+ this.cacheGuidesPersistent = false;
24290
+ this.db = null;
24291
+ }
24274
24292
  load(now = getNow()) {
24275
24293
  if (this.cache)
24276
24294
  return q.resolve(this.cache);
@@ -24278,26 +24296,30 @@ class GuideCache {
24278
24296
  if (!this.db)
24279
24297
  return q.resolve(this.cache);
24280
24298
  const deferred = q.defer();
24281
- const objectStore = this.db.transaction(['guides'], 'readwrite').objectStore('guides');
24282
- const request = objectStore.getAll();
24283
- request.onerror = () => {
24284
- log.warn('Error loading guide cache');
24285
- this.cacheGuidesPersistent = false;
24286
- this.db = null;
24287
- deferred.resolve(this.cache);
24288
- };
24289
- request.onsuccess = (event) => {
24290
- const cachedGuides = event.target.result;
24291
- _.each(cachedGuides, (cachedGuide) => {
24292
- if (cachedGuide.expires > now) {
24293
- this.cache[cachedGuide.guide.id] = cachedGuide;
24294
- }
24295
- else {
24296
- objectStore.delete(cachedGuide.guide.id);
24297
- }
24298
- });
24299
+ try {
24300
+ const objectStore = this.db.transaction(['guides'], 'readwrite').objectStore('guides');
24301
+ const request = objectStore.getAll();
24302
+ request.onerror = () => {
24303
+ this.dbError(new Error('load.request.onerror'));
24304
+ deferred.resolve(this.cache);
24305
+ };
24306
+ request.onsuccess = (event) => {
24307
+ const cachedGuides = event.target.result;
24308
+ _.each(cachedGuides, (cachedGuide) => {
24309
+ if (cachedGuide.expires > now) {
24310
+ this.cache[cachedGuide.guide.id] = cachedGuide;
24311
+ }
24312
+ else {
24313
+ objectStore.delete(cachedGuide.guide.id);
24314
+ }
24315
+ });
24316
+ deferred.resolve(this.cache);
24317
+ };
24318
+ }
24319
+ catch (error) {
24320
+ this.dbError(error);
24299
24321
  deferred.resolve(this.cache);
24300
- };
24322
+ }
24301
24323
  return deferred.promise;
24302
24324
  }
24303
24325
  add(guide, visitorId, now = getNow()) {
@@ -24316,16 +24338,22 @@ class GuideCache {
24316
24338
  save(cachedGuide) {
24317
24339
  if (!this.db)
24318
24340
  return;
24319
- const objectStore = this.db.transaction(['guides'], 'readwrite').objectStore('guides');
24320
24341
  const deferred = q.defer();
24321
- const update = objectStore.put(cachedGuide);
24322
- update.onerror = () => {
24323
- log.warn(`Error saving guide to cache ${cachedGuide.guide.id}`);
24324
- deferred.resolve();
24325
- };
24326
- update.onsuccess = () => {
24342
+ try {
24343
+ const objectStore = this.db.transaction(['guides'], 'readwrite').objectStore('guides');
24344
+ const update = objectStore.put(cachedGuide);
24345
+ update.onerror = () => {
24346
+ this.dbError(new Error('save.request.onerror'));
24347
+ deferred.resolve();
24348
+ };
24349
+ update.onsuccess = () => {
24350
+ deferred.resolve();
24351
+ };
24352
+ }
24353
+ catch (error) {
24354
+ this.dbError(error);
24327
24355
  deferred.resolve();
24328
- };
24356
+ }
24329
24357
  return deferred.promise;
24330
24358
  }
24331
24359
  update(lastGuideStepSeen) {
@@ -25894,13 +25922,12 @@ var loadGuides = function (apiKey, visitorId, page, callback) {
25894
25922
  }
25895
25923
  if (displayableGuides.length) {
25896
25924
  q.all([
25897
- loadGuideCss(),
25898
25925
  globalJsPromise,
25899
25926
  initializeResourceCenter(activeGuides),
25900
25927
  BuildingBlockWatermark.initializeWatermark(activeGuides),
25901
25928
  waitForGlobalCssToLoad(5000)
25902
25929
  ]
25903
- .concat(getRegisteredLoadGuideJobs(activeGuides))).then(function () {
25930
+ .concat(getRegisteredLoadGuideJobs(activeGuides), loadGuideCss())).then(function () {
25904
25931
  if (loadGuides.reqId === reqId) {
25905
25932
  if (store.getters['frames/isLeader']()) {
25906
25933
  restoreGuideShownState(getActiveGuides());
@@ -25914,7 +25941,8 @@ var loadGuides = function (apiKey, visitorId, page, callback) {
25914
25941
  }
25915
25942
  deferred.resolve();
25916
25943
  }, function (err) {
25917
- log.error('Post loadGuide request failed: ', err);
25944
+ log.critical(`Post loadGuide request failed: ${err}`);
25945
+ resetPendoUI();
25918
25946
  Events.guidesFailed.trigger();
25919
25947
  deferred.reject();
25920
25948
  });
@@ -25969,6 +25997,9 @@ function loadExternalCss(id, cssUrl) {
25969
25997
  var style = pendo$1.loadResource(cssUrl, function () {
25970
25998
  deferred.resolve();
25971
25999
  });
26000
+ if (_.isEmpty(style)) {
26001
+ return q.reject(`Failed to load CSS resource: ${cssUrl}. Check that the URL is correct and from an allowed origin.`);
26002
+ }
25972
26003
  style.id = id;
25973
26004
  return deferred.promise;
25974
26005
  }
@@ -26047,7 +26078,7 @@ function loadGuideCss() {
26047
26078
  else {
26048
26079
  dom('#' + CUSTOM_CSS_ID).remove();
26049
26080
  }
26050
- return q.all(promises);
26081
+ return promises;
26051
26082
  }
26052
26083
  var processGuideEventCache = function (options) {
26053
26084
  if (!guideEventQueue)
@@ -26199,14 +26230,6 @@ var initGuides = function (observer) {
26199
26230
  teardownFns.push(attachEventInternal(window, 'securitypolicyviolation', securityPolicyViolationFn));
26200
26231
  teardownFns.push(createVideoFullScreenListeners());
26201
26232
  if (observer.observing) {
26202
- var updateGuide = function () {
26203
- store.dispatch('guideUpdate/documentChanged');
26204
- };
26205
- const debouncedUpdate = _.debounce(updateGuide, 50);
26206
- teardownFns.push(() => { debouncedUpdate.cancel(); });
26207
- teardownFns.push(attachEvent(window, 'animationend', debouncedUpdate));
26208
- teardownFns.push(attachEvent(window, 'transitionend', debouncedUpdate));
26209
- teardownFns.push(attachEvent(window, 'mouseover', debouncedUpdate));
26210
26233
  store.commit('guideUpdate/setObserver', observer);
26211
26234
  store.commit('guideUpdate/setUseObserver');
26212
26235
  teardownFns.push(() => store.dispatch('guideUpdate/stopObserver'));
@@ -26297,6 +26320,10 @@ var setGuidesDisabled = function (areDisabled) {
26297
26320
  };
26298
26321
  var setGuidesDelayed = function (areDelayed) {
26299
26322
  var originalOptions = ConfigReader.getLocalConfig();
26323
+ delete originalOptions.delayGuides;
26324
+ if (originalOptions.guides) {
26325
+ delete originalOptions.guides.delay;
26326
+ }
26300
26327
  originalOptions.delayGuides = areDelayed;
26301
26328
  ConfigReader.setLocalConfig(originalOptions);
26302
26329
  };
@@ -26553,10 +26580,34 @@ const PENDO_HEADERS_KEY = '_pendoHeaders';
26553
26580
  function getTransformedUrl(url) {
26554
26581
  return store.getters['networkUrl/getTransformedUrl']()(url);
26555
26582
  }
26583
+ const HEADERS = 'headers';
26584
+ const DENIED_HEADERS = {
26585
+ 'authorization': true,
26586
+ 'cookie': true,
26587
+ 'set-cookie': true,
26588
+ 'x-api-key': true,
26589
+ 'x-forwarded-for': true,
26590
+ 'proxy-authorization': true,
26591
+ 'signature': true,
26592
+ 'forwarded': true,
26593
+ 'referrer': true
26594
+ };
26595
+ const XSRF = 'xsrf';
26596
+ const CSRF = 'csrf';
26597
+ const MAX_RESPONSE_SIZE = 1024 * 1024 * 2; // 2MB
26598
+ const MAX_RESPONSE_SIZE_MESSAGE = '[Body too large to capture (over 2 MB)]';
26556
26599
  class NetworkRequestIntercept {
26557
- entriesToObject(entries) {
26600
+ // Headers that should never be captured by pendo for PII/security reasons.
26601
+ isDeniedHeader(key) {
26602
+ const normalizedKey = key.toLowerCase();
26603
+ return DENIED_HEADERS[normalizedKey] || normalizedKey.indexOf(CSRF) !== -1 || normalizedKey.indexOf(XSRF) !== -1;
26604
+ }
26605
+ entriesToObject(entries, type) {
26558
26606
  const obj = {};
26559
26607
  for (const [key, value] of entries) {
26608
+ if (type === HEADERS && this.isDeniedHeader(key)) {
26609
+ continue;
26610
+ }
26560
26611
  obj[key] = value;
26561
26612
  }
26562
26613
  return obj;
@@ -26580,11 +26631,14 @@ class NetworkRequestIntercept {
26580
26631
  extractHeaders(headers = {}) {
26581
26632
  let result = {};
26582
26633
  if (headers instanceof Headers) {
26583
- result = this.entriesToObject(headers.entries());
26634
+ result = this.entriesToObject(headers.entries(), HEADERS);
26584
26635
  }
26585
26636
  else if (typeof headers === 'object') {
26586
26637
  for (const key in headers) {
26587
26638
  if (headers.hasOwnProperty(key)) {
26639
+ if (this.isDeniedHeader(key)) {
26640
+ continue;
26641
+ }
26588
26642
  result[key] = headers[key];
26589
26643
  }
26590
26644
  }
@@ -26601,17 +26655,25 @@ class NetworkRequestIntercept {
26601
26655
  safelyReadBody(config, type) {
26602
26656
  return __awaiter(this, void 0, void 0, function* () {
26603
26657
  try {
26604
- const contentType = config.headers.get('content-type') || '';
26605
- if (contentType.indexOf('application/json') !== -1) {
26606
- return yield config.json();
26607
- }
26608
- else if (contentType.indexOf('text/') !== -1) {
26609
- return yield config.text();
26658
+ const contentType = config.headers.get('content-type') || 'unknown';
26659
+ const contentLengthHeader = config.headers.get('content-length');
26660
+ // Check if content type is readable (text or JSON)
26661
+ const isReadableContent = contentType.indexOf('text/') !== -1 || contentType.indexOf('application/json') !== -1;
26662
+ if (!isReadableContent) {
26663
+ const typeDisplay = contentType;
26664
+ return `[Binary content: ${typeDisplay}]`;
26665
+ }
26666
+ if (contentLengthHeader) {
26667
+ const size = parseInt(contentLengthHeader, 10);
26668
+ if (size > MAX_RESPONSE_SIZE) {
26669
+ return MAX_RESPONSE_SIZE_MESSAGE;
26670
+ }
26610
26671
  }
26611
- else {
26612
- // For binary or unknown content types, just capture metadata
26613
- return `[Binary content: ${contentType}]`;
26672
+ const text = yield config.text();
26673
+ if (text.length > MAX_RESPONSE_SIZE) {
26674
+ return MAX_RESPONSE_SIZE_MESSAGE;
26614
26675
  }
26676
+ return text;
26615
26677
  }
26616
26678
  catch (e) {
26617
26679
  return `[Unable to read ${type} body]`;
@@ -26622,7 +26684,7 @@ class NetworkRequestIntercept {
26622
26684
  let headers = {};
26623
26685
  if (config.headers) {
26624
26686
  const headerEntries = Array.from(config.headers.entries());
26625
- headers = this.entriesToObject(headerEntries);
26687
+ headers = this.entriesToObject(headerEntries, HEADERS);
26626
26688
  }
26627
26689
  return headers;
26628
26690
  }
@@ -26793,7 +26855,8 @@ class NetworkRequestIntercept {
26793
26855
  if (xhr.readyState === 4) { // Request completed
26794
26856
  try {
26795
26857
  const headers = networkInterceptor.parseXHRResponseHeaders(xhr.getAllResponseHeaders());
26796
- const { status, statusText, responseText: body, _url } = xhr;
26858
+ const { status, statusText, _url } = xhr;
26859
+ const body = networkInterceptor.safelyReadXHRResponse(xhr, headers);
26797
26860
  _.each(networkInterceptor.callbackFns, ({ response }) => {
26798
26861
  if (_.isFunction(response)) {
26799
26862
  response({
@@ -26822,6 +26885,21 @@ class NetworkRequestIntercept {
26822
26885
  return networkInterceptor._originalXHRSend.apply(this, arguments);
26823
26886
  };
26824
26887
  }
26888
+ safelyReadXHRResponse(xhr, headers) {
26889
+ const networkInterceptor = this;
26890
+ const size = networkInterceptor.estimateResponseSize(headers['content-length'], xhr.responseText);
26891
+ if (size > MAX_RESPONSE_SIZE) {
26892
+ return MAX_RESPONSE_SIZE_MESSAGE;
26893
+ }
26894
+ return xhr.responseText;
26895
+ }
26896
+ estimateResponseSize(contentLength, responseText) {
26897
+ const parsedContentLength = parseInt(contentLength, 10);
26898
+ if (!_.isNumber(parsedContentLength) || isNaN(parsedContentLength)) {
26899
+ return responseText.length;
26900
+ }
26901
+ return parsedContentLength;
26902
+ }
26825
26903
  patchNetwork() {
26826
26904
  const networkInterceptor = this;
26827
26905
  if (networkInterceptor._networkPatched)
@@ -28357,7 +28435,7 @@ const initialize = makeSafe(function (options) {
28357
28435
  Events.appUsage.on(GuideActivity.handler);
28358
28436
  Events.appUsage.on(ResourceCenterActivity.handler);
28359
28437
  teardownFns.push(flushEvery(SEND_INTERVAL));
28360
- teardownFns.push(_PerformanceMonitor.initialize());
28438
+ teardownFns.push(performanceMonitor.initialize());
28361
28439
  }
28362
28440
  Events.appHidden.on(() => {
28363
28441
  flushNow(true, { hidden: true });
@@ -29398,8 +29476,10 @@ function track(name, props) {
29398
29476
  collectEventHelper({ type: 'track', name, props });
29399
29477
  }
29400
29478
 
29479
+ const privacyFilterCache = new Map();
29401
29480
  /**
29402
29481
  * Method to manually track agentic events. Uses agent functions for IDs and context.
29482
+ * Automatically applies privacy filters if agentId is provided and agent configuration exists.
29403
29483
  *
29404
29484
  * @access public
29405
29485
  * @category Events
@@ -29418,8 +29498,64 @@ function track(name, props) {
29418
29498
  * })
29419
29499
  */
29420
29500
  function trackAgent(type, props) {
29501
+ const filteredTypes = ['prompt', 'system_response'];
29502
+ if (_.contains(filteredTypes, type)) {
29503
+ props = applyPrivacyFilters(props);
29504
+ }
29421
29505
  collectEventHelper({ type, name: 'agentic', props });
29422
29506
  }
29507
+ /**
29508
+ * Apply privacy filters to event properties based on agent configuration
29509
+ * Optimized for maximum performance with caching
29510
+ * @param {Object} props - Event properties to filter
29511
+ * @returns {Object} - Filtered properties
29512
+ */
29513
+ function applyPrivacyFilters(props) {
29514
+ if (!(props === null || props === void 0 ? void 0 : props.agentId)) {
29515
+ return props;
29516
+ }
29517
+ try {
29518
+ const agents = ConfigReader.get('aiAgents', []);
29519
+ const agent = (agents === null || agents === void 0 ? void 0 : agents.find(agent => agent.id === props.agentId)) || null;
29520
+ const content = props.content;
29521
+ if (!content) {
29522
+ return props;
29523
+ }
29524
+ if (!(agent === null || agent === void 0 ? void 0 : agent.privacyFilters)) {
29525
+ return props;
29526
+ }
29527
+ const privacyFilter = getCachedRegex(agent.privacyFilters);
29528
+ if (!privacyFilter) {
29529
+ return props;
29530
+ }
29531
+ const filteredContent = content.replace(privacyFilter, '<redacted>');
29532
+ if (filteredContent === content) {
29533
+ return props;
29534
+ }
29535
+ return Object.assign(Object.assign({}, props), { content: filteredContent });
29536
+ }
29537
+ catch (error) {
29538
+ console.error('Error applying privacy filters', error);
29539
+ return props;
29540
+ }
29541
+ }
29542
+ /**
29543
+ * Get cached regex or compile and cache new one
29544
+ */
29545
+ function getCachedRegex(filterPattern) {
29546
+ let regex = privacyFilterCache.get(filterPattern);
29547
+ if (!regex) {
29548
+ try {
29549
+ regex = new RegExp(filterPattern, 'gmi');
29550
+ privacyFilterCache.set(filterPattern, regex);
29551
+ }
29552
+ catch (e) {
29553
+ privacyFilterCache.set(filterPattern, null);
29554
+ return null;
29555
+ }
29556
+ }
29557
+ return regex;
29558
+ }
29423
29559
 
29424
29560
  /**
29425
29561
  * Checks visitor and account metadata in the current Pendo installation. Either logs to console
@@ -33341,6 +33477,7 @@ var GuideUpdateModule = (function () {
33341
33477
  function observerCallback() {
33342
33478
  store.dispatch('guideUpdate/documentChanged');
33343
33479
  }
33480
+ const debouncedCallback = _.debounce(observerCallback, 50);
33344
33481
  function handleScheduledUpdate() {
33345
33482
  store.dispatch('guideUpdate/handleScheduledUpdate');
33346
33483
  }
@@ -33381,6 +33518,9 @@ var GuideUpdateModule = (function () {
33381
33518
  if (!context.state.observing) {
33382
33519
  const observer = context.getters.observer();
33383
33520
  observer.addEventListener('mutation', observerCallback);
33521
+ attachEvent(window, 'animationend', debouncedCallback);
33522
+ attachEvent(window, 'transitionend', debouncedCallback);
33523
+ attachEvent(window, 'mouseover', debouncedCallback);
33384
33524
  context.commit('setObserving', true);
33385
33525
  }
33386
33526
  }
@@ -33389,6 +33529,10 @@ var GuideUpdateModule = (function () {
33389
33529
  const observer = context.getters.observer();
33390
33530
  if (observer) {
33391
33531
  observer.removeEventListener('mutation', observerCallback);
33532
+ debouncedCallback.cancel();
33533
+ detachEvent(window, 'animationend', debouncedCallback);
33534
+ detachEvent(window, 'transitionend', debouncedCallback);
33535
+ detachEvent(window, 'mouseover', debouncedCallback);
33392
33536
  }
33393
33537
  context.commit('setObserving', false);
33394
33538
  context.dispatch('stopScheduledUpdate');
@@ -33411,12 +33555,12 @@ var GuideUpdateModule = (function () {
33411
33555
  state.scheduledUpdate = scheduledUpdate;
33412
33556
  },
33413
33557
  startUpdate(state, time) {
33414
- _PerformanceMonitor.startTimer('guide-loop');
33558
+ performanceMonitor.startTimer(GUIDE_LOOP_TIMER);
33415
33559
  state.needsUpdate = false;
33416
33560
  state.updateId = time;
33417
33561
  },
33418
33562
  completeUpdate(state, time) {
33419
- _PerformanceMonitor.stopTimer('guide-loop');
33563
+ performanceMonitor.stopTimer(GUIDE_LOOP_TIMER);
33420
33564
  state.updateId = null;
33421
33565
  state.updateCompleteTime = time;
33422
33566
  }
@@ -38201,7 +38345,7 @@ const EmbeddedGuides = (function () {
38201
38345
  return { guide, step };
38202
38346
  }
38203
38347
  function initializeEmbeddedGuides() {
38204
- restoreFromPreviouseGuides();
38348
+ restoreFromPreviousGuides();
38205
38349
  pluginApi.guides.registerDisplayableGuides('embeddedGuides', embeddedGuides);
38206
38350
  applyEmbeddedGuideBehaviors();
38207
38351
  }
@@ -38216,7 +38360,7 @@ const EmbeddedGuides = (function () {
38216
38360
  // When a guides payload is received, the embedded guides list is cleared and cached in _oldEmbeddedGuides.
38217
38361
  // This function replaces guides in the new payload with guides from the previous payload to prevent re-rendering.
38218
38362
  // Additionally, if guides from the previous payload are showing, and they are not in the new payload, this function hides them.
38219
- function restoreFromPreviouseGuides() {
38363
+ function restoreFromPreviousGuides() {
38220
38364
  _.each(_oldEmbeddedGuides, function (oldGuide) {
38221
38365
  const newGuideIndex = _.findIndex(embeddedGuides, function (guide) {
38222
38366
  return guide.id === oldGuide.id;
@@ -39681,7 +39825,7 @@ class PromptPlugin {
39681
39825
  promptType: method,
39682
39826
  url,
39683
39827
  privacyFilterApplied: filteredPrompt !== originalBody
39684
- });
39828
+ }, undefined, 'agentic');
39685
39829
  });
39686
39830
  }
39687
39831
  handleError({ error, context }) {
@@ -39723,7 +39867,7 @@ class PromptPlugin {
39723
39867
  promptType: 'request',
39724
39868
  agentType: 'prompt'
39725
39869
  }, promptEvent);
39726
- this.api.analytics.collectEvent('prompt', event);
39870
+ this.api.analytics.collectEvent('prompt', event, undefined, 'agentic');
39727
39871
  }
39728
39872
  teardown() {
39729
39873
  this._.each(this.prompts, (prompt) => prompt.teardown());
@@ -44934,6 +45078,9 @@ class MutationBuffer {
44934
45078
  this.movedMap = {};
44935
45079
  this.mutationCb(payload);
44936
45080
  });
45081
+ __publicField(this, "bufferBelongsToIframe", (iframeEl) => {
45082
+ return this.doc === iframeEl.contentDocument;
45083
+ });
44937
45084
  __publicField(this, "genTextAreaValueMutation", (textarea) => {
44938
45085
  let item = this.attributeMap.get(textarea);
44939
45086
  if (!item) {
@@ -45501,6 +45648,15 @@ function initViewportResizeObserver({ viewportResizeCb }, { win }) {
45501
45648
  );
45502
45649
  return on("resize", updateDimension, win);
45503
45650
  }
45651
+ function findAndRemoveIframeBuffer(iframeEl) {
45652
+ for (let i2 = mutationBuffers.length - 1; i2 >= 0; i2--) {
45653
+ const buf = mutationBuffers[i2];
45654
+ if (buf.bufferBelongsToIframe(iframeEl)) {
45655
+ buf.reset();
45656
+ mutationBuffers.splice(i2, 1);
45657
+ }
45658
+ }
45659
+ }
45504
45660
  const INPUT_TAGS = ["INPUT", "TEXTAREA", "SELECT"];
45505
45661
  const lastInputValueMap = /* @__PURE__ */ new WeakMap();
45506
45662
  function initInputObserver({
@@ -46347,6 +46503,7 @@ class IframeManager {
46347
46503
  __publicField(this, "wrappedEmit");
46348
46504
  __publicField(this, "takeFullSnapshot");
46349
46505
  __publicField(this, "loadListener");
46506
+ __publicField(this, "pageHideListener");
46350
46507
  __publicField(this, "stylesheetManager");
46351
46508
  __publicField(this, "recordCrossOriginIframes");
46352
46509
  this.mutationCb = options.mutationCb;
@@ -46384,6 +46541,9 @@ class IframeManager {
46384
46541
  addLoadListener(cb) {
46385
46542
  this.loadListener = cb;
46386
46543
  }
46544
+ addPageHideListener(cb) {
46545
+ this.pageHideListener = cb;
46546
+ }
46387
46547
  attachIframe(iframeEl, childSn) {
46388
46548
  var _a2, _b, _c;
46389
46549
  this.mutationCb({
@@ -46405,6 +46565,9 @@ class IframeManager {
46405
46565
  this.handleMessage.bind(this)
46406
46566
  );
46407
46567
  (_b = iframeEl.contentWindow) == null ? void 0 : _b.addEventListener("pagehide", () => {
46568
+ var _a3;
46569
+ (_a3 = this.pageHideListener) == null ? void 0 : _a3.call(this, iframeEl);
46570
+ this.mirror.removeNodeFromMap(iframeEl.contentDocument);
46408
46571
  this.crossOriginIframeMap.delete(iframeEl.contentWindow);
46409
46572
  });
46410
46573
  }
@@ -47618,6 +47781,7 @@ function record(options = {}) {
47618
47781
  iframeManager.setTakeFullSnapshot(takeFullSnapshot$1);
47619
47782
  try {
47620
47783
  const handlers = [];
47784
+ const iframeHandlersMap = /* @__PURE__ */ new Map();
47621
47785
  const observe = (doc) => {
47622
47786
  var _a2;
47623
47787
  return callbackWrapper(initObservers)(
@@ -47743,11 +47907,19 @@ function record(options = {}) {
47743
47907
  };
47744
47908
  iframeManager.addLoadListener((iframeEl) => {
47745
47909
  try {
47746
- handlers.push(observe(iframeEl.contentDocument));
47910
+ iframeHandlersMap.set(iframeEl, observe(iframeEl.contentDocument));
47747
47911
  } catch (error) {
47748
47912
  console.warn(error);
47749
47913
  }
47750
47914
  });
47915
+ iframeManager.addPageHideListener((iframeEl) => {
47916
+ const iframeHandler = iframeHandlersMap.get(iframeEl);
47917
+ if (iframeHandler) {
47918
+ iframeHandler();
47919
+ iframeHandlersMap.delete(iframeEl);
47920
+ }
47921
+ findAndRemoveIframeBuffer(iframeEl);
47922
+ });
47751
47923
  const init = () => {
47752
47924
  takeFullSnapshot$1();
47753
47925
  handlers.push(observe(document));
@@ -47781,6 +47953,7 @@ function record(options = {}) {
47781
47953
  }
47782
47954
  return () => {
47783
47955
  handlers.forEach((h) => h());
47956
+ iframeHandlersMap.forEach((h) => h());
47784
47957
  processedNodeManager.destroy();
47785
47958
  recording = false;
47786
47959
  unregisterErrorHandler();
@@ -54690,9 +54863,7 @@ const ErrorStackParser = {
54690
54863
  // if a location was matched, pass it to extractLocation() otherwise pop the last token
54691
54864
  const locationParts = this.extractLocation(location ? location[1] : tokens.pop() || '');
54692
54865
  const functionName = tokens.join(' ') || undefined;
54693
- const fileName = ['eval', '<anonymous>'].indexOf(locationParts[0]) > -1
54694
- ? undefined
54695
- : locationParts[0];
54866
+ const fileName = (locationParts[0] === 'eval' || locationParts[0] === '<anonymous>') ? undefined : locationParts[0];
54696
54867
  frames.push(new StackFrame({
54697
54868
  functionName,
54698
54869
  fileName,
@@ -54734,17 +54905,29 @@ const PII_PATTERN = {
54734
54905
  ip: /\b(?:\d{1,3}\.){3}\d{1,3}\b/g,
54735
54906
  ssn: /\b\d{3}-\d{2}-\d{4}\b/g,
54736
54907
  creditCard: /\b(?:\d[ -]*?){13,16}\b/g,
54737
- httpsUrls: /https:\/\/[^\s]+/g
54908
+ httpsUrls: /https:\/\/[^\s]+/g,
54909
+ email: /\b\S+@[\w-]+\.[\w-]+\b/g
54738
54910
  };
54739
54911
  const PII_REPLACEMENT = '*'.repeat(10);
54912
+ const JSON_PII_KEYS = ['password', 'email', 'key', 'token', 'auth', 'authentication', 'phone', 'address', 'ssn'];
54913
+ const UNABLE_TO_DISPLAY_BODY = '[Unable to display body: detected PII could not be redacted]';
54914
+ const joinedKeys = JSON_PII_KEYS.join('|');
54915
+ const keyPattern = `"([^"]*(?:${joinedKeys})[^"]*)"`;
54916
+ // only scrub strings, numbers, and booleans
54917
+ const acceptedValues = '("([^"\\\\]*(?:\\\\.[^"\\\\]*)*")|(\\d+)|(true|false))';
54740
54918
  /**
54741
54919
  * Truncates a string to the max length
54742
54920
  * @access private
54743
54921
  * @param {String} string
54744
54922
  * @returns {String}
54745
54923
  */
54746
- function truncate(string) {
54747
- return string.length > MAX_LENGTH ? string.slice(0, MAX_LENGTH) : string;
54924
+ function truncate(string, includeEllipsis = false) {
54925
+ if (string.length <= MAX_LENGTH)
54926
+ return string;
54927
+ if (includeEllipsis) {
54928
+ return string.slice(0, MAX_LENGTH).concat('...');
54929
+ }
54930
+ return string.slice(0, MAX_LENGTH);
54748
54931
  }
54749
54932
  /**
54750
54933
  * Generate a log key for a console log separated by pipes
@@ -54758,13 +54941,13 @@ function generateLogKey(methodName, message, stackTrace) {
54758
54941
  return `${methodName}|${message}|${stackTrace}`;
54759
54942
  }
54760
54943
  /**
54761
- * Checks if a string has 2 or more digits or a https URL
54944
+ * Checks if a string has 2 or more digits, a https URL, or an email address
54762
54945
  * @access private
54763
54946
  * @param {String} string
54764
54947
  * @returns {Boolean}
54765
54948
  */
54766
54949
  function mightContainPII(string) {
54767
- return /[\d]{2,}|https:\/\//.test(string);
54950
+ return /[\d]{2,}|https:\/\/|@/.test(string);
54768
54951
  }
54769
54952
  /**
54770
54953
  * Scrub basic PII from a string and replace it with a placeholder
@@ -54772,12 +54955,60 @@ function mightContainPII(string) {
54772
54955
  * @param {String} string
54773
54956
  * @returns {String}
54774
54957
  */
54775
- function scrubPII(string) {
54958
+ function scrubPII({ string, _ }) {
54776
54959
  if (!string || typeof string !== 'string')
54777
54960
  return string;
54778
54961
  if (!mightContainPII(string))
54779
54962
  return string;
54780
- return Object.values(PII_PATTERN).reduce((str, pattern) => str.replace(pattern, PII_REPLACEMENT), string);
54963
+ return _.reduce(_.values(PII_PATTERN), (str, pattern) => str.replace(pattern, PII_REPLACEMENT), string);
54964
+ }
54965
+ /**
54966
+ * Scrub PII from a stringified JSON object
54967
+ * @access private
54968
+ * @param {String} string
54969
+ * @returns {String}
54970
+ */
54971
+ function scrubJsonPII(string) {
54972
+ const fullScrubRegex = new RegExp(`${keyPattern}\\s*:\\s*[\\{\\[]`, 'i');
54973
+ if (fullScrubRegex.test(string)) {
54974
+ return UNABLE_TO_DISPLAY_BODY;
54975
+ }
54976
+ const keyValueScrubRegex = new RegExp(`${keyPattern}\\s*:\\s*${acceptedValues}`, 'gi');
54977
+ return string.replace(keyValueScrubRegex, (match, key) => `"${key}":"${PII_REPLACEMENT}"`);
54978
+ }
54979
+ /**
54980
+ * Checks if a string is a JSON object
54981
+ * @access private
54982
+ * @param {String} string
54983
+ * @param {String} contentType
54984
+ * @returns {Boolean}
54985
+ */
54986
+ function mightContainJson(string, contentType) {
54987
+ if (!string || typeof string !== 'string')
54988
+ return false;
54989
+ const firstChar = string.charAt(0);
54990
+ if (contentType && contentType.indexOf('application/json') !== -1) {
54991
+ return true;
54992
+ }
54993
+ return firstChar === '{' || firstChar === '[';
54994
+ }
54995
+ /**
54996
+ * Mask sensitive fields in a string
54997
+ * @access private
54998
+ * @param {String} string
54999
+ * @param {String} contentType
55000
+ * @returns {String}
55001
+ */
55002
+ function maskSensitiveFields({ string, contentType, _ }) {
55003
+ if (!string || typeof string !== 'string')
55004
+ return string;
55005
+ if (mightContainJson(string, contentType)) {
55006
+ string = scrubJsonPII(string);
55007
+ }
55008
+ if (mightContainPII(string)) {
55009
+ string = scrubPII({ string, _ });
55010
+ }
55011
+ return string;
54781
55012
  }
54782
55013
 
54783
55014
  /**
@@ -54952,11 +55183,10 @@ function createCspViolationMessage(blockedURI, directive, isReportOnly, original
54952
55183
  }
54953
55184
  }
54954
55185
 
54955
- const DEV_LOG_LEVELS = ['info', 'warn', 'error'];
54956
55186
  const TOKEN_MAX_SIZE = 100;
54957
55187
  const TOKEN_REFILL_RATE = 10;
54958
55188
  const TOKEN_REFRESH_INTERVAL = 1000;
54959
- class ConsoleCaptureBuffer {
55189
+ class DevlogBuffer {
54960
55190
  constructor(pendo, pluginAPI) {
54961
55191
  this.pendo = pendo;
54962
55192
  this.pluginAPI = pluginAPI;
@@ -54985,9 +55215,6 @@ class ConsoleCaptureBuffer {
54985
55215
  this.lastEvent = null;
54986
55216
  }
54987
55217
  push(event) {
54988
- const { devLogLevel } = event;
54989
- if (!this.pendo._.contains(DEV_LOG_LEVELS, devLogLevel))
54990
- return false;
54991
55218
  this.refillTokens();
54992
55219
  if (this.tokens === 0)
54993
55220
  return false;
@@ -55804,7 +56031,6 @@ var ConfigReader = (function () {
55804
56031
  * @type {boolean}
55805
56032
  */
55806
56033
  addOption('enableDebugEvents', [SNIPPET_SRC, PENDO_CONFIG_SRC]);
55807
- addOption('freeNPSData');
55808
56034
  addOption('eventPropertyConfigurations');
55809
56035
  /**
55810
56036
  * By default, a click event property rule can be matched against the element that is clicked on or its
@@ -56192,7 +56418,9 @@ var ConfigReader = (function () {
56192
56418
  addOption('enableAllEmbeddedGuideEvents', [PENDO_CONFIG_SRC], false);
56193
56419
  // Form Validation
56194
56420
  addOption('formValidation', [PENDO_CONFIG_SRC], false);
56421
+ // Performance Metrics
56195
56422
  addOption('performanceMetricsEnabled', [SNIPPET_SRC, PENDO_CONFIG_SRC], true);
56423
+ addOption('sendPerformanceMetrics', [SNIPPET_SRC, PENDO_CONFIG_SRC], false);
56196
56424
  }
56197
56425
  initializeOptions();
56198
56426
  var sourceGetters = {};
@@ -56981,7 +57209,7 @@ else {
56981
57209
  var SEND_INTERVAL = 2 * 60 * 1000;
56982
57210
  var ENCODED_EVENT_MAX_LENGTH = 1900;
56983
57211
 
56984
- class ConsoleTransport {
57212
+ class DevlogTransport {
56985
57213
  constructor(globalPendo, pluginAPI) {
56986
57214
  this.pendo = globalPendo;
56987
57215
  this.pluginAPI = pluginAPI;
@@ -57025,7 +57253,7 @@ class ConsoleTransport {
57025
57253
  }
57026
57254
  else if (options.keepalive && this.pluginAPI.transmit.sendBeacon.supported()) {
57027
57255
  const result = this.pluginAPI.transmit.sendBeacon(url, new Blob([options.body]));
57028
- return result ? Promise$2.resolve() : Promise$2.reject(new Error('sendBeacon failed to send console data'));
57256
+ return result ? Promise$2.resolve() : Promise$2.reject(new Error('sendBeacon failed to send devlog data'));
57029
57257
  }
57030
57258
  else {
57031
57259
  return this.pendo.ajax.post(url, options.body);
@@ -57060,6 +57288,17 @@ class ConsoleTransport {
57060
57288
  }
57061
57289
  }
57062
57290
 
57291
+ const DEV_LOG_TYPE = 'devlog';
57292
+ function createDevLogEnvelope(pluginAPI, globalPendo) {
57293
+ return {
57294
+ browser_time: pluginAPI.util.getNow(),
57295
+ url: globalPendo.url.get(),
57296
+ visitorId: globalPendo.get_visitor_id(),
57297
+ accountId: globalPendo.get_account_id(),
57298
+ type: DEV_LOG_TYPE
57299
+ };
57300
+ }
57301
+
57063
57302
  function ConsoleCapture() {
57064
57303
  let pluginAPI;
57065
57304
  let _;
@@ -57071,7 +57310,6 @@ function ConsoleCapture() {
57071
57310
  let isPtmPaused;
57072
57311
  const CAPTURE_CONSOLE_CONFIG = 'captureConsoleLogs';
57073
57312
  const CONSOLE_METHODS = ['log', 'warn', 'error', 'info'];
57074
- const DEV_LOG_TYPE = 'devlog';
57075
57313
  const DEV_LOG_SUB_TYPE = 'console';
57076
57314
  // deduplicate logs
57077
57315
  let lastLogKey = '';
@@ -57107,8 +57345,8 @@ function ConsoleCapture() {
57107
57345
  if (!captureConsoleEnabled)
57108
57346
  return;
57109
57347
  globalPendo = pendo;
57110
- buffer = new ConsoleCaptureBuffer(pendo, pluginAPI);
57111
- transport = new ConsoleTransport(pendo, pluginAPI);
57348
+ buffer = new DevlogBuffer(pendo, pluginAPI);
57349
+ transport = new DevlogTransport(pendo, pluginAPI);
57112
57350
  sendQueue = new SendQueue(transport.sendRequest.bind(transport));
57113
57351
  sendInterval = setInterval(() => {
57114
57352
  if (!sendQueue.failed()) {
@@ -57141,16 +57379,6 @@ function ConsoleCapture() {
57141
57379
  send();
57142
57380
  }
57143
57381
  }
57144
- function createDevLogEnvelope() {
57145
- return {
57146
- visitorId: globalPendo.get_visitor_id(),
57147
- accountId: globalPendo.get_account_id(),
57148
- type: DEV_LOG_TYPE,
57149
- subType: DEV_LOG_SUB_TYPE,
57150
- browser_time: pluginAPI.util.getNow(),
57151
- url: globalPendo.url.get()
57152
- };
57153
- }
57154
57382
  function readyHandler() {
57155
57383
  addIntercepts();
57156
57384
  pluginAPI.attachEventInternal(window, 'securitypolicyviolation', securityPolicyViolationFn);
@@ -57186,7 +57414,7 @@ function ConsoleCapture() {
57186
57414
  }
57187
57415
  }
57188
57416
  function createConsoleEvent(args, methodName, { skipStackTrace = false, skipScrubPII = false } = {}) {
57189
- if (!args || args.length === 0)
57417
+ if (!args || args.length === 0 || !_.contains(CONSOLE_METHODS, methodName))
57190
57418
  return;
57191
57419
  // stringify args
57192
57420
  let message = _.compact(_.map(args, arg => {
@@ -57216,8 +57444,8 @@ function ConsoleCapture() {
57216
57444
  buffer.lastEvent.devLogCount++;
57217
57445
  return;
57218
57446
  }
57219
- const devLogEnvelope = createDevLogEnvelope();
57220
- const consoleEvent = Object.assign(Object.assign({}, devLogEnvelope), { devLogLevel: methodName === 'log' ? 'info' : methodName, devLogMessage: skipScrubPII ? message : scrubPII(message), devLogTrace: stackTrace, devLogCount: 1 });
57447
+ const devLogEnvelope = createDevLogEnvelope(pluginAPI, globalPendo);
57448
+ const consoleEvent = Object.assign(Object.assign({}, devLogEnvelope), { subType: DEV_LOG_SUB_TYPE, devLogLevel: methodName === 'log' ? 'info' : methodName, devLogMessage: skipScrubPII ? message : scrubPII({ string: message, _ }), devLogTrace: stackTrace, devLogCount: 1 });
57221
57449
  const wasAccepted = buffer.push(consoleEvent);
57222
57450
  if (wasAccepted) {
57223
57451
  if (!isPtmPaused) {
@@ -57253,7 +57481,7 @@ function ConsoleCapture() {
57253
57481
  v: globalPendo.VERSION,
57254
57482
  ct: pluginAPI.util.getNow()
57255
57483
  };
57256
- const url = pluginAPI.transmit.buildBaseDataUrl('devlog', globalPendo.apiKey, queryParams);
57484
+ const url = pluginAPI.transmit.buildBaseDataUrl(DEV_LOG_TYPE, globalPendo.apiKey, queryParams);
57257
57485
  const jzb = buffer.pack();
57258
57486
  if (!jzb)
57259
57487
  return;
@@ -57291,4 +57519,329 @@ function ConsoleCapture() {
57291
57519
  }
57292
57520
  }
57293
57521
 
57294
- export { ConsoleCapture, Feedback, GuideMarkdown, createReplayPlugin as Replay, TextCapture, VocPortal, loadAgent };
57522
+ function NetworkCapture() {
57523
+ let pluginAPI;
57524
+ let globalPendo;
57525
+ let requestMap = {};
57526
+ let buffer;
57527
+ let sendQueue;
57528
+ let sendInterval;
57529
+ let transport;
57530
+ let isPtmPaused;
57531
+ let requestBodyCb;
57532
+ let responseBodyCb;
57533
+ let pendoDevlogBaseUrl;
57534
+ const CAPTURE_NETWORK_CONFIG = 'captureNetworkRequests';
57535
+ const NETWORK_SUB_TYPE = 'network';
57536
+ const NETWORK_LOGS_CONFIG = 'networkLogs';
57537
+ const NETWORK_LOGS_CONFIG_ALLOWED_REQUEST_HEADERS = 'networkLogs.allowedRequestHeaders';
57538
+ const NETWORK_LOGS_CONFIG_ALLOWED_RESPONSE_HEADERS = 'networkLogs.allowedResponseHeaders';
57539
+ const NETWORK_LOGS_CONFIG_CAPTURE_REQUEST_BODY = 'networkLogs.captureRequestBody';
57540
+ const NETWORK_LOGS_CONFIG_CAPTURE_RESPONSE_BODY = 'networkLogs.captureResponseBody';
57541
+ const allowedRequestHeaders = {
57542
+ 'content-type': true, 'content-length': true, 'accept': true, 'accept-language': true
57543
+ };
57544
+ const allowedResponseHeaders = {
57545
+ 'cache-control': true, 'content-length': true, 'content-type': true, 'content-language': true
57546
+ };
57547
+ return {
57548
+ name: 'NetworkCapture',
57549
+ initialize,
57550
+ teardown,
57551
+ handleRequest,
57552
+ handleResponse,
57553
+ handleError,
57554
+ startCapture,
57555
+ createNetworkEvent,
57556
+ send,
57557
+ onPtmPaused,
57558
+ onPtmUnpaused,
57559
+ onAppHidden,
57560
+ onAppUnloaded,
57561
+ addConfigOptions,
57562
+ processHeaderConfig,
57563
+ extractHeaders,
57564
+ setupBodyCallbacks,
57565
+ processBody,
57566
+ processRequestBody,
57567
+ processResponseBody,
57568
+ get requestMap() {
57569
+ return requestMap;
57570
+ },
57571
+ get buffer() {
57572
+ return buffer;
57573
+ },
57574
+ get sendQueue() {
57575
+ return sendQueue;
57576
+ },
57577
+ get isPtmPaused() {
57578
+ return isPtmPaused;
57579
+ },
57580
+ get allowedRequestHeaders() {
57581
+ return allowedRequestHeaders;
57582
+ },
57583
+ get allowedResponseHeaders() {
57584
+ return allowedResponseHeaders;
57585
+ },
57586
+ get requestBodyCb() {
57587
+ return requestBodyCb;
57588
+ },
57589
+ get responseBodyCb() {
57590
+ return responseBodyCb;
57591
+ }
57592
+ };
57593
+ function initialize(pendo, PluginAPI) {
57594
+ pluginAPI = PluginAPI;
57595
+ globalPendo = pendo;
57596
+ const { ConfigReader } = pluginAPI;
57597
+ ConfigReader.addOption(CAPTURE_NETWORK_CONFIG, [ConfigReader.sources.PENDO_CONFIG_SRC], false);
57598
+ const captureNetworkEnabled = ConfigReader.get(CAPTURE_NETWORK_CONFIG);
57599
+ if (!captureNetworkEnabled)
57600
+ return;
57601
+ buffer = new DevlogBuffer(pendo, pluginAPI);
57602
+ transport = new DevlogTransport(pendo, pluginAPI);
57603
+ sendQueue = new SendQueue(transport.sendRequest.bind(transport));
57604
+ addConfigOptions();
57605
+ processHeaderConfig(NETWORK_LOGS_CONFIG_ALLOWED_REQUEST_HEADERS, allowedRequestHeaders);
57606
+ processHeaderConfig(NETWORK_LOGS_CONFIG_ALLOWED_RESPONSE_HEADERS, allowedResponseHeaders);
57607
+ setupBodyCallbacks();
57608
+ sendInterval = setInterval(() => {
57609
+ if (!sendQueue.failed()) {
57610
+ send();
57611
+ }
57612
+ }, SEND_INTERVAL);
57613
+ sendQueue.start();
57614
+ pluginAPI.Events.ready.on(startCapture);
57615
+ pluginAPI.Events['ptm:paused'].on(onPtmPaused);
57616
+ pluginAPI.Events['ptm:unpaused'].on(onPtmUnpaused);
57617
+ pluginAPI.Events.appHidden.on(onAppHidden);
57618
+ pluginAPI.Events.appUnloaded.on(onAppUnloaded);
57619
+ pendoDevlogBaseUrl = pluginAPI.transmit.buildBaseDataUrl(DEV_LOG_TYPE, globalPendo.apiKey);
57620
+ }
57621
+ function addConfigOptions() {
57622
+ const { ConfigReader } = pluginAPI;
57623
+ ConfigReader.addOption(NETWORK_LOGS_CONFIG, [ConfigReader.sources.SNIPPET_SRC], {});
57624
+ /**
57625
+ * Additional request headers to capture in network logs.
57626
+ * Default headers are: `['content-type', 'content-length', 'accept', 'accept-language']`
57627
+ *
57628
+ * @access public
57629
+ * @category Config/Network Logs
57630
+ * @name networkLogs.allowedRequestHeaders
57631
+ * @default []
57632
+ * @type {string[]}
57633
+ */
57634
+ ConfigReader.addOption(NETWORK_LOGS_CONFIG_ALLOWED_REQUEST_HEADERS, [ConfigReader.sources.SNIPPET_SRC], []);
57635
+ /**
57636
+ * Additional response headers to capture in network logs.
57637
+ * Default headers are: `['cache-control', 'content-length', 'content-type', 'content-language']`
57638
+ *
57639
+ * @access public
57640
+ * @category Config/Network Logs
57641
+ * @name networkLogs.allowedResponseHeaders
57642
+ * @default []
57643
+ * @type {string[]}
57644
+ */
57645
+ ConfigReader.addOption(NETWORK_LOGS_CONFIG_ALLOWED_RESPONSE_HEADERS, [ConfigReader.sources.SNIPPET_SRC], []);
57646
+ /**
57647
+ * Callback function to process/modify request bodies before capturing them. If not provided, the request body will not be captured.
57648
+ *
57649
+ * @access public
57650
+ * @category Config/Network Logs
57651
+ * @name networkLogs.captureRequestBody
57652
+ * @default undefined
57653
+ * @type {function}
57654
+ */
57655
+ ConfigReader.addOption(NETWORK_LOGS_CONFIG_CAPTURE_REQUEST_BODY, [ConfigReader.sources.SNIPPET_SRC], undefined);
57656
+ /**
57657
+ * Callback function to process/modify response bodies before capturing them. If not provided, the response body will not be captured.
57658
+ *
57659
+ * @access public
57660
+ * @category Config/Network Logs
57661
+ * @name networkLogs.captureResponseBody
57662
+ * @default undefined
57663
+ * @type {function}
57664
+ */
57665
+ ConfigReader.addOption(NETWORK_LOGS_CONFIG_CAPTURE_RESPONSE_BODY, [ConfigReader.sources.SNIPPET_SRC], undefined);
57666
+ }
57667
+ function processHeaderConfig(configHeaderName, targetHeaders) {
57668
+ const configHeaders = pluginAPI.ConfigReader.get(configHeaderName);
57669
+ if (!configHeaders || configHeaders.length === 0)
57670
+ return;
57671
+ globalPendo._.each(configHeaders, (header) => {
57672
+ if (!header || typeof header !== 'string')
57673
+ return;
57674
+ targetHeaders[header.toLowerCase()] = true;
57675
+ });
57676
+ }
57677
+ function setupBodyCallbacks() {
57678
+ const config = pluginAPI.ConfigReader.get(NETWORK_LOGS_CONFIG);
57679
+ if (!config)
57680
+ return;
57681
+ if (globalPendo._.isFunction(config.captureRequestBody)) {
57682
+ requestBodyCb = config.captureRequestBody;
57683
+ }
57684
+ if (globalPendo._.isFunction(config.captureResponseBody)) {
57685
+ responseBodyCb = config.captureResponseBody;
57686
+ }
57687
+ }
57688
+ function onPtmPaused() {
57689
+ isPtmPaused = true;
57690
+ }
57691
+ function onPtmUnpaused() {
57692
+ isPtmPaused = false;
57693
+ if (!buffer.isEmpty()) {
57694
+ for (const event of buffer.events) {
57695
+ pluginAPI.Events.eventCaptured.trigger(event);
57696
+ }
57697
+ send();
57698
+ }
57699
+ }
57700
+ function onAppHidden() {
57701
+ send({ hidden: true });
57702
+ }
57703
+ function onAppUnloaded() {
57704
+ send({ unload: true });
57705
+ }
57706
+ function startCapture() {
57707
+ pluginAPI.NetworkRequest.on({ request: handleRequest, response: handleResponse, error: handleError });
57708
+ }
57709
+ function teardown() {
57710
+ if (sendInterval) {
57711
+ clearInterval(sendInterval);
57712
+ }
57713
+ if (sendQueue) {
57714
+ sendQueue.stop();
57715
+ }
57716
+ if (buffer) {
57717
+ buffer.clear();
57718
+ }
57719
+ pluginAPI.NetworkRequest.off({ request: handleRequest, response: handleResponse, error: handleError });
57720
+ requestMap = {};
57721
+ pluginAPI.Events['ptm:paused'].off(onPtmPaused);
57722
+ pluginAPI.Events['ptm:unpaused'].off(onPtmUnpaused);
57723
+ pluginAPI.Events.appHidden.off(onAppHidden);
57724
+ pluginAPI.Events.appUnloaded.off(onAppUnloaded);
57725
+ }
57726
+ function handleRequest(request) {
57727
+ requestMap[request.requestId] = request;
57728
+ }
57729
+ function handleResponse(response) {
57730
+ if (!response)
57731
+ return;
57732
+ const request = requestMap[response.requestId];
57733
+ if (!request)
57734
+ return;
57735
+ // Skip capturing successful devlog events to avoid infinite loops
57736
+ if (request.url.indexOf(pendoDevlogBaseUrl) !== -1 && response.status === 200) {
57737
+ delete requestMap[response.requestId];
57738
+ return;
57739
+ }
57740
+ // push an empty network event to the buffer to ensure that we have a token available
57741
+ const networkEvent = {};
57742
+ const wasAccepted = buffer.push(networkEvent);
57743
+ if (!wasAccepted) {
57744
+ // Token limit reached, remove request from request map
57745
+ delete requestMap[response.requestId];
57746
+ return;
57747
+ }
57748
+ globalPendo._.extend(networkEvent, createNetworkEvent({
57749
+ request,
57750
+ response
57751
+ }));
57752
+ if (!isPtmPaused) {
57753
+ pluginAPI.Events.eventCaptured.trigger(networkEvent);
57754
+ }
57755
+ delete requestMap[response.requestId];
57756
+ }
57757
+ function handleError({ error, context }) {
57758
+ if (error.requestId && requestMap[error.requestId]) {
57759
+ delete requestMap[error.requestId];
57760
+ }
57761
+ if (context.indexOf('fetch') !== -1) {
57762
+ pluginAPI.log.debug('[NetworkCapture] Fetch tracking failed:', error);
57763
+ }
57764
+ if (context.indexOf('xhr') !== -1) {
57765
+ pluginAPI.log.debug('[NetworkCapture] XHR tracking failed:', error);
57766
+ }
57767
+ }
57768
+ function extractHeaders(headers, allowedHeaders) {
57769
+ const { keys, reduce } = globalPendo._;
57770
+ if (!headers || !allowedHeaders)
57771
+ return [];
57772
+ return reduce(keys(headers), (acc, key) => {
57773
+ const normalizedKey = key.toLowerCase();
57774
+ if (allowedHeaders[normalizedKey]) {
57775
+ acc.push(`${key}: ${headers[key]}`);
57776
+ }
57777
+ return acc;
57778
+ }, []);
57779
+ }
57780
+ function processBody(body, contentType) {
57781
+ if (!body || typeof body !== 'string')
57782
+ return '';
57783
+ const processedBody = maskSensitiveFields({ string: body, contentType, _: globalPendo._ });
57784
+ return truncate(processedBody, true);
57785
+ }
57786
+ function processRequestBody({ request }) {
57787
+ if (!request || !request.body || !requestBodyCb)
57788
+ return '';
57789
+ try {
57790
+ const body = requestBodyCb(request.body, { request });
57791
+ const contentType = globalPendo._.get(request, 'headers.content-type', '');
57792
+ return processBody(body, contentType);
57793
+ }
57794
+ catch (error) {
57795
+ pluginAPI.log.error('[NetworkCapture] Error processing request body', error);
57796
+ return '[Failed to process request body]';
57797
+ }
57798
+ }
57799
+ function processResponseBody({ response }) {
57800
+ if (!response || !response.body || !responseBodyCb)
57801
+ return '';
57802
+ try {
57803
+ const body = responseBodyCb(response.body, { response });
57804
+ const contentType = globalPendo._.get(response, 'headers.content-type', '');
57805
+ return processBody(body, contentType);
57806
+ }
57807
+ catch (error) {
57808
+ pluginAPI.log.error('[NetworkCapture] Error processing response body', error);
57809
+ return '[Failed to process response body]';
57810
+ }
57811
+ }
57812
+ function createNetworkEvent({ request, response }) {
57813
+ const devLogEnvelope = createDevLogEnvelope(pluginAPI, globalPendo);
57814
+ const requestHeaders = extractHeaders(request.headers, allowedRequestHeaders);
57815
+ const responseHeaders = extractHeaders(response.headers, allowedResponseHeaders);
57816
+ const networkEvent = Object.assign(Object.assign({}, devLogEnvelope), { subType: NETWORK_SUB_TYPE, devLogMethod: request.method, devLogStatusCode: response.status, devLogRequestUrl: request.url, devLogRequestHeaders: requestHeaders, devLogResponseHeaders: responseHeaders, devLogCount: 1 });
57817
+ if (requestBodyCb) {
57818
+ networkEvent.devLogRequestBody = processRequestBody({ request });
57819
+ }
57820
+ if (responseBodyCb) {
57821
+ networkEvent.devLogResponseBody = processResponseBody({ response });
57822
+ }
57823
+ return networkEvent;
57824
+ }
57825
+ function send({ unload = false, hidden = false } = {}) {
57826
+ if (!buffer || buffer.isEmpty())
57827
+ return;
57828
+ if (!globalPendo.isSendingEvents()) {
57829
+ buffer.clear();
57830
+ return;
57831
+ }
57832
+ const jzb = buffer.pack();
57833
+ const queryParams = {
57834
+ v: globalPendo.VERSION,
57835
+ ct: pluginAPI.util.getNow()
57836
+ };
57837
+ const url = pluginAPI.transmit.buildBaseDataUrl(DEV_LOG_TYPE, globalPendo.apiKey, queryParams);
57838
+ if (unload || hidden) {
57839
+ sendQueue.drain([{ url, jzb }], unload);
57840
+ }
57841
+ else {
57842
+ sendQueue.push({ url, jzb });
57843
+ }
57844
+ }
57845
+ }
57846
+
57847
+ export { ConsoleCapture, Feedback, GuideMarkdown, NetworkCapture, createReplayPlugin as Replay, TextCapture, VocPortal, loadAgent };