@pendo/agent 2.298.1 → 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.1_';
3908
- let PACKAGE_VERSION = '2.298.1';
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
  }
@@ -33728,7 +33858,7 @@ const DebuggerModule = (() => {
33728
33858
  frameId: context.state.frameId
33729
33859
  });
33730
33860
  }
33731
- _.each(buffer.events, (event) => eventCapturedFn(event));
33861
+ _.each(buffer.events, (event) => store.dispatch('debugger/eventCaptured', event));
33732
33862
  },
33733
33863
  join: (context, data) => {
33734
33864
  if (context.state.frameId === data.frameId)
@@ -37505,11 +37635,16 @@ _.extend(debug, debugging);
37505
37635
 
37506
37636
  const waitForLeader = (Events, store, q) => {
37507
37637
  const buffer = {
37508
- events: []
37638
+ events: [],
37639
+ add: (evt) => {
37640
+ buffer.events.push(evt.data[0]);
37641
+ },
37642
+ clear: () => {
37643
+ buffer.events = [];
37644
+ Events.off('eventCaptured', buffer.add);
37645
+ }
37509
37646
  };
37510
- Events.on('eventCaptured', (evt) => {
37511
- buffer.events.push(evt);
37512
- });
37647
+ Events.on('eventCaptured', buffer.add);
37513
37648
  if (store.getters['frames/leaderExists']()) {
37514
37649
  return q.resolve(buffer);
37515
37650
  }
@@ -37535,6 +37670,7 @@ const DebuggerLauncher = (function () {
37535
37670
  if (store.getters['frames/isLeader']()) {
37536
37671
  startDebuggingModuleIfEnabled();
37537
37672
  }
37673
+ buffer.clear();
37538
37674
  });
37539
37675
  registerMessageHandler('pendo-debugger::launch', (data, msg) => {
37540
37676
  if (data.config.apiKey !== pendo.apiKey)
@@ -38195,7 +38331,7 @@ const EmbeddedGuides = (function () {
38195
38331
  return { guide, step };
38196
38332
  }
38197
38333
  function initializeEmbeddedGuides() {
38198
- restoreFromPreviouseGuides();
38334
+ restoreFromPreviousGuides();
38199
38335
  pluginApi.guides.registerDisplayableGuides('embeddedGuides', embeddedGuides);
38200
38336
  applyEmbeddedGuideBehaviors();
38201
38337
  }
@@ -38210,7 +38346,7 @@ const EmbeddedGuides = (function () {
38210
38346
  // When a guides payload is received, the embedded guides list is cleared and cached in _oldEmbeddedGuides.
38211
38347
  // This function replaces guides in the new payload with guides from the previous payload to prevent re-rendering.
38212
38348
  // Additionally, if guides from the previous payload are showing, and they are not in the new payload, this function hides them.
38213
- function restoreFromPreviouseGuides() {
38349
+ function restoreFromPreviousGuides() {
38214
38350
  _.each(_oldEmbeddedGuides, function (oldGuide) {
38215
38351
  const newGuideIndex = _.findIndex(embeddedGuides, function (guide) {
38216
38352
  return guide.id === oldGuide.id;
@@ -54684,9 +54820,7 @@ const ErrorStackParser = {
54684
54820
  // if a location was matched, pass it to extractLocation() otherwise pop the last token
54685
54821
  const locationParts = this.extractLocation(location ? location[1] : tokens.pop() || '');
54686
54822
  const functionName = tokens.join(' ') || undefined;
54687
- const fileName = ['eval', '<anonymous>'].indexOf(locationParts[0]) > -1
54688
- ? undefined
54689
- : locationParts[0];
54823
+ const fileName = (locationParts[0] === 'eval' || locationParts[0] === '<anonymous>') ? undefined : locationParts[0];
54690
54824
  frames.push(new StackFrame({
54691
54825
  functionName,
54692
54826
  fileName,
@@ -54728,17 +54862,29 @@ const PII_PATTERN = {
54728
54862
  ip: /\b(?:\d{1,3}\.){3}\d{1,3}\b/g,
54729
54863
  ssn: /\b\d{3}-\d{2}-\d{4}\b/g,
54730
54864
  creditCard: /\b(?:\d[ -]*?){13,16}\b/g,
54731
- httpsUrls: /https:\/\/[^\s]+/g
54865
+ httpsUrls: /https:\/\/[^\s]+/g,
54866
+ email: /\b\S+@[\w-]+\.[\w-]+\b/g
54732
54867
  };
54733
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))';
54734
54875
  /**
54735
54876
  * Truncates a string to the max length
54736
54877
  * @access private
54737
54878
  * @param {String} string
54738
54879
  * @returns {String}
54739
54880
  */
54740
- function truncate(string) {
54741
- 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);
54742
54888
  }
54743
54889
  /**
54744
54890
  * Generate a log key for a console log separated by pipes
@@ -54752,13 +54898,13 @@ function generateLogKey(methodName, message, stackTrace) {
54752
54898
  return `${methodName}|${message}|${stackTrace}`;
54753
54899
  }
54754
54900
  /**
54755
- * 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
54756
54902
  * @access private
54757
54903
  * @param {String} string
54758
54904
  * @returns {Boolean}
54759
54905
  */
54760
54906
  function mightContainPII(string) {
54761
- return /[\d]{2,}|https:\/\//.test(string);
54907
+ return /[\d]{2,}|https:\/\/|@/.test(string);
54762
54908
  }
54763
54909
  /**
54764
54910
  * Scrub basic PII from a string and replace it with a placeholder
@@ -54766,12 +54912,60 @@ function mightContainPII(string) {
54766
54912
  * @param {String} string
54767
54913
  * @returns {String}
54768
54914
  */
54769
- function scrubPII(string) {
54915
+ function scrubPII({ string, _ }) {
54770
54916
  if (!string || typeof string !== 'string')
54771
54917
  return string;
54772
54918
  if (!mightContainPII(string))
54773
54919
  return string;
54774
- 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;
54775
54969
  }
54776
54970
 
54777
54971
  /**
@@ -54946,11 +55140,10 @@ function createCspViolationMessage(blockedURI, directive, isReportOnly, original
54946
55140
  }
54947
55141
  }
54948
55142
 
54949
- const DEV_LOG_LEVELS = ['info', 'warn', 'error'];
54950
55143
  const TOKEN_MAX_SIZE = 100;
54951
55144
  const TOKEN_REFILL_RATE = 10;
54952
55145
  const TOKEN_REFRESH_INTERVAL = 1000;
54953
- class ConsoleCaptureBuffer {
55146
+ class DevlogBuffer {
54954
55147
  constructor(pendo, pluginAPI) {
54955
55148
  this.pendo = pendo;
54956
55149
  this.pluginAPI = pluginAPI;
@@ -54979,9 +55172,6 @@ class ConsoleCaptureBuffer {
54979
55172
  this.lastEvent = null;
54980
55173
  }
54981
55174
  push(event) {
54982
- const { devLogLevel } = event;
54983
- if (!this.pendo._.contains(DEV_LOG_LEVELS, devLogLevel))
54984
- return false;
54985
55175
  this.refillTokens();
54986
55176
  if (this.tokens === 0)
54987
55177
  return false;
@@ -55798,7 +55988,6 @@ var ConfigReader = (function () {
55798
55988
  * @type {boolean}
55799
55989
  */
55800
55990
  addOption('enableDebugEvents', [SNIPPET_SRC, PENDO_CONFIG_SRC]);
55801
- addOption('freeNPSData');
55802
55991
  addOption('eventPropertyConfigurations');
55803
55992
  /**
55804
55993
  * By default, a click event property rule can be matched against the element that is clicked on or its
@@ -56186,7 +56375,9 @@ var ConfigReader = (function () {
56186
56375
  addOption('enableAllEmbeddedGuideEvents', [PENDO_CONFIG_SRC], false);
56187
56376
  // Form Validation
56188
56377
  addOption('formValidation', [PENDO_CONFIG_SRC], false);
56378
+ // Performance Metrics
56189
56379
  addOption('performanceMetricsEnabled', [SNIPPET_SRC, PENDO_CONFIG_SRC], true);
56380
+ addOption('sendPerformanceMetrics', [SNIPPET_SRC, PENDO_CONFIG_SRC], false);
56190
56381
  }
56191
56382
  initializeOptions();
56192
56383
  var sourceGetters = {};
@@ -56975,7 +57166,7 @@ else {
56975
57166
  var SEND_INTERVAL = 2 * 60 * 1000;
56976
57167
  var ENCODED_EVENT_MAX_LENGTH = 1900;
56977
57168
 
56978
- class ConsoleTransport {
57169
+ class DevlogTransport {
56979
57170
  constructor(globalPendo, pluginAPI) {
56980
57171
  this.pendo = globalPendo;
56981
57172
  this.pluginAPI = pluginAPI;
@@ -57019,7 +57210,7 @@ class ConsoleTransport {
57019
57210
  }
57020
57211
  else if (options.keepalive && this.pluginAPI.transmit.sendBeacon.supported()) {
57021
57212
  const result = this.pluginAPI.transmit.sendBeacon(url, new Blob([options.body]));
57022
- 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'));
57023
57214
  }
57024
57215
  else {
57025
57216
  return this.pendo.ajax.post(url, options.body);
@@ -57054,6 +57245,17 @@ class ConsoleTransport {
57054
57245
  }
57055
57246
  }
57056
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
+
57057
57259
  function ConsoleCapture() {
57058
57260
  let pluginAPI;
57059
57261
  let _;
@@ -57065,7 +57267,6 @@ function ConsoleCapture() {
57065
57267
  let isPtmPaused;
57066
57268
  const CAPTURE_CONSOLE_CONFIG = 'captureConsoleLogs';
57067
57269
  const CONSOLE_METHODS = ['log', 'warn', 'error', 'info'];
57068
- const DEV_LOG_TYPE = 'devlog';
57069
57270
  const DEV_LOG_SUB_TYPE = 'console';
57070
57271
  // deduplicate logs
57071
57272
  let lastLogKey = '';
@@ -57101,8 +57302,8 @@ function ConsoleCapture() {
57101
57302
  if (!captureConsoleEnabled)
57102
57303
  return;
57103
57304
  globalPendo = pendo;
57104
- buffer = new ConsoleCaptureBuffer(pendo, pluginAPI);
57105
- transport = new ConsoleTransport(pendo, pluginAPI);
57305
+ buffer = new DevlogBuffer(pendo, pluginAPI);
57306
+ transport = new DevlogTransport(pendo, pluginAPI);
57106
57307
  sendQueue = new SendQueue(transport.sendRequest.bind(transport));
57107
57308
  sendInterval = setInterval(() => {
57108
57309
  if (!sendQueue.failed()) {
@@ -57135,16 +57336,6 @@ function ConsoleCapture() {
57135
57336
  send();
57136
57337
  }
57137
57338
  }
57138
- function createDevLogEnvelope() {
57139
- return {
57140
- visitorId: globalPendo.get_visitor_id(),
57141
- accountId: globalPendo.get_account_id(),
57142
- type: DEV_LOG_TYPE,
57143
- subType: DEV_LOG_SUB_TYPE,
57144
- browser_time: pluginAPI.util.getNow(),
57145
- url: globalPendo.url.get()
57146
- };
57147
- }
57148
57339
  function readyHandler() {
57149
57340
  addIntercepts();
57150
57341
  pluginAPI.attachEventInternal(window, 'securitypolicyviolation', securityPolicyViolationFn);
@@ -57180,7 +57371,7 @@ function ConsoleCapture() {
57180
57371
  }
57181
57372
  }
57182
57373
  function createConsoleEvent(args, methodName, { skipStackTrace = false, skipScrubPII = false } = {}) {
57183
- if (!args || args.length === 0)
57374
+ if (!args || args.length === 0 || !_.contains(CONSOLE_METHODS, methodName))
57184
57375
  return;
57185
57376
  // stringify args
57186
57377
  let message = _.compact(_.map(args, arg => {
@@ -57210,8 +57401,8 @@ function ConsoleCapture() {
57210
57401
  buffer.lastEvent.devLogCount++;
57211
57402
  return;
57212
57403
  }
57213
- const devLogEnvelope = createDevLogEnvelope();
57214
- 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 });
57215
57406
  const wasAccepted = buffer.push(consoleEvent);
57216
57407
  if (wasAccepted) {
57217
57408
  if (!isPtmPaused) {
@@ -57247,7 +57438,7 @@ function ConsoleCapture() {
57247
57438
  v: globalPendo.VERSION,
57248
57439
  ct: pluginAPI.util.getNow()
57249
57440
  };
57250
- const url = pluginAPI.transmit.buildBaseDataUrl('devlog', globalPendo.apiKey, queryParams);
57441
+ const url = pluginAPI.transmit.buildBaseDataUrl(DEV_LOG_TYPE, globalPendo.apiKey, queryParams);
57251
57442
  const jzb = buffer.pack();
57252
57443
  if (!jzb)
57253
57444
  return;
@@ -57285,4 +57476,329 @@ function ConsoleCapture() {
57285
57476
  }
57286
57477
  }
57287
57478
 
57288
- 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 };