@pendo/agent 2.345.0 → 2.346.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/dist/dom.esm.js +5 -1
- package/dist/pendo.debugger.min.js +11 -11
- package/dist/pendo.module.js +216 -85
- package/dist/pendo.module.min.js +11 -10
- package/dist/pendo.preview.min.js +5 -5
- package/dist/servers.json +8 -8
- package/package.json +1 -1
package/dist/pendo.module.js
CHANGED
|
@@ -668,7 +668,7 @@ function loadStagingAgent(config, pendo, debuggingEnabled) {
|
|
|
668
668
|
var altAgentUrl = getAlternateAgentUrl(config, true, debuggingEnabled);
|
|
669
669
|
if (altAgentUrl) {
|
|
670
670
|
setupMethodCapture(pendo);
|
|
671
|
-
includeScript(altAgentUrl, pendo);
|
|
671
|
+
includeScript(altAgentUrl, pendo, config.pendoGlobalKey);
|
|
672
672
|
return true;
|
|
673
673
|
}
|
|
674
674
|
}
|
|
@@ -679,7 +679,7 @@ function loadUnminifiedAgent(config, pendo, debuggingEnabled) {
|
|
|
679
679
|
var altAgentUrl = getAlternateAgentUrl(config, false, debuggingEnabled);
|
|
680
680
|
if (altAgentUrl) {
|
|
681
681
|
setupMethodCapture(pendo);
|
|
682
|
-
includeScript(altAgentUrl, pendo);
|
|
682
|
+
includeScript(altAgentUrl, pendo, config.pendoGlobalKey);
|
|
683
683
|
return true;
|
|
684
684
|
}
|
|
685
685
|
}
|
|
@@ -688,11 +688,16 @@ function loadUnminifiedAgent(config, pendo, debuggingEnabled) {
|
|
|
688
688
|
function loadAlternateAgent(config, pendo, debuggingEnabled) {
|
|
689
689
|
return loadStagingAgent(config, pendo, debuggingEnabled) || loadUnminifiedAgent(config, pendo, debuggingEnabled);
|
|
690
690
|
}
|
|
691
|
-
function includeScript(scriptUrl, pendo) {
|
|
691
|
+
function includeScript(scriptUrl, pendo, globalKey) {
|
|
692
692
|
var scriptTagName = 'script';
|
|
693
693
|
var stagingScriptTag = document.createElement(scriptTagName);
|
|
694
694
|
stagingScriptTag.async = true;
|
|
695
695
|
stagingScriptTag.src = getPolicy(pendo).createScriptURL(scriptUrl);
|
|
696
|
+
// The alternate agent reads its global key from its own script tag, and its
|
|
697
|
+
// bundled PendoConfig doesn't carry the key the snippet set.
|
|
698
|
+
if (globalKey) {
|
|
699
|
+
stagingScriptTag.setAttribute('data-pendo-global-key', globalKey);
|
|
700
|
+
}
|
|
696
701
|
var otherScriptTag = document.getElementsByTagName(scriptTagName)[0];
|
|
697
702
|
if (otherScriptTag && otherScriptTag.parentNode) {
|
|
698
703
|
otherScriptTag.parentNode.insertBefore(stagingScriptTag, otherScriptTag);
|
|
@@ -4009,6 +4014,9 @@ function shouldWrapHashChange() {
|
|
|
4009
4014
|
function isMobileUserAgent() {
|
|
4010
4015
|
return (/Android|webOS|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i).test(getUA());
|
|
4011
4016
|
}
|
|
4017
|
+
function supportsPopover() {
|
|
4018
|
+
return !!window.HTMLElement && isNativeCode(window.HTMLElement.prototype.showPopover);
|
|
4019
|
+
}
|
|
4012
4020
|
function isSafari(userAgent = getUA()) {
|
|
4013
4021
|
return userAgent.indexOf('Safari') >= 0 && !isChromium(userAgent);
|
|
4014
4022
|
}
|
|
@@ -4021,6 +4029,7 @@ function isChromium(userAgent = getUA()) {
|
|
|
4021
4029
|
const sniffer = {
|
|
4022
4030
|
supportsHistoryApi: shouldWrapNativeHistory,
|
|
4023
4031
|
supportsHashChange: shouldWrapHashChange,
|
|
4032
|
+
supportsPopover,
|
|
4024
4033
|
hasEvent(event) {
|
|
4025
4034
|
// IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
|
|
4026
4035
|
// it. In particular the event is not fired when backspace or delete key are pressed or
|
|
@@ -4129,8 +4138,8 @@ let SERVER = '';
|
|
|
4129
4138
|
let ASSET_HOST = '';
|
|
4130
4139
|
let ASSET_PATH = '';
|
|
4131
4140
|
let DESIGNER_SERVER = '';
|
|
4132
|
-
let VERSION = '2.
|
|
4133
|
-
let PACKAGE_VERSION = '2.
|
|
4141
|
+
let VERSION = '2.346.0_';
|
|
4142
|
+
let PACKAGE_VERSION = '2.346.0';
|
|
4134
4143
|
let LOADER = 'xhr';
|
|
4135
4144
|
/* eslint-enable web-sdk-eslint-rules/no-gulp-env-references */
|
|
4136
4145
|
/**
|
|
@@ -23855,6 +23864,78 @@ var backdropVisibilityStep = {
|
|
|
23855
23864
|
script: backdropVisibilityRuntimeScript
|
|
23856
23865
|
};
|
|
23857
23866
|
|
|
23867
|
+
// ":modal" also matches a fullscreen element, which cannot take children
|
|
23868
|
+
const MODAL_DIALOG = 'dialog:modal';
|
|
23869
|
+
function queryModalDialogs() {
|
|
23870
|
+
try {
|
|
23871
|
+
return document.querySelectorAll(MODAL_DIALOG);
|
|
23872
|
+
}
|
|
23873
|
+
catch (e) {
|
|
23874
|
+
return [];
|
|
23875
|
+
}
|
|
23876
|
+
}
|
|
23877
|
+
function closestModalDialog(element) {
|
|
23878
|
+
try {
|
|
23879
|
+
return element && _.isFunction(element.closest) ? element.closest(MODAL_DIALOG) : null;
|
|
23880
|
+
}
|
|
23881
|
+
catch (e) {
|
|
23882
|
+
return null;
|
|
23883
|
+
}
|
|
23884
|
+
}
|
|
23885
|
+
function queryConfiguredSelector(step) {
|
|
23886
|
+
const selector = _.get(step, 'attributes.popover.selector');
|
|
23887
|
+
if (!selector)
|
|
23888
|
+
return null;
|
|
23889
|
+
try {
|
|
23890
|
+
return dom(selector)[0] || null;
|
|
23891
|
+
}
|
|
23892
|
+
catch (e) {
|
|
23893
|
+
log.info('Invalid popover selector', { contexts: ['error', 'guide', 'render'] });
|
|
23894
|
+
return null;
|
|
23895
|
+
}
|
|
23896
|
+
}
|
|
23897
|
+
function findHost(step) {
|
|
23898
|
+
const host = queryConfiguredSelector(step) ||
|
|
23899
|
+
closestModalDialog(step.targetElement) ||
|
|
23900
|
+
_.last(queryModalDialogs());
|
|
23901
|
+
// a host inside a shadow root would change the guide's root node
|
|
23902
|
+
return host && host.getRootNode() === document ? host : null;
|
|
23903
|
+
}
|
|
23904
|
+
function promoteToPopover(element) {
|
|
23905
|
+
element.setAttribute('popover', 'manual');
|
|
23906
|
+
try {
|
|
23907
|
+
element.showPopover();
|
|
23908
|
+
}
|
|
23909
|
+
catch (e) {
|
|
23910
|
+
element.removeAttribute('popover');
|
|
23911
|
+
log.info('Failed to render guide as popover', { contexts: ['error', 'guide', 'render'] });
|
|
23912
|
+
}
|
|
23913
|
+
}
|
|
23914
|
+
// Registered ahead of backdrop-visibility-step and autofocus-step, which mark the siblings of the
|
|
23915
|
+
// guide's ancestors: the guide must already be inside the dialog or the dialog ends up hidden.
|
|
23916
|
+
function popoverRuntimeScript(step, guide, pendo) {
|
|
23917
|
+
if (pendo.designer || !step.guideElement)
|
|
23918
|
+
return;
|
|
23919
|
+
if (!_.get(step, 'attributes.popover.enabled') || !sniffer.supportsPopover())
|
|
23920
|
+
return;
|
|
23921
|
+
if (_.get(guide, 'attributes.resourceCenter') || _.get(guide, 'attributes.embedConfig'))
|
|
23922
|
+
return;
|
|
23923
|
+
const guideContainer = step.guideElement.find(`#${step.containerId}`);
|
|
23924
|
+
if (!guideContainer.length)
|
|
23925
|
+
return;
|
|
23926
|
+
// positioned in document coordinates, which a fixed root in the top layer breaks
|
|
23927
|
+
if (guideContainer.attr('data-vertical-alignment') === 'Relative to Element')
|
|
23928
|
+
return;
|
|
23929
|
+
const host = findHost(step);
|
|
23930
|
+
if (!host)
|
|
23931
|
+
return;
|
|
23932
|
+
host.appendChild(step.guideElement[0]);
|
|
23933
|
+
promoteToPopover(step.guideElement[0]);
|
|
23934
|
+
}
|
|
23935
|
+
var popoverStep = {
|
|
23936
|
+
script: popoverRuntimeScript
|
|
23937
|
+
};
|
|
23938
|
+
|
|
23858
23939
|
function isTooltip({ _ }, step) {
|
|
23859
23940
|
const tooltip = 'tooltip';
|
|
23860
23941
|
return _.get(step, 'attributes.calculatedType') === tooltip ||
|
|
@@ -24011,6 +24092,7 @@ function registerPostrenderScripts(runtime) {
|
|
|
24011
24092
|
_.each([
|
|
24012
24093
|
advanceOnElement,
|
|
24013
24094
|
announcementMarkSeen,
|
|
24095
|
+
popoverStep,
|
|
24014
24096
|
autofocusStep,
|
|
24015
24097
|
backdropVisibilityStep,
|
|
24016
24098
|
repositionOnScroll,
|
|
@@ -25966,8 +26048,14 @@ var MESSAGE_ENDED = 'pendo-live-preview::ended';
|
|
|
25966
26048
|
var MESSAGE_END_ACK = 'pendo-live-preview::end-ack';
|
|
25967
26049
|
var RELAY_END_MESSAGE_TIMEOUT = 2000;
|
|
25968
26050
|
var activeRelay = null;
|
|
26051
|
+
// event.source alone is not enough: an ancestor page can navigate the relay frame to its own
|
|
26052
|
+
// document and keep the same window object, so the sender's origin has to be the relay's too.
|
|
26053
|
+
// An unknown relay origin ('*') cannot be matched, so it never authenticates a message.
|
|
25969
26054
|
function isRelayMessage(event, relay) {
|
|
25970
|
-
return
|
|
26055
|
+
return relay.origin !== '*' &&
|
|
26056
|
+
event.origin === relay.origin &&
|
|
26057
|
+
event.source === relay.frame.contentWindow &&
|
|
26058
|
+
!!event.data;
|
|
25971
26059
|
}
|
|
25972
26060
|
function sendConfig(relay) {
|
|
25973
26061
|
if (!relay.frame.contentWindow)
|
|
@@ -28502,7 +28590,8 @@ class NetworkRequestIntercept {
|
|
|
28502
28590
|
return MAX_RESPONSE_SIZE_MESSAGE;
|
|
28503
28591
|
}
|
|
28504
28592
|
}
|
|
28505
|
-
const
|
|
28593
|
+
const canStream = config.body && _.isFunction(config.body.getReader) && typeof TextDecoder !== 'undefined';
|
|
28594
|
+
const text = canStream ? yield this.readBodyStream(config.body) : yield config.text();
|
|
28506
28595
|
if (text.length > MAX_RESPONSE_SIZE) {
|
|
28507
28596
|
return MAX_RESPONSE_SIZE_MESSAGE;
|
|
28508
28597
|
}
|
|
@@ -28513,6 +28602,26 @@ class NetworkRequestIntercept {
|
|
|
28513
28602
|
}
|
|
28514
28603
|
});
|
|
28515
28604
|
}
|
|
28605
|
+
// Reads chunk by chunk so a long or never-ending stream stops buffering once it passes the cap
|
|
28606
|
+
readBodyStream(body) {
|
|
28607
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
28608
|
+
const reader = body.getReader();
|
|
28609
|
+
const decoder = new TextDecoder();
|
|
28610
|
+
let text = '';
|
|
28611
|
+
let bytesRead = 0;
|
|
28612
|
+
let chunk = yield reader.read();
|
|
28613
|
+
while (!chunk.done) {
|
|
28614
|
+
bytesRead += chunk.value.byteLength;
|
|
28615
|
+
if (bytesRead > MAX_RESPONSE_SIZE) {
|
|
28616
|
+
reader.cancel().catch(_.noop);
|
|
28617
|
+
return MAX_RESPONSE_SIZE_MESSAGE;
|
|
28618
|
+
}
|
|
28619
|
+
text += decoder.decode(chunk.value, { stream: true });
|
|
28620
|
+
chunk = yield reader.read();
|
|
28621
|
+
}
|
|
28622
|
+
return text + decoder.decode();
|
|
28623
|
+
});
|
|
28624
|
+
}
|
|
28516
28625
|
extractConfigHeaders(config) {
|
|
28517
28626
|
let headers = {};
|
|
28518
28627
|
if (config.headers) {
|
|
@@ -28585,33 +28694,7 @@ class NetworkRequestIntercept {
|
|
|
28585
28694
|
// Make the actual fetch call and capture response
|
|
28586
28695
|
try {
|
|
28587
28696
|
const res = yield networkInterceptor._originalFetch.apply(this, args);
|
|
28588
|
-
|
|
28589
|
-
const responseClone = res.clone();
|
|
28590
|
-
try {
|
|
28591
|
-
// Capture response data
|
|
28592
|
-
const responseBody = yield networkInterceptor.safelyReadBody(responseClone, 'response');
|
|
28593
|
-
const { status, statusText } = res;
|
|
28594
|
-
const headers = networkInterceptor.extractConfigHeaders(res);
|
|
28595
|
-
_.each(networkInterceptor.callbackFns, ({ response }) => {
|
|
28596
|
-
if (_.isFunction(response)) {
|
|
28597
|
-
response({
|
|
28598
|
-
requestId,
|
|
28599
|
-
status,
|
|
28600
|
-
statusText,
|
|
28601
|
-
body: responseBody,
|
|
28602
|
-
headers,
|
|
28603
|
-
url: transformedUrl,
|
|
28604
|
-
method
|
|
28605
|
-
});
|
|
28606
|
-
}
|
|
28607
|
-
});
|
|
28608
|
-
}
|
|
28609
|
-
catch (e) {
|
|
28610
|
-
_.each(networkInterceptor.callbackFns, ({ error }) => {
|
|
28611
|
-
if (_.isFunction(error))
|
|
28612
|
-
error({ context: 'fetchResponse', error: e });
|
|
28613
|
-
});
|
|
28614
|
-
}
|
|
28697
|
+
networkInterceptor.captureResponse(res, { requestId, url: transformedUrl, method });
|
|
28615
28698
|
return res;
|
|
28616
28699
|
}
|
|
28617
28700
|
catch (error) {
|
|
@@ -28635,6 +28718,29 @@ class NetworkRequestIntercept {
|
|
|
28635
28718
|
});
|
|
28636
28719
|
};
|
|
28637
28720
|
}
|
|
28721
|
+
// Not awaited by the fetch wrapper: the app gets its response before the body is read
|
|
28722
|
+
captureResponse(res, { requestId, url, method }) {
|
|
28723
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
28724
|
+
try {
|
|
28725
|
+
// Clone synchronously, before the app can consume the body
|
|
28726
|
+
const responseClone = res.clone();
|
|
28727
|
+
const body = yield this.safelyReadBody(responseClone, 'response');
|
|
28728
|
+
const { status, statusText } = res;
|
|
28729
|
+
const headers = this.extractConfigHeaders(res);
|
|
28730
|
+
_.each(this.callbackFns, ({ response }) => {
|
|
28731
|
+
if (_.isFunction(response)) {
|
|
28732
|
+
response({ requestId, status, statusText, body, headers, url, method });
|
|
28733
|
+
}
|
|
28734
|
+
});
|
|
28735
|
+
}
|
|
28736
|
+
catch (e) {
|
|
28737
|
+
_.each(this.callbackFns, ({ error }) => {
|
|
28738
|
+
if (_.isFunction(error))
|
|
28739
|
+
error({ context: 'fetchResponse', error: e });
|
|
28740
|
+
});
|
|
28741
|
+
}
|
|
28742
|
+
});
|
|
28743
|
+
}
|
|
28638
28744
|
patchXHR() {
|
|
28639
28745
|
const networkInterceptor = this;
|
|
28640
28746
|
networkInterceptor._originalSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
|
|
@@ -42167,7 +42273,7 @@ class PromptPlugin {
|
|
|
42167
42273
|
});
|
|
42168
42274
|
const currentlyActiveIds = this._.map(this._.filter(this.prompts, prompt => prompt.isActive), prompt => prompt.id);
|
|
42169
42275
|
this._.each(allAgents, aiAgent => {
|
|
42170
|
-
if (this.isConversationAgent(aiAgent)) {
|
|
42276
|
+
if (this.isConversationAgent(aiAgent) || this.isNetworkAgent(aiAgent)) {
|
|
42171
42277
|
return;
|
|
42172
42278
|
}
|
|
42173
42279
|
const isCurrentlyActive = this._.contains(currentlyActiveIds, aiAgent.id);
|
|
@@ -42192,30 +42298,21 @@ class PromptPlugin {
|
|
|
42192
42298
|
isConversationAgent(agent) {
|
|
42193
42299
|
return agent.preset && agent.preset != 'chatgpt';
|
|
42194
42300
|
}
|
|
42301
|
+
// network tracking is not supported; these agents are ignored rather than falling back to DOM.
|
|
42302
|
+
isNetworkAgent(agent) {
|
|
42303
|
+
return agent.trackingMethod === 'network';
|
|
42304
|
+
}
|
|
42195
42305
|
onConfigLoaded(aiAgents = []) {
|
|
42196
42306
|
if (!aiAgents || !this._.isArray(aiAgents) || aiAgents.length === 0) {
|
|
42197
42307
|
return;
|
|
42198
42308
|
}
|
|
42199
|
-
const networkAgents = [];
|
|
42200
42309
|
const domAgents = [];
|
|
42201
42310
|
for (const agent of aiAgents) {
|
|
42202
|
-
if (this.isConversationAgent(agent)) {
|
|
42311
|
+
if (this.isConversationAgent(agent) || this.isNetworkAgent(agent)) {
|
|
42203
42312
|
continue;
|
|
42204
42313
|
}
|
|
42205
|
-
if
|
|
42206
|
-
|
|
42207
|
-
}
|
|
42208
|
-
else if (agent.trackingMethod === 'dom') {
|
|
42209
|
-
domAgents.push(agent);
|
|
42210
|
-
}
|
|
42211
|
-
else {
|
|
42212
|
-
// Default to DOM if no trackingMethod specified
|
|
42213
|
-
domAgents.push(agent);
|
|
42214
|
-
}
|
|
42215
|
-
}
|
|
42216
|
-
if (networkAgents.length > 0) {
|
|
42217
|
-
this.networkAgents = networkAgents;
|
|
42218
|
-
this.api.NetworkRequest.on({ request: this.handleRequest, error: this.handleError });
|
|
42314
|
+
// Default to DOM if no trackingMethod specified
|
|
42315
|
+
domAgents.push(agent);
|
|
42219
42316
|
}
|
|
42220
42317
|
// Cache agents for fast lookup
|
|
42221
42318
|
if (!this.agentsCache) {
|
|
@@ -42236,29 +42333,6 @@ class PromptPlugin {
|
|
|
42236
42333
|
}
|
|
42237
42334
|
});
|
|
42238
42335
|
}
|
|
42239
|
-
handleRequest({ method, url, body }) {
|
|
42240
|
-
const currentUrl = getNormalizedUrl();
|
|
42241
|
-
this._.each(this.networkAgents, aiAgent => {
|
|
42242
|
-
if (!this.testPageRule(aiAgent.pageRule, currentUrl, aiAgent.id)) {
|
|
42243
|
-
return;
|
|
42244
|
-
}
|
|
42245
|
-
const originalBody = body || '';
|
|
42246
|
-
this.api.analytics.collectEvent('prompt', {
|
|
42247
|
-
prompt: originalBody,
|
|
42248
|
-
agentId: aiAgent.id,
|
|
42249
|
-
promptType: method,
|
|
42250
|
-
url
|
|
42251
|
-
}, undefined, 'agentic');
|
|
42252
|
-
});
|
|
42253
|
-
}
|
|
42254
|
-
handleError({ error, context }) {
|
|
42255
|
-
if (context == 'fetch') {
|
|
42256
|
-
this.api.log.debug('[PromptPlugin] Fetch tracking failed:', error);
|
|
42257
|
-
}
|
|
42258
|
-
if (context == 'xhr') {
|
|
42259
|
-
this.api.log.debug('[PromptPlugin] XHR tracking failed:', error);
|
|
42260
|
-
}
|
|
42261
|
-
}
|
|
42262
42336
|
observePrompt(config) {
|
|
42263
42337
|
const { id, cssSelectors } = config;
|
|
42264
42338
|
// Check if agent already exists
|
|
@@ -42297,7 +42371,6 @@ class PromptPlugin {
|
|
|
42297
42371
|
if (this.agentsCache) {
|
|
42298
42372
|
this.agentsCache.clear();
|
|
42299
42373
|
}
|
|
42300
|
-
this.api.NetworkRequest.off({ request: this.handleRequest, error: this.handleError });
|
|
42301
42374
|
}
|
|
42302
42375
|
}
|
|
42303
42376
|
var PromptAnalytics = new PromptPlugin();
|
|
@@ -43650,6 +43723,13 @@ function encodeMetadataForUrl(value, urlBeforePlaceholder = '') {
|
|
|
43650
43723
|
}
|
|
43651
43724
|
return window.encodeURI(str);
|
|
43652
43725
|
}
|
|
43726
|
+
// Resolve through the browser's own URL parser: it strips tabs/newlines inside the scheme
|
|
43727
|
+
// (e.g. "java\tscript:"), which a regex over the raw string would miss.
|
|
43728
|
+
function hasDangerousUrlScheme(url) {
|
|
43729
|
+
const anchor = document.createElement('a');
|
|
43730
|
+
anchor.href = url;
|
|
43731
|
+
return DANGEROUS_URL_SCHEME.test(anchor.protocol);
|
|
43732
|
+
}
|
|
43653
43733
|
|
|
43654
43734
|
const MetadataSubstitution = {
|
|
43655
43735
|
name: 'MetadataSubstitution',
|
|
@@ -43736,27 +43816,37 @@ function processPlaceholder(placeholder, pendo) {
|
|
|
43736
43816
|
let match;
|
|
43737
43817
|
const { data, target } = placeholder;
|
|
43738
43818
|
const subRegex = new RegExp(guideMarkdownUtil.substitutionRegex);
|
|
43819
|
+
const isUrlTarget = target === 'href' || target === 'value';
|
|
43820
|
+
const startedWithDangerousScheme = isUrlTarget && hasDangerousUrlScheme(getRawTargetValue(data, target));
|
|
43739
43821
|
while ((match = matchPlaceholder(placeholder, subRegex))) {
|
|
43740
43822
|
const usedArrayPath = Array.isArray(match) && match.length >= 2;
|
|
43741
43823
|
const currentStr = target === 'textContent'
|
|
43742
43824
|
? data[target]
|
|
43743
|
-
: decodeURI((data
|
|
43825
|
+
: decodeURI(getRawTargetValue(data, target));
|
|
43744
43826
|
const matched = usedArrayPath ? match : subRegex.exec(currentStr);
|
|
43745
43827
|
if (!matched)
|
|
43746
43828
|
continue;
|
|
43747
43829
|
const mdValue = getSubstituteValue(matched, pendo);
|
|
43748
43830
|
substituteMetadataByTarget(data, target, mdValue, matched);
|
|
43831
|
+
// Substituted values can carry new placeholders into the next pass, so a dangerous
|
|
43832
|
+
// scheme can be assembled across passes. Check the whole URL, not each fragment.
|
|
43833
|
+
if (isUrlTarget && !startedWithDangerousScheme && hasDangerousUrlScheme(getRawTargetValue(data, target))) {
|
|
43834
|
+
data[target] = '';
|
|
43835
|
+
return;
|
|
43836
|
+
}
|
|
43749
43837
|
}
|
|
43750
43838
|
}
|
|
43839
|
+
function getRawTargetValue(data, target) {
|
|
43840
|
+
return data.getAttribute && (target === 'href' || target === 'value')
|
|
43841
|
+
? (data.getAttribute(target) || '')
|
|
43842
|
+
: data[target];
|
|
43843
|
+
}
|
|
43751
43844
|
function matchPlaceholder(placeholder, regex) {
|
|
43752
43845
|
if (placeholder.target === 'textContent') {
|
|
43753
43846
|
return placeholder.data[placeholder.target].match(regex);
|
|
43754
43847
|
}
|
|
43755
43848
|
else {
|
|
43756
|
-
|
|
43757
|
-
? (placeholder.data.getAttribute(placeholder.target) || '')
|
|
43758
|
-
: placeholder.data[placeholder.target];
|
|
43759
|
-
return decodeURI(raw).match(regex);
|
|
43849
|
+
return decodeURI(getRawTargetValue(placeholder.data, placeholder.target)).match(regex);
|
|
43760
43850
|
}
|
|
43761
43851
|
}
|
|
43762
43852
|
function getMetadataValueCaseInsensitive(metadata, type, property) {
|
|
@@ -53413,6 +53503,7 @@ function createReplayPlugin() {
|
|
|
53413
53503
|
/**
|
|
53414
53504
|
* limit of number of keys in an object
|
|
53415
53505
|
* if an object contains more keys than this limit, we would call its toString function directly
|
|
53506
|
+
* arrays longer than this limit are cut down to their first NUM_OF_KEYS_LIMIT elements
|
|
53416
53507
|
*/
|
|
53417
53508
|
const NUM_OF_KEYS_LIMIT = 50;
|
|
53418
53509
|
/**
|
|
@@ -53484,8 +53575,9 @@ function shouldIgnore(_obj) {
|
|
|
53484
53575
|
/**
|
|
53485
53576
|
* stringify any argument
|
|
53486
53577
|
* @param arg - the arg to stringify
|
|
53578
|
+
* @param maxLength - stop serializing once this many characters have been visited
|
|
53487
53579
|
*/
|
|
53488
|
-
function stringify(arg) {
|
|
53580
|
+
function stringify(arg, maxLength = Infinity) {
|
|
53489
53581
|
if (arg === undefined)
|
|
53490
53582
|
return 'undefined';
|
|
53491
53583
|
// Handle primitives
|
|
@@ -53496,11 +53588,19 @@ function stringify(arg) {
|
|
|
53496
53588
|
return arg.toString();
|
|
53497
53589
|
return String(arg);
|
|
53498
53590
|
}
|
|
53591
|
+
let charsVisited = 0;
|
|
53499
53592
|
return JSON.stringify(arg, function (key, value) {
|
|
53593
|
+
if (charsVisited > maxLength)
|
|
53594
|
+
return '';
|
|
53595
|
+
const valueLength = typeof value === 'string' ? value.length : 0;
|
|
53596
|
+
charsVisited += key.length + valueLength;
|
|
53500
53597
|
if (value === null)
|
|
53501
53598
|
return value;
|
|
53502
53599
|
if (value === undefined)
|
|
53503
53600
|
return 'undefined';
|
|
53601
|
+
if (Array.isArray(value) && value.length > NUM_OF_KEYS_LIMIT) {
|
|
53602
|
+
return value.slice(0, NUM_OF_KEYS_LIMIT);
|
|
53603
|
+
}
|
|
53504
53604
|
// Handle objects
|
|
53505
53605
|
if (shouldIgnore(value)) {
|
|
53506
53606
|
return value.toString ? value.toString() : '[object Object]';
|
|
@@ -54127,8 +54227,11 @@ function ConsoleCapture() {
|
|
|
54127
54227
|
let isRecordingActive = false;
|
|
54128
54228
|
const CAPTURE_CONSOLE_CONFIG = 'captureConsoleLogs';
|
|
54129
54229
|
const ALWAYS_CAPTURE_ERRORS_CONFIG = 'alwaysCaptureDevLogErrors';
|
|
54230
|
+
const CONSOLE_LOG_LEVELS_CONFIG = 'consoleLogLevels';
|
|
54130
54231
|
const CONSOLE_METHODS = ['log', 'warn', 'error', 'info'];
|
|
54131
54232
|
const DEV_LOG_SUB_TYPE = 'console';
|
|
54233
|
+
const DEFAULT_CONSOLE_LOG_LEVELS = ['error', 'warn', 'info'];
|
|
54234
|
+
let consoleLogLevels = DEFAULT_CONSOLE_LOG_LEVELS;
|
|
54132
54235
|
// deduplicate logs
|
|
54133
54236
|
let lastLogKey = '';
|
|
54134
54237
|
return {
|
|
@@ -54147,6 +54250,8 @@ function ConsoleCapture() {
|
|
|
54147
54250
|
onPtmPaused,
|
|
54148
54251
|
onPtmUnpaused,
|
|
54149
54252
|
securityPolicyViolationFn,
|
|
54253
|
+
uncaughtErrorFn,
|
|
54254
|
+
unhandledRejectionFn,
|
|
54150
54255
|
get isCapturingConsoleLogs() {
|
|
54151
54256
|
return isCapturingConsoleLogs;
|
|
54152
54257
|
},
|
|
@@ -54166,10 +54271,12 @@ function ConsoleCapture() {
|
|
|
54166
54271
|
const { ConfigReader } = pluginAPI;
|
|
54167
54272
|
ConfigReader.addOption(CAPTURE_CONSOLE_CONFIG, [ConfigReader.sources.PENDO_CONFIG_SRC], false);
|
|
54168
54273
|
ConfigReader.addOption(ALWAYS_CAPTURE_ERRORS_CONFIG, [ConfigReader.sources.PENDO_CONFIG_SRC], false);
|
|
54274
|
+
ConfigReader.addOption(CONSOLE_LOG_LEVELS_CONFIG, [ConfigReader.sources.PENDO_CONFIG_SRC], DEFAULT_CONSOLE_LOG_LEVELS);
|
|
54169
54275
|
const captureConsoleEnabled = ConfigReader.get(CAPTURE_CONSOLE_CONFIG);
|
|
54170
54276
|
if (!captureConsoleEnabled)
|
|
54171
54277
|
return;
|
|
54172
54278
|
alwaysCaptureErrors = !!ConfigReader.get(ALWAYS_CAPTURE_ERRORS_CONFIG);
|
|
54279
|
+
consoleLogLevels = ConfigReader.get(CONSOLE_LOG_LEVELS_CONFIG);
|
|
54173
54280
|
globalPendo = pendo;
|
|
54174
54281
|
buffer = new DevlogBuffer(pendo, pluginAPI);
|
|
54175
54282
|
transport = new DevlogTransport(pendo, pluginAPI);
|
|
@@ -54230,6 +54337,8 @@ function ConsoleCapture() {
|
|
|
54230
54337
|
try {
|
|
54231
54338
|
addIntercepts();
|
|
54232
54339
|
pluginAPI.attachEventInternal(window, 'securitypolicyviolation', securityPolicyViolationFn);
|
|
54340
|
+
pluginAPI.attachEventInternal(window, 'error', uncaughtErrorFn);
|
|
54341
|
+
pluginAPI.attachEventInternal(window, 'unhandledrejection', unhandledRejectionFn);
|
|
54233
54342
|
}
|
|
54234
54343
|
catch (error) {
|
|
54235
54344
|
pluginAPI.log.error('Error setting up console capture intercepts: ', error);
|
|
@@ -54270,16 +54379,36 @@ function ConsoleCapture() {
|
|
|
54270
54379
|
createConsoleEvent([message], 'error', { skipStackTrace: true, skipScrubPII: true });
|
|
54271
54380
|
}
|
|
54272
54381
|
}
|
|
54382
|
+
function uncaughtErrorFn(evt) {
|
|
54383
|
+
if (!evt || typeof evt !== 'object')
|
|
54384
|
+
return;
|
|
54385
|
+
const rawMessage = evt.message || (evt.error && String(evt.error));
|
|
54386
|
+
if (!rawMessage)
|
|
54387
|
+
return;
|
|
54388
|
+
const message = rawMessage.indexOf('Uncaught') === 0 ? rawMessage : `Uncaught ${rawMessage}`;
|
|
54389
|
+
const stackTrace = evt.error && evt.error.stack;
|
|
54390
|
+
createConsoleEvent([stackTrace ? `${message}\n${stackTrace}` : message], 'error', { skipStackTrace: true });
|
|
54391
|
+
}
|
|
54392
|
+
function unhandledRejectionFn(evt) {
|
|
54393
|
+
if (!evt || typeof evt !== 'object')
|
|
54394
|
+
return;
|
|
54395
|
+
const { reason } = evt;
|
|
54396
|
+
const detail = reason && reason.stack ? reason.stack : reason;
|
|
54397
|
+
createConsoleEvent(['Uncaught (in promise)', detail], 'error', { skipStackTrace: true });
|
|
54398
|
+
}
|
|
54273
54399
|
function createConsoleEvent(args, methodName, { skipStackTrace = false, skipScrubPII = false } = {}) {
|
|
54274
54400
|
if (!isCapturingConsoleLogs || !args || args.length === 0 || !_.contains(CONSOLE_METHODS, methodName))
|
|
54275
54401
|
return;
|
|
54276
54402
|
if (isErrorOnlyCapture() && methodName !== 'error')
|
|
54277
54403
|
return;
|
|
54404
|
+
const devLogLevel = methodName === 'log' ? 'info' : methodName;
|
|
54405
|
+
if (!_.contains(consoleLogLevels, devLogLevel))
|
|
54406
|
+
return;
|
|
54278
54407
|
// stringify args
|
|
54279
54408
|
let message = _.compact(_.map(args, arg => {
|
|
54280
54409
|
let stringifiedArg;
|
|
54281
54410
|
try {
|
|
54282
|
-
stringifiedArg = stringify(arg);
|
|
54411
|
+
stringifiedArg = stringify(arg, MAX_LENGTH);
|
|
54283
54412
|
}
|
|
54284
54413
|
catch (error) {
|
|
54285
54414
|
pluginAPI.log.error('Error stringifying arg: ', error);
|
|
@@ -54303,10 +54432,10 @@ function ConsoleCapture() {
|
|
|
54303
54432
|
buffer.lastEvent.devLogCount++;
|
|
54304
54433
|
return;
|
|
54305
54434
|
}
|
|
54306
|
-
const devLogEnvelope = createDevLogEnvelope(pluginAPI, globalPendo);
|
|
54307
|
-
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 });
|
|
54308
54435
|
if (!buffer.hasTokenAvailable())
|
|
54309
|
-
return
|
|
54436
|
+
return;
|
|
54437
|
+
const devLogEnvelope = createDevLogEnvelope(pluginAPI, globalPendo);
|
|
54438
|
+
const consoleEvent = Object.assign(Object.assign({}, devLogEnvelope), { subType: DEV_LOG_SUB_TYPE, devLogLevel, devLogMessage: skipScrubPII ? message : scrubPII({ string: message, _ }), devLogTrace: stackTrace, devLogCount: 1 });
|
|
54310
54439
|
if (!isPtmPaused) {
|
|
54311
54440
|
pluginAPI.Events.eventCaptured.trigger(consoleEvent);
|
|
54312
54441
|
}
|
|
@@ -54364,6 +54493,8 @@ function ConsoleCapture() {
|
|
|
54364
54493
|
pluginAPI.Events['recording:unpaused'].off(recordingStarted);
|
|
54365
54494
|
pluginAPI.Events['recording:paused'].off(recordingStopped);
|
|
54366
54495
|
pluginAPI.detachEventInternal(window, 'securitypolicyviolation', securityPolicyViolationFn);
|
|
54496
|
+
pluginAPI.detachEventInternal(window, 'error', uncaughtErrorFn);
|
|
54497
|
+
pluginAPI.detachEventInternal(window, 'unhandledrejection', unhandledRejectionFn);
|
|
54367
54498
|
_.each(CONSOLE_METHODS, function (methodName) {
|
|
54368
54499
|
if (!console[methodName])
|
|
54369
54500
|
return _.noop;
|