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