@pendo/agent 2.298.2 → 2.299.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.299.0_';
3909
+ let PACKAGE_VERSION = '2.299.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);
@@ -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();
@@ -12255,42 +12279,55 @@ class PerformanceMonitor {
12255
12279
  }
12256
12280
  initialize() {
12257
12281
  this._measuringPerformance = true;
12258
- this._markPerformance('initialize');
12282
+ this._markPerformance(INITIALIZE);
12259
12283
  this.interval = setInterval(() => {
12260
12284
  this.send();
12261
- }, PERF_INTERVAL);
12285
+ }, PERFORMANCE_SEND_INTERVAL);
12262
12286
  return () => {
12263
12287
  this.teardown();
12264
12288
  };
12265
12289
  }
12266
12290
  teardown() {
12291
+ this._markPerformance(TEARDOWN);
12292
+ this.send();
12267
12293
  this._measuringPerformance = false;
12268
12294
  this._clearMarksAndMeasures();
12269
12295
  clearInterval(this.interval);
12270
12296
  }
12271
- startTimer(name) {
12272
- this._markPerformance(`${name}-start`);
12297
+ startTimer(name, detail = null) {
12298
+ this._markPerformance(`${name}-start`, { detail });
12273
12299
  }
12274
- stopTimer(name) {
12275
- this._markPerformance(`${name}-stop`);
12276
- this._measurePerformance(name);
12300
+ stopTimer(name, detail = null) {
12301
+ this._markPerformance(`${name}-stop`, { detail });
12302
+ this._measurePerformance(name, { detail });
12277
12303
  }
12278
- count(name, incr = 1) {
12279
- this._markPerformance(name, {
12280
- detail: { type: 'count', incr }
12281
- });
12304
+ count(name, increment = 1) {
12305
+ const detail = increment > 1 ? { type: 'counter', increment } : null;
12306
+ this._markPerformance(name, { detail });
12282
12307
  }
12283
12308
  send() {
12284
- const metrics = _.reduce(_.keys(Metrics), (acc, name) => {
12309
+ const metrics = _.reduce(METRICS, (acc, metric) => {
12310
+ const { name, type, instrument } = metric;
12311
+ if (!instrument)
12312
+ return acc;
12285
12313
  const entries = performance.getEntriesByName(name);
12286
- const value = Metrics[name].instrument(entries);
12287
- acc.push({ name, value });
12314
+ const value = instrument(entries);
12315
+ if (type === 'counter' && value > 0) {
12316
+ acc.push({
12317
+ name,
12318
+ type,
12319
+ value,
12320
+ timestamp: Date.now()
12321
+ });
12322
+ }
12288
12323
  return acc;
12289
12324
  }, []);
12290
12325
  const payload = _.filter(metrics, (metric) => {
12291
12326
  return metric.value > 0;
12292
12327
  });
12293
12328
  this._clearMarksAndMeasures();
12329
+ if (!ConfigReader.get('sendPerformanceMetrics'))
12330
+ return;
12294
12331
  if (_.size(payload)) {
12295
12332
  writeMetricsPOST(payload);
12296
12333
  }
@@ -12307,39 +12344,34 @@ class PerformanceMonitor {
12307
12344
  _shouldMeasurePerformance() {
12308
12345
  return this._isPerformanceApiAvailable && this._measuringPerformance;
12309
12346
  }
12310
- _markPerformance(name, attr) {
12347
+ _markPerformance(name, metadata) {
12311
12348
  if (!this._shouldMeasurePerformance())
12312
12349
  return;
12313
- name = `pendo-${name}`;
12314
- performance.mark(name);
12350
+ performance.mark(name, metadata);
12315
12351
  this.marks.add(name);
12316
12352
  }
12317
- _measurePerformance(name) {
12353
+ _measurePerformance(name, metadata) {
12318
12354
  if (!this._shouldMeasurePerformance())
12319
12355
  return;
12320
- name = `pendo-${name}`;
12321
12356
  const startMark = `${name}-start`;
12322
12357
  const stopMark = `${name}-stop`;
12323
12358
  if (performance.getEntriesByName(startMark).length && performance.getEntriesByName(stopMark).length) {
12324
- performance.measure(name, startMark, stopMark);
12359
+ performance.measure(name, startMark, stopMark, metadata);
12325
12360
  this.measures.add(name);
12326
12361
  }
12327
12362
  }
12328
12363
  _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 => {
12364
+ for (const name of this.marks) {
12332
12365
  performance.clearMarks(name);
12333
- });
12366
+ }
12334
12367
  this.marks.clear();
12335
- // eslint-disable-next-line agent-eslint-rules/no-array-foreach
12336
- this.measures.forEach(name => {
12368
+ for (const name of this.measures) {
12337
12369
  performance.clearMeasures(name);
12338
- });
12370
+ }
12339
12371
  this.measures.clear();
12340
12372
  }
12341
12373
  }
12342
- const _PerformanceMonitor = new PerformanceMonitor();
12374
+ const performanceMonitor = new PerformanceMonitor();
12343
12375
 
12344
12376
  var defaultTrackName = '_PENDO_UNNAMED_';
12345
12377
  var SILO_AVG_COMPRESSION_RATIO = 5;
@@ -12391,9 +12423,6 @@ function collectEvent(type, props, url, name, eventProperties, context) {
12391
12423
  if (!isURLValid(event.url)) {
12392
12424
  return;
12393
12425
  }
12394
- if (!eventIsWhitelisted(event)) {
12395
- return;
12396
- }
12397
12426
  if (type === 'track') {
12398
12427
  trackEventQueue.push(event);
12399
12428
  return;
@@ -12404,19 +12433,6 @@ function collectEvent(type, props, url, name, eventProperties, context) {
12404
12433
  }
12405
12434
  eventQueue.push(event);
12406
12435
  }
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
12436
  function pipeline() {
12421
12437
  var args = _.toArray(arguments);
12422
12438
  return function generatedPipeline(obj, next) {
@@ -12841,7 +12857,7 @@ function createSendQueue(options, send, guaranteedSend) {
12841
12857
  }
12842
12858
  });
12843
12859
  queue.onTimeout = function () {
12844
- _PerformanceMonitor.count(`${options.beacon}-gif-failure`);
12860
+ performanceMonitor.count(BEACON_GIF_FAILURES[options.beacon]);
12845
12861
  };
12846
12862
  queue.retryPending = true;
12847
12863
  return queue;
@@ -13240,11 +13256,12 @@ function attributeSerializer(context, node) {
13240
13256
 
13241
13257
  function childIndexSerializer(context, node) {
13242
13258
  if (node.parentNode && node.parentNode.childNodes) {
13243
- var nodes = _.chain(node.parentNode.childNodes);
13259
+ const nodes = _.chain(node.parentNode.childNodes);
13244
13260
  context.myIndex = nodes.indexOf(node).value();
13245
- context.childIndex = nodes.filter(function (n) {
13261
+ const childNodes = nodes.filter(function (n) {
13246
13262
  return n.nodeType == ELEMENT;
13247
- }).indexOf(node).value();
13263
+ });
13264
+ context.childIndex = childNodes.indexOf(node).value();
13248
13265
  }
13249
13266
  return context;
13250
13267
  }
@@ -13373,7 +13390,7 @@ var getValidTarget = function (node) {
13373
13390
  */
13374
13391
  var handle_event = function (evt) {
13375
13392
  try {
13376
- _PerformanceMonitor.startTimer('event-captured');
13393
+ performanceMonitor.startTimer(EVENT_CAPTURED_TIMER);
13377
13394
  if (dom.data.get(evt, 'counted'))
13378
13395
  return;
13379
13396
  dom.data.set(evt, 'counted', true);
@@ -13414,7 +13431,7 @@ var handle_event = function (evt) {
13414
13431
  log.critical('pendo.io while handling event', { error: e });
13415
13432
  }
13416
13433
  finally {
13417
- _PerformanceMonitor.stopTimer('event-captured');
13434
+ performanceMonitor.stopTimer(EVENT_CAPTURED_TIMER);
13418
13435
  }
13419
13436
  };
13420
13437
  function getClickEventProperties(target) {
@@ -19408,6 +19425,9 @@ var BuildingBlockResourceCenter = (function () {
19408
19425
  webWidgetWrapper(provider, 'messenger:on', 'unreadMessages', function (unreadCount) {
19409
19426
  updateNotificationBubbleCount(unreadCount, 'chat');
19410
19427
  });
19428
+ if (resourceCenter.isInProgress() && !resourceCenter.attributes.doNotResume) {
19429
+ handleNativeIntegrationContinuation(resourceCenter.activeModule);
19430
+ }
19411
19431
  }
19412
19432
  // Zendesk Native Chat API - https://developer.zendesk.com/embeddables/docs/widget/chat_api_migration
19413
19433
  function configureZendeskChatSettings(integrationObj) {
@@ -20399,17 +20419,18 @@ var GuideStateModule = (function () {
20399
20419
  updateLastGuideStepSeen(context, lastGuideStepSeen) {
20400
20420
  if (lastGuideStepSeen.visitorId && lastGuideStepSeen.visitorId !== context.getters.visitorId())
20401
20421
  return;
20402
- const isValidGuide = _.some(context.getters.guideList(), function ({ id }) {
20422
+ // Embedded guides should not update the lastGuideStepSeen in order to not interfere with auto guide display
20423
+ // Embedded guides are not included in getters.guideList
20424
+ const shouldUpdateLastGuideStepSeen = _.some(context.getters.guideList(), function ({ id }) {
20403
20425
  return id === lastGuideStepSeen.guideId;
20404
20426
  });
20405
20427
  if (lastGuideStepSeen.guideStepId) {
20406
20428
  context.commit('setStepState', lastGuideStepSeen);
20407
20429
  }
20408
- if (!isValidGuide) {
20409
- return;
20430
+ if (shouldUpdateLastGuideStepSeen) {
20431
+ context.commit('setLastGuideStepSeen', lastGuideStepSeen);
20432
+ pendo$1.lastGuideStepSeen = lastGuideStepSeen;
20410
20433
  }
20411
- context.commit('setLastGuideStepSeen', lastGuideStepSeen);
20412
- pendo$1.lastGuideStepSeen = lastGuideStepSeen;
20413
20434
  if (guideCache) {
20414
20435
  guideCache.update(lastGuideStepSeen);
20415
20436
  }
@@ -21385,7 +21406,7 @@ class UpdateRunner {
21385
21406
  monitor.start();
21386
21407
  promise = currentPhase.process(monitor);
21387
21408
  if (!promise && monitor.isTimeExceeded()) {
21388
- _PerformanceMonitor.count(METRIC_GUIDELOOP_TIMEOUT);
21409
+ performanceMonitor.count(GUIDE_LOOP_TIMEOUT);
21389
21410
  if (currentPhase.handleProcessTimeExceeded) {
21390
21411
  currentPhase.handleProcessTimeExceeded(monitor);
21391
21412
  }
@@ -26553,10 +26574,34 @@ const PENDO_HEADERS_KEY = '_pendoHeaders';
26553
26574
  function getTransformedUrl(url) {
26554
26575
  return store.getters['networkUrl/getTransformedUrl']()(url);
26555
26576
  }
26577
+ const HEADERS = 'headers';
26578
+ const DENIED_HEADERS = {
26579
+ 'authorization': true,
26580
+ 'cookie': true,
26581
+ 'set-cookie': true,
26582
+ 'x-api-key': true,
26583
+ 'x-forwarded-for': true,
26584
+ 'proxy-authorization': true,
26585
+ 'signature': true,
26586
+ 'forwarded': true,
26587
+ 'referrer': true
26588
+ };
26589
+ const XSRF = 'xsrf';
26590
+ const CSRF = 'csrf';
26591
+ const MAX_RESPONSE_SIZE = 1024 * 1024 * 2; // 2MB
26592
+ const MAX_RESPONSE_SIZE_MESSAGE = '[Body too large to capture (over 2 MB)]';
26556
26593
  class NetworkRequestIntercept {
26557
- entriesToObject(entries) {
26594
+ // Headers that should never be captured by pendo for PII/security reasons.
26595
+ isDeniedHeader(key) {
26596
+ const normalizedKey = key.toLowerCase();
26597
+ return DENIED_HEADERS[normalizedKey] || normalizedKey.indexOf(CSRF) !== -1 || normalizedKey.indexOf(XSRF) !== -1;
26598
+ }
26599
+ entriesToObject(entries, type) {
26558
26600
  const obj = {};
26559
26601
  for (const [key, value] of entries) {
26602
+ if (type === HEADERS && this.isDeniedHeader(key)) {
26603
+ continue;
26604
+ }
26560
26605
  obj[key] = value;
26561
26606
  }
26562
26607
  return obj;
@@ -26580,11 +26625,14 @@ class NetworkRequestIntercept {
26580
26625
  extractHeaders(headers = {}) {
26581
26626
  let result = {};
26582
26627
  if (headers instanceof Headers) {
26583
- result = this.entriesToObject(headers.entries());
26628
+ result = this.entriesToObject(headers.entries(), HEADERS);
26584
26629
  }
26585
26630
  else if (typeof headers === 'object') {
26586
26631
  for (const key in headers) {
26587
26632
  if (headers.hasOwnProperty(key)) {
26633
+ if (this.isDeniedHeader(key)) {
26634
+ continue;
26635
+ }
26588
26636
  result[key] = headers[key];
26589
26637
  }
26590
26638
  }
@@ -26601,17 +26649,25 @@ class NetworkRequestIntercept {
26601
26649
  safelyReadBody(config, type) {
26602
26650
  return __awaiter(this, void 0, void 0, function* () {
26603
26651
  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();
26652
+ const contentType = config.headers.get('content-type') || 'unknown';
26653
+ const contentLengthHeader = config.headers.get('content-length');
26654
+ // Check if content type is readable (text or JSON)
26655
+ const isReadableContent = contentType.indexOf('text/') !== -1 || contentType.indexOf('application/json') !== -1;
26656
+ if (!isReadableContent) {
26657
+ const typeDisplay = contentType;
26658
+ return `[Binary content: ${typeDisplay}]`;
26659
+ }
26660
+ if (contentLengthHeader) {
26661
+ const size = parseInt(contentLengthHeader, 10);
26662
+ if (size > MAX_RESPONSE_SIZE) {
26663
+ return MAX_RESPONSE_SIZE_MESSAGE;
26664
+ }
26610
26665
  }
26611
- else {
26612
- // For binary or unknown content types, just capture metadata
26613
- return `[Binary content: ${contentType}]`;
26666
+ const text = yield config.text();
26667
+ if (text.length > MAX_RESPONSE_SIZE) {
26668
+ return MAX_RESPONSE_SIZE_MESSAGE;
26614
26669
  }
26670
+ return text;
26615
26671
  }
26616
26672
  catch (e) {
26617
26673
  return `[Unable to read ${type} body]`;
@@ -26622,7 +26678,7 @@ class NetworkRequestIntercept {
26622
26678
  let headers = {};
26623
26679
  if (config.headers) {
26624
26680
  const headerEntries = Array.from(config.headers.entries());
26625
- headers = this.entriesToObject(headerEntries);
26681
+ headers = this.entriesToObject(headerEntries, HEADERS);
26626
26682
  }
26627
26683
  return headers;
26628
26684
  }
@@ -26793,7 +26849,8 @@ class NetworkRequestIntercept {
26793
26849
  if (xhr.readyState === 4) { // Request completed
26794
26850
  try {
26795
26851
  const headers = networkInterceptor.parseXHRResponseHeaders(xhr.getAllResponseHeaders());
26796
- const { status, statusText, responseText: body, _url } = xhr;
26852
+ const { status, statusText, _url } = xhr;
26853
+ const body = networkInterceptor.safelyReadXHRResponse(xhr, headers);
26797
26854
  _.each(networkInterceptor.callbackFns, ({ response }) => {
26798
26855
  if (_.isFunction(response)) {
26799
26856
  response({
@@ -26822,6 +26879,21 @@ class NetworkRequestIntercept {
26822
26879
  return networkInterceptor._originalXHRSend.apply(this, arguments);
26823
26880
  };
26824
26881
  }
26882
+ safelyReadXHRResponse(xhr, headers) {
26883
+ const networkInterceptor = this;
26884
+ const size = networkInterceptor.estimateResponseSize(headers['content-length'], xhr.responseText);
26885
+ if (size > MAX_RESPONSE_SIZE) {
26886
+ return MAX_RESPONSE_SIZE_MESSAGE;
26887
+ }
26888
+ return xhr.responseText;
26889
+ }
26890
+ estimateResponseSize(contentLength, responseText) {
26891
+ const parsedContentLength = parseInt(contentLength, 10);
26892
+ if (!_.isNumber(parsedContentLength) || isNaN(parsedContentLength)) {
26893
+ return responseText.length;
26894
+ }
26895
+ return parsedContentLength;
26896
+ }
26825
26897
  patchNetwork() {
26826
26898
  const networkInterceptor = this;
26827
26899
  if (networkInterceptor._networkPatched)
@@ -28357,7 +28429,7 @@ const initialize = makeSafe(function (options) {
28357
28429
  Events.appUsage.on(GuideActivity.handler);
28358
28430
  Events.appUsage.on(ResourceCenterActivity.handler);
28359
28431
  teardownFns.push(flushEvery(SEND_INTERVAL));
28360
- teardownFns.push(_PerformanceMonitor.initialize());
28432
+ teardownFns.push(performanceMonitor.initialize());
28361
28433
  }
28362
28434
  Events.appHidden.on(() => {
28363
28435
  flushNow(true, { hidden: true });
@@ -29398,8 +29470,10 @@ function track(name, props) {
29398
29470
  collectEventHelper({ type: 'track', name, props });
29399
29471
  }
29400
29472
 
29473
+ const privacyFilterCache = new Map();
29401
29474
  /**
29402
29475
  * Method to manually track agentic events. Uses agent functions for IDs and context.
29476
+ * Automatically applies privacy filters if agentId is provided and agent configuration exists.
29403
29477
  *
29404
29478
  * @access public
29405
29479
  * @category Events
@@ -29418,8 +29492,64 @@ function track(name, props) {
29418
29492
  * })
29419
29493
  */
29420
29494
  function trackAgent(type, props) {
29495
+ const filteredTypes = ['prompt', 'system_response'];
29496
+ if (_.contains(filteredTypes, type)) {
29497
+ props = applyPrivacyFilters(props);
29498
+ }
29421
29499
  collectEventHelper({ type, name: 'agentic', props });
29422
29500
  }
29501
+ /**
29502
+ * Apply privacy filters to event properties based on agent configuration
29503
+ * Optimized for maximum performance with caching
29504
+ * @param {Object} props - Event properties to filter
29505
+ * @returns {Object} - Filtered properties
29506
+ */
29507
+ function applyPrivacyFilters(props) {
29508
+ if (!(props === null || props === void 0 ? void 0 : props.agentId)) {
29509
+ return props;
29510
+ }
29511
+ try {
29512
+ const agents = ConfigReader.get('aiAgents', []);
29513
+ const agent = (agents === null || agents === void 0 ? void 0 : agents.find(agent => agent.id === props.agentId)) || null;
29514
+ const content = props.content;
29515
+ if (!content) {
29516
+ return props;
29517
+ }
29518
+ if (!(agent === null || agent === void 0 ? void 0 : agent.privacyFilters)) {
29519
+ return props;
29520
+ }
29521
+ const privacyFilter = getCachedRegex(agent.privacyFilters);
29522
+ if (!privacyFilter) {
29523
+ return props;
29524
+ }
29525
+ const filteredContent = content.replace(privacyFilter, '<redacted>');
29526
+ if (filteredContent === content) {
29527
+ return props;
29528
+ }
29529
+ return Object.assign(Object.assign({}, props), { content: filteredContent });
29530
+ }
29531
+ catch (error) {
29532
+ console.error('Error applying privacy filters', error);
29533
+ return props;
29534
+ }
29535
+ }
29536
+ /**
29537
+ * Get cached regex or compile and cache new one
29538
+ */
29539
+ function getCachedRegex(filterPattern) {
29540
+ let regex = privacyFilterCache.get(filterPattern);
29541
+ if (!regex) {
29542
+ try {
29543
+ regex = new RegExp(filterPattern, 'gmi');
29544
+ privacyFilterCache.set(filterPattern, regex);
29545
+ }
29546
+ catch (e) {
29547
+ privacyFilterCache.set(filterPattern, null);
29548
+ return null;
29549
+ }
29550
+ }
29551
+ return regex;
29552
+ }
29423
29553
 
29424
29554
  /**
29425
29555
  * Checks visitor and account metadata in the current Pendo installation. Either logs to console
@@ -33411,12 +33541,12 @@ var GuideUpdateModule = (function () {
33411
33541
  state.scheduledUpdate = scheduledUpdate;
33412
33542
  },
33413
33543
  startUpdate(state, time) {
33414
- _PerformanceMonitor.startTimer('guide-loop');
33544
+ performanceMonitor.startTimer(GUIDE_LOOP_TIMER);
33415
33545
  state.needsUpdate = false;
33416
33546
  state.updateId = time;
33417
33547
  },
33418
33548
  completeUpdate(state, time) {
33419
- _PerformanceMonitor.stopTimer('guide-loop');
33549
+ performanceMonitor.stopTimer(GUIDE_LOOP_TIMER);
33420
33550
  state.updateId = null;
33421
33551
  state.updateCompleteTime = time;
33422
33552
  }
@@ -38201,7 +38331,7 @@ const EmbeddedGuides = (function () {
38201
38331
  return { guide, step };
38202
38332
  }
38203
38333
  function initializeEmbeddedGuides() {
38204
- restoreFromPreviouseGuides();
38334
+ restoreFromPreviousGuides();
38205
38335
  pluginApi.guides.registerDisplayableGuides('embeddedGuides', embeddedGuides);
38206
38336
  applyEmbeddedGuideBehaviors();
38207
38337
  }
@@ -38216,7 +38346,7 @@ const EmbeddedGuides = (function () {
38216
38346
  // When a guides payload is received, the embedded guides list is cleared and cached in _oldEmbeddedGuides.
38217
38347
  // This function replaces guides in the new payload with guides from the previous payload to prevent re-rendering.
38218
38348
  // Additionally, if guides from the previous payload are showing, and they are not in the new payload, this function hides them.
38219
- function restoreFromPreviouseGuides() {
38349
+ function restoreFromPreviousGuides() {
38220
38350
  _.each(_oldEmbeddedGuides, function (oldGuide) {
38221
38351
  const newGuideIndex = _.findIndex(embeddedGuides, function (guide) {
38222
38352
  return guide.id === oldGuide.id;
@@ -54690,9 +54820,7 @@ const ErrorStackParser = {
54690
54820
  // if a location was matched, pass it to extractLocation() otherwise pop the last token
54691
54821
  const locationParts = this.extractLocation(location ? location[1] : tokens.pop() || '');
54692
54822
  const functionName = tokens.join(' ') || undefined;
54693
- const fileName = ['eval', '<anonymous>'].indexOf(locationParts[0]) > -1
54694
- ? undefined
54695
- : locationParts[0];
54823
+ const fileName = (locationParts[0] === 'eval' || locationParts[0] === '<anonymous>') ? undefined : locationParts[0];
54696
54824
  frames.push(new StackFrame({
54697
54825
  functionName,
54698
54826
  fileName,
@@ -54734,17 +54862,29 @@ const PII_PATTERN = {
54734
54862
  ip: /\b(?:\d{1,3}\.){3}\d{1,3}\b/g,
54735
54863
  ssn: /\b\d{3}-\d{2}-\d{4}\b/g,
54736
54864
  creditCard: /\b(?:\d[ -]*?){13,16}\b/g,
54737
- httpsUrls: /https:\/\/[^\s]+/g
54865
+ httpsUrls: /https:\/\/[^\s]+/g,
54866
+ email: /\b\S+@[\w-]+\.[\w-]+\b/g
54738
54867
  };
54739
54868
  const PII_REPLACEMENT = '*'.repeat(10);
54869
+ const JSON_PII_KEYS = ['password', 'email', 'key', 'token', 'auth', 'authentication', 'phone', 'address', 'ssn'];
54870
+ const UNABLE_TO_DISPLAY_BODY = '[Unable to display body: detected PII could not be redacted]';
54871
+ const joinedKeys = JSON_PII_KEYS.join('|');
54872
+ const keyPattern = `"([^"]*(?:${joinedKeys})[^"]*)"`;
54873
+ // only scrub strings, numbers, and booleans
54874
+ const acceptedValues = '("([^"\\\\]*(?:\\\\.[^"\\\\]*)*")|(\\d+)|(true|false))';
54740
54875
  /**
54741
54876
  * Truncates a string to the max length
54742
54877
  * @access private
54743
54878
  * @param {String} string
54744
54879
  * @returns {String}
54745
54880
  */
54746
- function truncate(string) {
54747
- return string.length > MAX_LENGTH ? string.slice(0, MAX_LENGTH) : string;
54881
+ function truncate(string, includeEllipsis = false) {
54882
+ if (string.length <= MAX_LENGTH)
54883
+ return string;
54884
+ if (includeEllipsis) {
54885
+ return string.slice(0, MAX_LENGTH).concat('...');
54886
+ }
54887
+ return string.slice(0, MAX_LENGTH);
54748
54888
  }
54749
54889
  /**
54750
54890
  * Generate a log key for a console log separated by pipes
@@ -54758,13 +54898,13 @@ function generateLogKey(methodName, message, stackTrace) {
54758
54898
  return `${methodName}|${message}|${stackTrace}`;
54759
54899
  }
54760
54900
  /**
54761
- * Checks if a string has 2 or more digits or a https URL
54901
+ * Checks if a string has 2 or more digits, a https URL, or an email address
54762
54902
  * @access private
54763
54903
  * @param {String} string
54764
54904
  * @returns {Boolean}
54765
54905
  */
54766
54906
  function mightContainPII(string) {
54767
- return /[\d]{2,}|https:\/\//.test(string);
54907
+ return /[\d]{2,}|https:\/\/|@/.test(string);
54768
54908
  }
54769
54909
  /**
54770
54910
  * Scrub basic PII from a string and replace it with a placeholder
@@ -54772,12 +54912,60 @@ function mightContainPII(string) {
54772
54912
  * @param {String} string
54773
54913
  * @returns {String}
54774
54914
  */
54775
- function scrubPII(string) {
54915
+ function scrubPII({ string, _ }) {
54776
54916
  if (!string || typeof string !== 'string')
54777
54917
  return string;
54778
54918
  if (!mightContainPII(string))
54779
54919
  return string;
54780
- return Object.values(PII_PATTERN).reduce((str, pattern) => str.replace(pattern, PII_REPLACEMENT), string);
54920
+ return _.reduce(_.values(PII_PATTERN), (str, pattern) => str.replace(pattern, PII_REPLACEMENT), string);
54921
+ }
54922
+ /**
54923
+ * Scrub PII from a stringified JSON object
54924
+ * @access private
54925
+ * @param {String} string
54926
+ * @returns {String}
54927
+ */
54928
+ function scrubJsonPII(string) {
54929
+ const fullScrubRegex = new RegExp(`${keyPattern}\\s*:\\s*[\\{\\[]`, 'i');
54930
+ if (fullScrubRegex.test(string)) {
54931
+ return UNABLE_TO_DISPLAY_BODY;
54932
+ }
54933
+ const keyValueScrubRegex = new RegExp(`${keyPattern}\\s*:\\s*${acceptedValues}`, 'gi');
54934
+ return string.replace(keyValueScrubRegex, (match, key) => `"${key}":"${PII_REPLACEMENT}"`);
54935
+ }
54936
+ /**
54937
+ * Checks if a string is a JSON object
54938
+ * @access private
54939
+ * @param {String} string
54940
+ * @param {String} contentType
54941
+ * @returns {Boolean}
54942
+ */
54943
+ function mightContainJson(string, contentType) {
54944
+ if (!string || typeof string !== 'string')
54945
+ return false;
54946
+ const firstChar = string.charAt(0);
54947
+ if (contentType && contentType.indexOf('application/json') !== -1) {
54948
+ return true;
54949
+ }
54950
+ return firstChar === '{' || firstChar === '[';
54951
+ }
54952
+ /**
54953
+ * Mask sensitive fields in a string
54954
+ * @access private
54955
+ * @param {String} string
54956
+ * @param {String} contentType
54957
+ * @returns {String}
54958
+ */
54959
+ function maskSensitiveFields({ string, contentType, _ }) {
54960
+ if (!string || typeof string !== 'string')
54961
+ return string;
54962
+ if (mightContainJson(string, contentType)) {
54963
+ string = scrubJsonPII(string);
54964
+ }
54965
+ if (mightContainPII(string)) {
54966
+ string = scrubPII({ string, _ });
54967
+ }
54968
+ return string;
54781
54969
  }
54782
54970
 
54783
54971
  /**
@@ -54952,11 +55140,10 @@ function createCspViolationMessage(blockedURI, directive, isReportOnly, original
54952
55140
  }
54953
55141
  }
54954
55142
 
54955
- const DEV_LOG_LEVELS = ['info', 'warn', 'error'];
54956
55143
  const TOKEN_MAX_SIZE = 100;
54957
55144
  const TOKEN_REFILL_RATE = 10;
54958
55145
  const TOKEN_REFRESH_INTERVAL = 1000;
54959
- class ConsoleCaptureBuffer {
55146
+ class DevlogBuffer {
54960
55147
  constructor(pendo, pluginAPI) {
54961
55148
  this.pendo = pendo;
54962
55149
  this.pluginAPI = pluginAPI;
@@ -54985,9 +55172,6 @@ class ConsoleCaptureBuffer {
54985
55172
  this.lastEvent = null;
54986
55173
  }
54987
55174
  push(event) {
54988
- const { devLogLevel } = event;
54989
- if (!this.pendo._.contains(DEV_LOG_LEVELS, devLogLevel))
54990
- return false;
54991
55175
  this.refillTokens();
54992
55176
  if (this.tokens === 0)
54993
55177
  return false;
@@ -55804,7 +55988,6 @@ var ConfigReader = (function () {
55804
55988
  * @type {boolean}
55805
55989
  */
55806
55990
  addOption('enableDebugEvents', [SNIPPET_SRC, PENDO_CONFIG_SRC]);
55807
- addOption('freeNPSData');
55808
55991
  addOption('eventPropertyConfigurations');
55809
55992
  /**
55810
55993
  * By default, a click event property rule can be matched against the element that is clicked on or its
@@ -56192,7 +56375,9 @@ var ConfigReader = (function () {
56192
56375
  addOption('enableAllEmbeddedGuideEvents', [PENDO_CONFIG_SRC], false);
56193
56376
  // Form Validation
56194
56377
  addOption('formValidation', [PENDO_CONFIG_SRC], false);
56378
+ // Performance Metrics
56195
56379
  addOption('performanceMetricsEnabled', [SNIPPET_SRC, PENDO_CONFIG_SRC], true);
56380
+ addOption('sendPerformanceMetrics', [SNIPPET_SRC, PENDO_CONFIG_SRC], false);
56196
56381
  }
56197
56382
  initializeOptions();
56198
56383
  var sourceGetters = {};
@@ -56981,7 +57166,7 @@ else {
56981
57166
  var SEND_INTERVAL = 2 * 60 * 1000;
56982
57167
  var ENCODED_EVENT_MAX_LENGTH = 1900;
56983
57168
 
56984
- class ConsoleTransport {
57169
+ class DevlogTransport {
56985
57170
  constructor(globalPendo, pluginAPI) {
56986
57171
  this.pendo = globalPendo;
56987
57172
  this.pluginAPI = pluginAPI;
@@ -57025,7 +57210,7 @@ class ConsoleTransport {
57025
57210
  }
57026
57211
  else if (options.keepalive && this.pluginAPI.transmit.sendBeacon.supported()) {
57027
57212
  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'));
57213
+ return result ? Promise$2.resolve() : Promise$2.reject(new Error('sendBeacon failed to send devlog data'));
57029
57214
  }
57030
57215
  else {
57031
57216
  return this.pendo.ajax.post(url, options.body);
@@ -57060,6 +57245,17 @@ class ConsoleTransport {
57060
57245
  }
57061
57246
  }
57062
57247
 
57248
+ const DEV_LOG_TYPE = 'devlog';
57249
+ function createDevLogEnvelope(pluginAPI, globalPendo) {
57250
+ return {
57251
+ browser_time: pluginAPI.util.getNow(),
57252
+ url: globalPendo.url.get(),
57253
+ visitorId: globalPendo.get_visitor_id(),
57254
+ accountId: globalPendo.get_account_id(),
57255
+ type: DEV_LOG_TYPE
57256
+ };
57257
+ }
57258
+
57063
57259
  function ConsoleCapture() {
57064
57260
  let pluginAPI;
57065
57261
  let _;
@@ -57071,7 +57267,6 @@ function ConsoleCapture() {
57071
57267
  let isPtmPaused;
57072
57268
  const CAPTURE_CONSOLE_CONFIG = 'captureConsoleLogs';
57073
57269
  const CONSOLE_METHODS = ['log', 'warn', 'error', 'info'];
57074
- const DEV_LOG_TYPE = 'devlog';
57075
57270
  const DEV_LOG_SUB_TYPE = 'console';
57076
57271
  // deduplicate logs
57077
57272
  let lastLogKey = '';
@@ -57107,8 +57302,8 @@ function ConsoleCapture() {
57107
57302
  if (!captureConsoleEnabled)
57108
57303
  return;
57109
57304
  globalPendo = pendo;
57110
- buffer = new ConsoleCaptureBuffer(pendo, pluginAPI);
57111
- transport = new ConsoleTransport(pendo, pluginAPI);
57305
+ buffer = new DevlogBuffer(pendo, pluginAPI);
57306
+ transport = new DevlogTransport(pendo, pluginAPI);
57112
57307
  sendQueue = new SendQueue(transport.sendRequest.bind(transport));
57113
57308
  sendInterval = setInterval(() => {
57114
57309
  if (!sendQueue.failed()) {
@@ -57141,16 +57336,6 @@ function ConsoleCapture() {
57141
57336
  send();
57142
57337
  }
57143
57338
  }
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
57339
  function readyHandler() {
57155
57340
  addIntercepts();
57156
57341
  pluginAPI.attachEventInternal(window, 'securitypolicyviolation', securityPolicyViolationFn);
@@ -57186,7 +57371,7 @@ function ConsoleCapture() {
57186
57371
  }
57187
57372
  }
57188
57373
  function createConsoleEvent(args, methodName, { skipStackTrace = false, skipScrubPII = false } = {}) {
57189
- if (!args || args.length === 0)
57374
+ if (!args || args.length === 0 || !_.contains(CONSOLE_METHODS, methodName))
57190
57375
  return;
57191
57376
  // stringify args
57192
57377
  let message = _.compact(_.map(args, arg => {
@@ -57216,8 +57401,8 @@ function ConsoleCapture() {
57216
57401
  buffer.lastEvent.devLogCount++;
57217
57402
  return;
57218
57403
  }
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 });
57404
+ const devLogEnvelope = createDevLogEnvelope(pluginAPI, globalPendo);
57405
+ 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
57406
  const wasAccepted = buffer.push(consoleEvent);
57222
57407
  if (wasAccepted) {
57223
57408
  if (!isPtmPaused) {
@@ -57253,7 +57438,7 @@ function ConsoleCapture() {
57253
57438
  v: globalPendo.VERSION,
57254
57439
  ct: pluginAPI.util.getNow()
57255
57440
  };
57256
- const url = pluginAPI.transmit.buildBaseDataUrl('devlog', globalPendo.apiKey, queryParams);
57441
+ const url = pluginAPI.transmit.buildBaseDataUrl(DEV_LOG_TYPE, globalPendo.apiKey, queryParams);
57257
57442
  const jzb = buffer.pack();
57258
57443
  if (!jzb)
57259
57444
  return;
@@ -57291,4 +57476,329 @@ function ConsoleCapture() {
57291
57476
  }
57292
57477
  }
57293
57478
 
57294
- export { ConsoleCapture, Feedback, GuideMarkdown, createReplayPlugin as Replay, TextCapture, VocPortal, loadAgent };
57479
+ function NetworkCapture() {
57480
+ let pluginAPI;
57481
+ let globalPendo;
57482
+ let requestMap = {};
57483
+ let buffer;
57484
+ let sendQueue;
57485
+ let sendInterval;
57486
+ let transport;
57487
+ let isPtmPaused;
57488
+ let requestBodyCb;
57489
+ let responseBodyCb;
57490
+ let pendoDevlogBaseUrl;
57491
+ const CAPTURE_NETWORK_CONFIG = 'captureNetworkRequests';
57492
+ const NETWORK_SUB_TYPE = 'network';
57493
+ const NETWORK_LOGS_CONFIG = 'networkLogs';
57494
+ const NETWORK_LOGS_CONFIG_ALLOWED_REQUEST_HEADERS = 'networkLogs.allowedRequestHeaders';
57495
+ const NETWORK_LOGS_CONFIG_ALLOWED_RESPONSE_HEADERS = 'networkLogs.allowedResponseHeaders';
57496
+ const NETWORK_LOGS_CONFIG_CAPTURE_REQUEST_BODY = 'networkLogs.captureRequestBody';
57497
+ const NETWORK_LOGS_CONFIG_CAPTURE_RESPONSE_BODY = 'networkLogs.captureResponseBody';
57498
+ const allowedRequestHeaders = {
57499
+ 'content-type': true, 'content-length': true, 'accept': true, 'accept-language': true
57500
+ };
57501
+ const allowedResponseHeaders = {
57502
+ 'cache-control': true, 'content-length': true, 'content-type': true, 'content-language': true
57503
+ };
57504
+ return {
57505
+ name: 'NetworkCapture',
57506
+ initialize,
57507
+ teardown,
57508
+ handleRequest,
57509
+ handleResponse,
57510
+ handleError,
57511
+ startCapture,
57512
+ createNetworkEvent,
57513
+ send,
57514
+ onPtmPaused,
57515
+ onPtmUnpaused,
57516
+ onAppHidden,
57517
+ onAppUnloaded,
57518
+ addConfigOptions,
57519
+ processHeaderConfig,
57520
+ extractHeaders,
57521
+ setupBodyCallbacks,
57522
+ processBody,
57523
+ processRequestBody,
57524
+ processResponseBody,
57525
+ get requestMap() {
57526
+ return requestMap;
57527
+ },
57528
+ get buffer() {
57529
+ return buffer;
57530
+ },
57531
+ get sendQueue() {
57532
+ return sendQueue;
57533
+ },
57534
+ get isPtmPaused() {
57535
+ return isPtmPaused;
57536
+ },
57537
+ get allowedRequestHeaders() {
57538
+ return allowedRequestHeaders;
57539
+ },
57540
+ get allowedResponseHeaders() {
57541
+ return allowedResponseHeaders;
57542
+ },
57543
+ get requestBodyCb() {
57544
+ return requestBodyCb;
57545
+ },
57546
+ get responseBodyCb() {
57547
+ return responseBodyCb;
57548
+ }
57549
+ };
57550
+ function initialize(pendo, PluginAPI) {
57551
+ pluginAPI = PluginAPI;
57552
+ globalPendo = pendo;
57553
+ const { ConfigReader } = pluginAPI;
57554
+ ConfigReader.addOption(CAPTURE_NETWORK_CONFIG, [ConfigReader.sources.PENDO_CONFIG_SRC], false);
57555
+ const captureNetworkEnabled = ConfigReader.get(CAPTURE_NETWORK_CONFIG);
57556
+ if (!captureNetworkEnabled)
57557
+ return;
57558
+ buffer = new DevlogBuffer(pendo, pluginAPI);
57559
+ transport = new DevlogTransport(pendo, pluginAPI);
57560
+ sendQueue = new SendQueue(transport.sendRequest.bind(transport));
57561
+ addConfigOptions();
57562
+ processHeaderConfig(NETWORK_LOGS_CONFIG_ALLOWED_REQUEST_HEADERS, allowedRequestHeaders);
57563
+ processHeaderConfig(NETWORK_LOGS_CONFIG_ALLOWED_RESPONSE_HEADERS, allowedResponseHeaders);
57564
+ setupBodyCallbacks();
57565
+ sendInterval = setInterval(() => {
57566
+ if (!sendQueue.failed()) {
57567
+ send();
57568
+ }
57569
+ }, SEND_INTERVAL);
57570
+ sendQueue.start();
57571
+ pluginAPI.Events.ready.on(startCapture);
57572
+ pluginAPI.Events['ptm:paused'].on(onPtmPaused);
57573
+ pluginAPI.Events['ptm:unpaused'].on(onPtmUnpaused);
57574
+ pluginAPI.Events.appHidden.on(onAppHidden);
57575
+ pluginAPI.Events.appUnloaded.on(onAppUnloaded);
57576
+ pendoDevlogBaseUrl = pluginAPI.transmit.buildBaseDataUrl(DEV_LOG_TYPE, globalPendo.apiKey);
57577
+ }
57578
+ function addConfigOptions() {
57579
+ const { ConfigReader } = pluginAPI;
57580
+ ConfigReader.addOption(NETWORK_LOGS_CONFIG, [ConfigReader.sources.SNIPPET_SRC], {});
57581
+ /**
57582
+ * Additional request headers to capture in network logs.
57583
+ * Default headers are: `['content-type', 'content-length', 'accept', 'accept-language']`
57584
+ *
57585
+ * @access public
57586
+ * @category Config/Network Logs
57587
+ * @name networkLogs.allowedRequestHeaders
57588
+ * @default []
57589
+ * @type {string[]}
57590
+ */
57591
+ ConfigReader.addOption(NETWORK_LOGS_CONFIG_ALLOWED_REQUEST_HEADERS, [ConfigReader.sources.SNIPPET_SRC], []);
57592
+ /**
57593
+ * Additional response headers to capture in network logs.
57594
+ * Default headers are: `['cache-control', 'content-length', 'content-type', 'content-language']`
57595
+ *
57596
+ * @access public
57597
+ * @category Config/Network Logs
57598
+ * @name networkLogs.allowedResponseHeaders
57599
+ * @default []
57600
+ * @type {string[]}
57601
+ */
57602
+ ConfigReader.addOption(NETWORK_LOGS_CONFIG_ALLOWED_RESPONSE_HEADERS, [ConfigReader.sources.SNIPPET_SRC], []);
57603
+ /**
57604
+ * Callback function to process/modify request bodies before capturing them. If not provided, the request body will not be captured.
57605
+ *
57606
+ * @access public
57607
+ * @category Config/Network Logs
57608
+ * @name networkLogs.captureRequestBody
57609
+ * @default undefined
57610
+ * @type {function}
57611
+ */
57612
+ ConfigReader.addOption(NETWORK_LOGS_CONFIG_CAPTURE_REQUEST_BODY, [ConfigReader.sources.SNIPPET_SRC], undefined);
57613
+ /**
57614
+ * Callback function to process/modify response bodies before capturing them. If not provided, the response body will not be captured.
57615
+ *
57616
+ * @access public
57617
+ * @category Config/Network Logs
57618
+ * @name networkLogs.captureResponseBody
57619
+ * @default undefined
57620
+ * @type {function}
57621
+ */
57622
+ ConfigReader.addOption(NETWORK_LOGS_CONFIG_CAPTURE_RESPONSE_BODY, [ConfigReader.sources.SNIPPET_SRC], undefined);
57623
+ }
57624
+ function processHeaderConfig(configHeaderName, targetHeaders) {
57625
+ const configHeaders = pluginAPI.ConfigReader.get(configHeaderName);
57626
+ if (!configHeaders || configHeaders.length === 0)
57627
+ return;
57628
+ globalPendo._.each(configHeaders, (header) => {
57629
+ if (!header || typeof header !== 'string')
57630
+ return;
57631
+ targetHeaders[header.toLowerCase()] = true;
57632
+ });
57633
+ }
57634
+ function setupBodyCallbacks() {
57635
+ const config = pluginAPI.ConfigReader.get(NETWORK_LOGS_CONFIG);
57636
+ if (!config)
57637
+ return;
57638
+ if (globalPendo._.isFunction(config.captureRequestBody)) {
57639
+ requestBodyCb = config.captureRequestBody;
57640
+ }
57641
+ if (globalPendo._.isFunction(config.captureResponseBody)) {
57642
+ responseBodyCb = config.captureResponseBody;
57643
+ }
57644
+ }
57645
+ function onPtmPaused() {
57646
+ isPtmPaused = true;
57647
+ }
57648
+ function onPtmUnpaused() {
57649
+ isPtmPaused = false;
57650
+ if (!buffer.isEmpty()) {
57651
+ for (const event of buffer.events) {
57652
+ pluginAPI.Events.eventCaptured.trigger(event);
57653
+ }
57654
+ send();
57655
+ }
57656
+ }
57657
+ function onAppHidden() {
57658
+ send({ hidden: true });
57659
+ }
57660
+ function onAppUnloaded() {
57661
+ send({ unload: true });
57662
+ }
57663
+ function startCapture() {
57664
+ pluginAPI.NetworkRequest.on({ request: handleRequest, response: handleResponse, error: handleError });
57665
+ }
57666
+ function teardown() {
57667
+ if (sendInterval) {
57668
+ clearInterval(sendInterval);
57669
+ }
57670
+ if (sendQueue) {
57671
+ sendQueue.stop();
57672
+ }
57673
+ if (buffer) {
57674
+ buffer.clear();
57675
+ }
57676
+ pluginAPI.NetworkRequest.off({ request: handleRequest, response: handleResponse, error: handleError });
57677
+ requestMap = {};
57678
+ pluginAPI.Events['ptm:paused'].off(onPtmPaused);
57679
+ pluginAPI.Events['ptm:unpaused'].off(onPtmUnpaused);
57680
+ pluginAPI.Events.appHidden.off(onAppHidden);
57681
+ pluginAPI.Events.appUnloaded.off(onAppUnloaded);
57682
+ }
57683
+ function handleRequest(request) {
57684
+ requestMap[request.requestId] = request;
57685
+ }
57686
+ function handleResponse(response) {
57687
+ if (!response)
57688
+ return;
57689
+ const request = requestMap[response.requestId];
57690
+ if (!request)
57691
+ return;
57692
+ // Skip capturing successful devlog events to avoid infinite loops
57693
+ if (request.url.indexOf(pendoDevlogBaseUrl) !== -1 && response.status === 200) {
57694
+ delete requestMap[response.requestId];
57695
+ return;
57696
+ }
57697
+ // push an empty network event to the buffer to ensure that we have a token available
57698
+ const networkEvent = {};
57699
+ const wasAccepted = buffer.push(networkEvent);
57700
+ if (!wasAccepted) {
57701
+ // Token limit reached, remove request from request map
57702
+ delete requestMap[response.requestId];
57703
+ return;
57704
+ }
57705
+ globalPendo._.extend(networkEvent, createNetworkEvent({
57706
+ request,
57707
+ response
57708
+ }));
57709
+ if (!isPtmPaused) {
57710
+ pluginAPI.Events.eventCaptured.trigger(networkEvent);
57711
+ }
57712
+ delete requestMap[response.requestId];
57713
+ }
57714
+ function handleError({ error, context }) {
57715
+ if (error.requestId && requestMap[error.requestId]) {
57716
+ delete requestMap[error.requestId];
57717
+ }
57718
+ if (context.indexOf('fetch') !== -1) {
57719
+ pluginAPI.log.debug('[NetworkCapture] Fetch tracking failed:', error);
57720
+ }
57721
+ if (context.indexOf('xhr') !== -1) {
57722
+ pluginAPI.log.debug('[NetworkCapture] XHR tracking failed:', error);
57723
+ }
57724
+ }
57725
+ function extractHeaders(headers, allowedHeaders) {
57726
+ const { keys, reduce } = globalPendo._;
57727
+ if (!headers || !allowedHeaders)
57728
+ return [];
57729
+ return reduce(keys(headers), (acc, key) => {
57730
+ const normalizedKey = key.toLowerCase();
57731
+ if (allowedHeaders[normalizedKey]) {
57732
+ acc.push(`${key}: ${headers[key]}`);
57733
+ }
57734
+ return acc;
57735
+ }, []);
57736
+ }
57737
+ function processBody(body, contentType) {
57738
+ if (!body || typeof body !== 'string')
57739
+ return '';
57740
+ const processedBody = maskSensitiveFields({ string: body, contentType, _: globalPendo._ });
57741
+ return truncate(processedBody, true);
57742
+ }
57743
+ function processRequestBody({ request }) {
57744
+ if (!request || !request.body || !requestBodyCb)
57745
+ return '';
57746
+ try {
57747
+ const body = requestBodyCb(request.body, { request });
57748
+ const contentType = globalPendo._.get(request, 'headers.content-type', '');
57749
+ return processBody(body, contentType);
57750
+ }
57751
+ catch (error) {
57752
+ pluginAPI.log.error('[NetworkCapture] Error processing request body', error);
57753
+ return '[Failed to process request body]';
57754
+ }
57755
+ }
57756
+ function processResponseBody({ response }) {
57757
+ if (!response || !response.body || !responseBodyCb)
57758
+ return '';
57759
+ try {
57760
+ const body = responseBodyCb(response.body, { response });
57761
+ const contentType = globalPendo._.get(response, 'headers.content-type', '');
57762
+ return processBody(body, contentType);
57763
+ }
57764
+ catch (error) {
57765
+ pluginAPI.log.error('[NetworkCapture] Error processing response body', error);
57766
+ return '[Failed to process response body]';
57767
+ }
57768
+ }
57769
+ function createNetworkEvent({ request, response }) {
57770
+ const devLogEnvelope = createDevLogEnvelope(pluginAPI, globalPendo);
57771
+ const requestHeaders = extractHeaders(request.headers, allowedRequestHeaders);
57772
+ const responseHeaders = extractHeaders(response.headers, allowedResponseHeaders);
57773
+ 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 });
57774
+ if (requestBodyCb) {
57775
+ networkEvent.devLogRequestBody = processRequestBody({ request });
57776
+ }
57777
+ if (responseBodyCb) {
57778
+ networkEvent.devLogResponseBody = processResponseBody({ response });
57779
+ }
57780
+ return networkEvent;
57781
+ }
57782
+ function send({ unload = false, hidden = false } = {}) {
57783
+ if (!buffer || buffer.isEmpty())
57784
+ return;
57785
+ if (!globalPendo.isSendingEvents()) {
57786
+ buffer.clear();
57787
+ return;
57788
+ }
57789
+ const jzb = buffer.pack();
57790
+ const queryParams = {
57791
+ v: globalPendo.VERSION,
57792
+ ct: pluginAPI.util.getNow()
57793
+ };
57794
+ const url = pluginAPI.transmit.buildBaseDataUrl(DEV_LOG_TYPE, globalPendo.apiKey, queryParams);
57795
+ if (unload || hidden) {
57796
+ sendQueue.drain([{ url, jzb }], unload);
57797
+ }
57798
+ else {
57799
+ sendQueue.push({ url, jzb });
57800
+ }
57801
+ }
57802
+ }
57803
+
57804
+ export { ConsoleCapture, Feedback, GuideMarkdown, NetworkCapture, createReplayPlugin as Replay, TextCapture, VocPortal, loadAgent };