@pendo/agent 2.337.3 → 2.338.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 +9 -2
- package/dist/pendo.debugger.min.js +13 -8
- package/dist/pendo.module.js +198 -34
- package/dist/pendo.module.min.js +9 -9
- package/dist/pendo.preview.min.js +5 -5
- package/dist/replay.worker.min.js +1 -1
- package/dist/servers.json +8 -8
- package/package.json +1 -1
package/dist/pendo.module.js
CHANGED
|
@@ -430,6 +430,12 @@ function getPolicy(pendo) {
|
|
|
430
430
|
return policy;
|
|
431
431
|
}
|
|
432
432
|
|
|
433
|
+
function escapeRegExp(string) {
|
|
434
|
+
if (!string)
|
|
435
|
+
return;
|
|
436
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
437
|
+
}
|
|
438
|
+
|
|
433
439
|
var STAGING_SERVER_HASHES = 'stagingServerHashes';
|
|
434
440
|
var STAGING_DOMAINS = 'stagingDomains';
|
|
435
441
|
var pendo$1;
|
|
@@ -515,7 +521,9 @@ function urlMatchesPatterns(url, patterns = [], hashes = []) {
|
|
|
515
521
|
}
|
|
516
522
|
}
|
|
517
523
|
if (typeof pattern === 'string') {
|
|
518
|
-
|
|
524
|
+
// String entries are literal origins/hosts, not patterns. Escape
|
|
525
|
+
// them so a '.' can't act as a wildcard and match a look-alike domain.
|
|
526
|
+
pattern = new RegExp('^' + escapeRegExp(pattern) + '$');
|
|
519
527
|
}
|
|
520
528
|
if (pattern instanceof RegExp && pattern.test(url)) {
|
|
521
529
|
return true;
|
|
@@ -3916,7 +3924,13 @@ function isMobileUserAgent() {
|
|
|
3916
3924
|
return (/Android|webOS|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i).test(getUA());
|
|
3917
3925
|
}
|
|
3918
3926
|
function isSafari(userAgent = getUA()) {
|
|
3919
|
-
return userAgent.indexOf('Safari') >= 0 && !userAgent
|
|
3927
|
+
return userAgent.indexOf('Safari') >= 0 && !isChromium(userAgent);
|
|
3928
|
+
}
|
|
3929
|
+
// Every Chromium UA carries a trailing Safari/ token, so Chrome, Edge, Opera,
|
|
3930
|
+
// Brave and Electron all have to be excluded from isSafari(). Chromium builds
|
|
3931
|
+
// report Chromium/ without a Chrome/ token, hence both checks.
|
|
3932
|
+
function isChromium(userAgent = getUA()) {
|
|
3933
|
+
return userAgent.indexOf('Chrome') >= 0 || userAgent.indexOf('Chromium') >= 0;
|
|
3920
3934
|
}
|
|
3921
3935
|
const sniffer = {
|
|
3922
3936
|
supportsHistoryApi: shouldWrapNativeHistory,
|
|
@@ -4029,8 +4043,8 @@ let SERVER = '';
|
|
|
4029
4043
|
let ASSET_HOST = '';
|
|
4030
4044
|
let ASSET_PATH = '';
|
|
4031
4045
|
let DESIGNER_SERVER = '';
|
|
4032
|
-
let VERSION = '2.
|
|
4033
|
-
let PACKAGE_VERSION = '2.
|
|
4046
|
+
let VERSION = '2.338.0_';
|
|
4047
|
+
let PACKAGE_VERSION = '2.338.0';
|
|
4034
4048
|
let LOADER = 'xhr';
|
|
4035
4049
|
/* eslint-enable web-sdk-eslint-rules/no-gulp-env-references */
|
|
4036
4050
|
/**
|
|
@@ -4447,12 +4461,6 @@ class LogEmitter extends EventTarget {
|
|
|
4447
4461
|
}
|
|
4448
4462
|
const log = new LogEmitter();
|
|
4449
4463
|
|
|
4450
|
-
function escapeRegExp(string) {
|
|
4451
|
-
if (!string)
|
|
4452
|
-
return;
|
|
4453
|
-
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
4454
|
-
}
|
|
4455
|
-
|
|
4456
4464
|
const allowKeys = function () {
|
|
4457
4465
|
var allowKeys = ConfigReader.get('storage.allowKeys') || '*';
|
|
4458
4466
|
return _.isArray(allowKeys) ? _.indexBy(allowKeys) : allowKeys;
|
|
@@ -10079,6 +10087,26 @@ var writeMessage = function (msg) {
|
|
|
10079
10087
|
});
|
|
10080
10088
|
return q.resolve(writeImgTag(url)).catch(_.noop);
|
|
10081
10089
|
};
|
|
10090
|
+
/**
|
|
10091
|
+
* Releases a fetch response body we are never going to read. An undrained body
|
|
10092
|
+
* holds its stream, the underlying data pipe and the socket open until the
|
|
10093
|
+
* {Response} is collected, which may never happen in a long-lived embedded
|
|
10094
|
+
* renderer. Cancelling does not affect keepalive delivery.
|
|
10095
|
+
*
|
|
10096
|
+
* @access private
|
|
10097
|
+
* @param {Response} response to discard the body of
|
|
10098
|
+
* @returns {Response} the same response, for chaining
|
|
10099
|
+
*/
|
|
10100
|
+
function cancelResponseBody(response) {
|
|
10101
|
+
try {
|
|
10102
|
+
if (response && response.body && _.isFunction(response.body.cancel)) {
|
|
10103
|
+
response.body.cancel().catch(_.noop);
|
|
10104
|
+
}
|
|
10105
|
+
}
|
|
10106
|
+
catch (e) {
|
|
10107
|
+
}
|
|
10108
|
+
return response;
|
|
10109
|
+
}
|
|
10082
10110
|
/**
|
|
10083
10111
|
* @access private
|
|
10084
10112
|
* @param {string} msg to send
|
|
@@ -10107,7 +10135,9 @@ function writeErrorPOST(msg) {
|
|
|
10107
10135
|
else {
|
|
10108
10136
|
promise = ajax.postJSON(url, errorData);
|
|
10109
10137
|
}
|
|
10110
|
-
return promise.then(function () {
|
|
10138
|
+
return promise.then(function (response) {
|
|
10139
|
+
cancelResponseBody(response);
|
|
10140
|
+
}, function (err) { log.debug('error writing error:' + err); });
|
|
10111
10141
|
}
|
|
10112
10142
|
catch (e) {
|
|
10113
10143
|
log.info('Failed to write error to server using POST endpoint: ' + e);
|
|
@@ -10129,6 +10159,10 @@ function writeMetricsPOST(payload) {
|
|
|
10129
10159
|
keepalive: true,
|
|
10130
10160
|
body: JSON.stringify(payload),
|
|
10131
10161
|
headers: { 'Content-Type': 'application/json' }
|
|
10162
|
+
})
|
|
10163
|
+
.then(cancelResponseBody)
|
|
10164
|
+
.catch(function (err) {
|
|
10165
|
+
log.debug(`Failed to write metrics payload to server: ${err}`);
|
|
10132
10166
|
});
|
|
10133
10167
|
}
|
|
10134
10168
|
else {
|
|
@@ -10156,13 +10190,23 @@ function writeImgTag(src) {
|
|
|
10156
10190
|
image.src = src;
|
|
10157
10191
|
return deferred.promise;
|
|
10158
10192
|
}
|
|
10193
|
+
/**
|
|
10194
|
+
* Sends a fire-and-forget beacon request. The response body is cancelled before
|
|
10195
|
+
* the promise resolves, so callers get {Response.status}, {Response.ok} and the
|
|
10196
|
+
* headers but cannot read the body. Use {fetch} directly if you need to read one.
|
|
10197
|
+
*
|
|
10198
|
+
* @access private
|
|
10199
|
+
* @param {string} url to request
|
|
10200
|
+
* @param {Object} options passed to {fetch}, defaulting to a keepalive GET
|
|
10201
|
+
* @returns {Promise<Response>} with its body already cancelled
|
|
10202
|
+
*/
|
|
10159
10203
|
function fetchKeepalive(url, options = {}) {
|
|
10160
10204
|
const defaults = {
|
|
10161
10205
|
method: 'GET',
|
|
10162
10206
|
keepalive: true
|
|
10163
10207
|
};
|
|
10164
10208
|
const fetchOptions = _.extend({}, defaults, options);
|
|
10165
|
-
return fetch(url, fetchOptions);
|
|
10209
|
+
return fetch(url, fetchOptions).then(cancelResponseBody);
|
|
10166
10210
|
}
|
|
10167
10211
|
fetchKeepalive.supported = function () {
|
|
10168
10212
|
try {
|
|
@@ -11788,6 +11832,13 @@ _.extend(dom, {
|
|
|
11788
11832
|
});
|
|
11789
11833
|
_.extend(dom.prototype, DomEvent.$, DomQuery.$);
|
|
11790
11834
|
|
|
11835
|
+
// Domains that Pendo owns, will be swapped in by build patches
|
|
11836
|
+
const pendoDomainPatterns = [
|
|
11837
|
+
/^https:\/\/((adopt|oem)\.)?((us1)\.)?(app|via|adopt|cdn|oem|novus-api|novus)(\.(au|eu|gov|hsbc|jpn|tv))?\.pendo\.io$/,
|
|
11838
|
+
/^https:\/\/([0-9]{8}t[0-9]{4}-dot-)(pendo-au|pendo-eu|pendo-govramp|pendo-hsbc|pendo-io|pendo-jp-prod|pendo-tv|pendo-us1)\.appspot\.com$/,
|
|
11839
|
+
/^https:\/\/hotfix-(ops|app)-([0-9]+-dot-)(pendo-au|pendo-eu|pendo-govramp|pendo-hsbc|pendo-io|pendo-jp-prod|pendo-tv|pendo-us1)\.appspot\.com$/,
|
|
11840
|
+
/^https:\/\/(pendo-au|pendo-eu|pendo-govramp|pendo-hsbc|pendo-io|pendo-jp-prod|pendo-tv|pendo-us1)-static\.storage\.googleapis\.com$/
|
|
11841
|
+
]; // eslint-disable-line web-sdk-eslint-rules/no-gulp-env-references
|
|
11791
11842
|
function isTrustedOrigin(host) {
|
|
11792
11843
|
if (!host)
|
|
11793
11844
|
return false;
|
|
@@ -11801,13 +11852,6 @@ function isTrustedOrigin(host) {
|
|
|
11801
11852
|
return true;
|
|
11802
11853
|
if (host === getContentHost())
|
|
11803
11854
|
return true;
|
|
11804
|
-
// Domains that Pendo owns, will be swapped in by build patches
|
|
11805
|
-
const patterns = [
|
|
11806
|
-
/^https:\/\/((adopt|oem)\.)?((us1)\.)?(app|via|adopt|cdn|oem|novus-api|novus)(\.(au|eu|gov|hsbc|jpn|tv))?\.pendo\.io$/,
|
|
11807
|
-
/^https:\/\/([0-9]{8}t[0-9]{4}-dot-)(pendo-au|pendo-eu|pendo-govramp|pendo-hsbc|pendo-io|pendo-jp-prod|pendo-tv|pendo-us1)\.appspot\.com$/,
|
|
11808
|
-
/^https:\/\/hotfix-(ops|app)-([0-9]+-dot-)(pendo-au|pendo-eu|pendo-govramp|pendo-hsbc|pendo-io|pendo-jp-prod|pendo-tv|pendo-us1)\.appspot\.com$/,
|
|
11809
|
-
/^https:\/\/(pendo-au|pendo-eu|pendo-govramp|pendo-hsbc|pendo-io|pendo-jp-prod|pendo-tv|pendo-us1)-static\.storage\.googleapis\.com$/
|
|
11810
|
-
]; // eslint-disable-line web-sdk-eslint-rules/no-gulp-env-references
|
|
11811
11855
|
var adoptHost = ConfigReader.get('adoptHost');
|
|
11812
11856
|
if (adoptHost) {
|
|
11813
11857
|
var fullHostname = 'https://' + adoptHost;
|
|
@@ -11817,10 +11861,17 @@ function isTrustedOrigin(host) {
|
|
|
11817
11861
|
}
|
|
11818
11862
|
if (urlMatchesPatterns(host, ConfigReader.get('allowedOriginServers', []), ConfigReader.get('allowedOriginServerHashes', [])))
|
|
11819
11863
|
return true;
|
|
11820
|
-
return _.any(
|
|
11864
|
+
return _.any(pendoDomainPatterns, function (pattern) {
|
|
11821
11865
|
return pattern.test(host);
|
|
11822
11866
|
});
|
|
11823
11867
|
}
|
|
11868
|
+
function isPendoDomain(origin) {
|
|
11869
|
+
if (!origin)
|
|
11870
|
+
return false;
|
|
11871
|
+
return _.any(pendoDomainPatterns, function (pattern) {
|
|
11872
|
+
return pattern.test(origin);
|
|
11873
|
+
});
|
|
11874
|
+
}
|
|
11824
11875
|
function hasAllowedOriginServers() {
|
|
11825
11876
|
const allowedOriginServers = ConfigReader.get('allowedOriginServers', []);
|
|
11826
11877
|
return allowedOriginServers && allowedOriginServers.length > 0;
|
|
@@ -13158,6 +13209,7 @@ function sendWithImg(apiKey, silo) {
|
|
|
13158
13209
|
return writeImgTag(buildGetRequestUrl(apiKey, silo));
|
|
13159
13210
|
}
|
|
13160
13211
|
function handleFetchResponse(response) {
|
|
13212
|
+
cancelResponseBody(response);
|
|
13161
13213
|
if (response.status < 200 || response.status >= 300) {
|
|
13162
13214
|
return q.reject(new Error(`received status code ${response.status}: ${response.statusText}`));
|
|
13163
13215
|
}
|
|
@@ -26801,10 +26853,23 @@ var DesignerV2 = (function () {
|
|
|
26801
26853
|
}
|
|
26802
26854
|
return validTarget;
|
|
26803
26855
|
}
|
|
26856
|
+
function hasSameOriginOpener() {
|
|
26857
|
+
if (!window.opener)
|
|
26858
|
+
return false;
|
|
26859
|
+
try {
|
|
26860
|
+
return window.opener.location.origin === window.location.origin;
|
|
26861
|
+
}
|
|
26862
|
+
catch (e) {
|
|
26863
|
+
return false;
|
|
26864
|
+
}
|
|
26865
|
+
}
|
|
26804
26866
|
function launchOnToken(url) {
|
|
26805
26867
|
if (isDesignerFrame(window.name)) {
|
|
26806
26868
|
return;
|
|
26807
26869
|
}
|
|
26870
|
+
if (isPendoDomain(window.location.origin) && !hasSameOriginOpener()) {
|
|
26871
|
+
return;
|
|
26872
|
+
}
|
|
26808
26873
|
var tokenParamTest = /pendo-designer=([A-Za-z0-9-]+)/;
|
|
26809
26874
|
if (!tokenParamTest.test(url))
|
|
26810
26875
|
return;
|
|
@@ -26817,7 +26882,7 @@ var DesignerV2 = (function () {
|
|
|
26817
26882
|
// clear stored designer state if we're launching from a token
|
|
26818
26883
|
window.localStorage.removeItem('pendo-navigation-state');
|
|
26819
26884
|
window.localStorage.removeItem('pendo-designer-mode');
|
|
26820
|
-
launchInAppDesigner({
|
|
26885
|
+
DesignerV2.launchInAppDesigner({
|
|
26821
26886
|
target: validTarget,
|
|
26822
26887
|
host: validAssetHost,
|
|
26823
26888
|
preloader: false,
|
|
@@ -29734,7 +29799,11 @@ var BuildingBlockGuides = (function () {
|
|
|
29734
29799
|
// position and calculate its height before making it visible.
|
|
29735
29800
|
// In the designer, changing visibility like this causes the guide to "flicker"
|
|
29736
29801
|
// between re-renders
|
|
29737
|
-
|
|
29802
|
+
// Only hide the first match. Resource center module previews nest the module's
|
|
29803
|
+
// pendo-g- inside the home view's pendo-g- under the same id (via
|
|
29804
|
+
// correctGuideContainerId), and imageNodeHandler only un-hides the first match,
|
|
29805
|
+
// so hiding every match left the module content hidden forever.
|
|
29806
|
+
dom(guideContainer[0]).css({ visibility: 'hidden' });
|
|
29738
29807
|
guideToAppend.css({ visibility: 'hidden' });
|
|
29739
29808
|
const embedConfig = _.get(guide, 'attributes.embedConfig', null);
|
|
29740
29809
|
var watermark = BuildingBlockWatermark.buildWatermark({
|
|
@@ -50694,6 +50763,7 @@ const RECORDING_CONFIG_ON_RECORDING_STOP = 'recording.onRecordingStop';
|
|
|
50694
50763
|
const RECORDING_CONFIG_WORKER_OVERRIDE = 'recording.workerOverride';
|
|
50695
50764
|
const RECORDING_CONFIG_TREAT_IFRAME_AS_ROOT = 'recording.treatIframeAsRoot';
|
|
50696
50765
|
const RECORDING_CONFIG_DISABLE_UNLOAD = 'recording.disableUnload';
|
|
50766
|
+
const RECORDING_CONFIG_RECORD_CROSS_ORIGIN_IFRAMES = 'recording.recordCrossOriginIframes';
|
|
50697
50767
|
const RESOURCE_CACHING = 'resourceCaching';
|
|
50698
50768
|
const ONE_DAY_IN_MILLISECONDS = 24 * 60 * 60 * 1000;
|
|
50699
50769
|
const ONE_MINUTE_IN_MILLISECONDS = 60 * 1000;
|
|
@@ -50805,6 +50875,26 @@ class SessionRecorder {
|
|
|
50805
50875
|
* @type {boolean}
|
|
50806
50876
|
*/
|
|
50807
50877
|
cf.addOption(RECORDING_CONFIG_TREAT_IFRAME_AS_ROOT, [cf.sources.SNIPPET_SRC], undefined);
|
|
50878
|
+
/**
|
|
50879
|
+
* Controls whether content embedded from another domain is included in replay. When enabled, the
|
|
50880
|
+
* embedded content shows up as part of the page. When disabled, it appears as a blank box.
|
|
50881
|
+
*
|
|
50882
|
+
* This only applies to embedded content that also has Pendo installed. Embeds from other companies
|
|
50883
|
+
* (Stripe, YouTube) are never captured either way — turning this off just stops Pendo from
|
|
50884
|
+
* messaging those frames at all.
|
|
50885
|
+
*
|
|
50886
|
+
* To capture embedded content as its own separate replay instead of part of the page it sits in,
|
|
50887
|
+
* see recording.treatIframeAsRoot.
|
|
50888
|
+
*
|
|
50889
|
+
* @access public
|
|
50890
|
+
* @category Config/Replay
|
|
50891
|
+
* @name recording.recordCrossOriginIframes
|
|
50892
|
+
* @default true
|
|
50893
|
+
* @type {boolean}
|
|
50894
|
+
*/
|
|
50895
|
+
// Like recording.enabled, this defaults to undefined so that it only overrides the
|
|
50896
|
+
// value returned by the backend when it is explicitly set to true/false
|
|
50897
|
+
cf.addOption(RECORDING_CONFIG_RECORD_CROSS_ORIGIN_IFRAMES, [cf.sources.SNIPPET_SRC], undefined);
|
|
50808
50898
|
cf.addOption(RESOURCE_CACHING, [cf.sources.PENDO_CONFIG_SRC], undefined);
|
|
50809
50899
|
}
|
|
50810
50900
|
initialize(pendo, PluginAPI) {
|
|
@@ -51024,6 +51114,9 @@ class SessionRecorder {
|
|
|
51024
51114
|
});
|
|
51025
51115
|
const blockedSelectors = ['.pendo-ignore', '.pendo-sr-ignore'].concat(recordingOptions.blockedSelectors || []);
|
|
51026
51116
|
const hiddenSelectors = ['.pendo-sr-hide'].concat(recordingOptions.hiddenSelectors || []);
|
|
51117
|
+
// The snippet config value should take precedence over the value from the visitor config
|
|
51118
|
+
const snippetValue = this.api.ConfigReader.get(RECORDING_CONFIG_RECORD_CROSS_ORIGIN_IFRAMES);
|
|
51119
|
+
const recordCrossOriginIframes = this.pendo._.isBoolean(snippetValue) ? snippetValue : recordingOptions.recordCrossOriginIframes;
|
|
51027
51120
|
const config = {
|
|
51028
51121
|
maskInputFn: this.pendo._.partial(maskInput, maskOptions),
|
|
51029
51122
|
maskTextFn: this.pendo._.partial(maskText, maskOptions),
|
|
@@ -51034,7 +51127,7 @@ class SessionRecorder {
|
|
|
51034
51127
|
// we need to concat hidden selectors to re-use block selector logic
|
|
51035
51128
|
blockSelector: blockedSelectors.concat(hiddenSelectors).join(','),
|
|
51036
51129
|
hideSelector: hiddenSelectors.join(','),
|
|
51037
|
-
recordCrossOriginIframes:
|
|
51130
|
+
recordCrossOriginIframes: recordCrossOriginIframes != null ? recordCrossOriginIframes : true,
|
|
51038
51131
|
emitFromIframe: !!this.api.ConfigReader.get(RECORDING_CONFIG_TREAT_IFRAME_AS_ROOT)
|
|
51039
51132
|
};
|
|
51040
51133
|
return config;
|
|
@@ -51790,6 +51883,20 @@ var WorkerFactory = /*#__PURE__*/createInlineWorkerFactory(/* rollup-plugin-web-
|
|
|
51790
51883
|
}
|
|
51791
51884
|
|
|
51792
51885
|
const ONE_HUNDRED_MB_IN_BYTES = 100 * 1024 * 1024;
|
|
51886
|
+
// An undrained response body holds its stream, the underlying data pipe and the
|
|
51887
|
+
// socket open until the response is collected, which in a worker that lives for
|
|
51888
|
+
// the whole session may never happen. Deliberately duplicated from
|
|
51889
|
+
// cancelResponseBody in src/transmit.js instead of imported: this is its own
|
|
51890
|
+
// bundle, and importing transmit would pull the rest of its dependencies in.
|
|
51891
|
+
function cancelResponseBody(response) {
|
|
51892
|
+
try {
|
|
51893
|
+
if (response && response.body && typeof response.body.cancel === 'function') {
|
|
51894
|
+
response.body.cancel().catch(function () { });
|
|
51895
|
+
}
|
|
51896
|
+
}
|
|
51897
|
+
catch (e) {
|
|
51898
|
+
}
|
|
51899
|
+
}
|
|
51793
51900
|
function matchHostedResources(recordingEvent, stringifiedRecordingPayload) {
|
|
51794
51901
|
const fontURLRegex = /https:\/\/[^"\\\s]+?\.(woff2?|ttf|otf|eot)(\?[^"\\\s]*)?\b/g;
|
|
51795
51902
|
const matches = stringifiedRecordingPayload.match(fontURLRegex);
|
|
@@ -51864,6 +51971,7 @@ var WorkerFactory = /*#__PURE__*/createInlineWorkerFactory(/* rollup-plugin-web-
|
|
|
51864
51971
|
method: 'POST',
|
|
51865
51972
|
body
|
|
51866
51973
|
}).then(function (response) {
|
|
51974
|
+
cancelResponseBody(response);
|
|
51867
51975
|
if (response.status < 200 || response.status >= 300) {
|
|
51868
51976
|
postMessage({
|
|
51869
51977
|
error: new Error(`received status code ${response.status}: ${response.statusText}`),
|
|
@@ -52623,7 +52731,10 @@ function ConsoleCapture() {
|
|
|
52623
52731
|
let transport;
|
|
52624
52732
|
let isPtmPaused;
|
|
52625
52733
|
let isCapturingConsoleLogs = false;
|
|
52734
|
+
let alwaysCaptureErrors = false;
|
|
52735
|
+
let isRecordingActive = false;
|
|
52626
52736
|
const CAPTURE_CONSOLE_CONFIG = 'captureConsoleLogs';
|
|
52737
|
+
const ALWAYS_CAPTURE_ERRORS_CONFIG = 'alwaysCaptureDevLogErrors';
|
|
52627
52738
|
const CONSOLE_METHODS = ['log', 'warn', 'error', 'info'];
|
|
52628
52739
|
const DEV_LOG_SUB_TYPE = 'console';
|
|
52629
52740
|
// deduplicate logs
|
|
@@ -52662,9 +52773,11 @@ function ConsoleCapture() {
|
|
|
52662
52773
|
pluginAPI = PluginAPI;
|
|
52663
52774
|
const { ConfigReader } = pluginAPI;
|
|
52664
52775
|
ConfigReader.addOption(CAPTURE_CONSOLE_CONFIG, [ConfigReader.sources.PENDO_CONFIG_SRC], false);
|
|
52776
|
+
ConfigReader.addOption(ALWAYS_CAPTURE_ERRORS_CONFIG, [ConfigReader.sources.PENDO_CONFIG_SRC], false);
|
|
52665
52777
|
const captureConsoleEnabled = ConfigReader.get(CAPTURE_CONSOLE_CONFIG);
|
|
52666
52778
|
if (!captureConsoleEnabled)
|
|
52667
52779
|
return;
|
|
52780
|
+
alwaysCaptureErrors = !!ConfigReader.get(ALWAYS_CAPTURE_ERRORS_CONFIG);
|
|
52668
52781
|
globalPendo = pendo;
|
|
52669
52782
|
buffer = new DevlogBuffer(pendo, pluginAPI);
|
|
52670
52783
|
transport = new DevlogTransport(pendo, pluginAPI);
|
|
@@ -52708,11 +52821,19 @@ function ConsoleCapture() {
|
|
|
52708
52821
|
pluginAPI.log.info(`[ConsoleCapture] Console log capture ${shouldCapture ? 'started' : 'stopped'}${reason ? `: ${reason}` : ''}`);
|
|
52709
52822
|
}
|
|
52710
52823
|
function recordingStarted() {
|
|
52824
|
+
isRecordingActive = true;
|
|
52711
52825
|
setCaptureState({ shouldCapture: true, reason: 'recording started' });
|
|
52712
52826
|
}
|
|
52713
52827
|
function recordingStopped() {
|
|
52828
|
+
isRecordingActive = false;
|
|
52829
|
+
if (alwaysCaptureErrors)
|
|
52830
|
+
return; // capture isn't tied to the recording lifecycle
|
|
52714
52831
|
setCaptureState({ shouldCapture: false, reason: 'recording stopped' });
|
|
52715
52832
|
}
|
|
52833
|
+
// ingestion only keeps error logs when there's no recording, so don't send the rest
|
|
52834
|
+
function isErrorOnlyCapture() {
|
|
52835
|
+
return alwaysCaptureErrors && !isRecordingActive;
|
|
52836
|
+
}
|
|
52716
52837
|
function readyHandler() {
|
|
52717
52838
|
try {
|
|
52718
52839
|
addIntercepts();
|
|
@@ -52721,6 +52842,10 @@ function ConsoleCapture() {
|
|
|
52721
52842
|
catch (error) {
|
|
52722
52843
|
pluginAPI.log.error('Error setting up console capture intercepts: ', error);
|
|
52723
52844
|
teardown();
|
|
52845
|
+
return;
|
|
52846
|
+
}
|
|
52847
|
+
if (alwaysCaptureErrors) {
|
|
52848
|
+
setCaptureState({ shouldCapture: true, reason: 'always capture dev log errors' });
|
|
52724
52849
|
}
|
|
52725
52850
|
}
|
|
52726
52851
|
function addIntercepts() {
|
|
@@ -52756,6 +52881,8 @@ function ConsoleCapture() {
|
|
|
52756
52881
|
function createConsoleEvent(args, methodName, { skipStackTrace = false, skipScrubPII = false } = {}) {
|
|
52757
52882
|
if (!isCapturingConsoleLogs || !args || args.length === 0 || !_.contains(CONSOLE_METHODS, methodName))
|
|
52758
52883
|
return;
|
|
52884
|
+
if (isErrorOnlyCapture() && methodName !== 'error')
|
|
52885
|
+
return;
|
|
52759
52886
|
// stringify args
|
|
52760
52887
|
let message = _.compact(_.map(args, arg => {
|
|
52761
52888
|
let stringifiedArg;
|
|
@@ -52869,8 +52996,11 @@ function NetworkCapture() {
|
|
|
52869
52996
|
let responseBodyCb;
|
|
52870
52997
|
let pendoDevlogBaseUrl;
|
|
52871
52998
|
let isCapturingNetworkLogs = false;
|
|
52999
|
+
let alwaysCaptureErrors = false;
|
|
53000
|
+
let isRecordingActive = false;
|
|
52872
53001
|
let excludeRequestUrls = [];
|
|
52873
53002
|
const CAPTURE_NETWORK_CONFIG = 'captureNetworkRequests';
|
|
53003
|
+
const ALWAYS_CAPTURE_ERRORS_CONFIG = 'alwaysCaptureDevLogErrors';
|
|
52874
53004
|
const NETWORK_SUB_TYPE = 'network';
|
|
52875
53005
|
const NETWORK_LOGS_CONFIG = 'networkLogs';
|
|
52876
53006
|
const NETWORK_LOGS_CONFIG_ALLOWED_REQUEST_HEADERS = 'networkLogs.allowedRequestHeaders';
|
|
@@ -52946,9 +53076,11 @@ function NetworkCapture() {
|
|
|
52946
53076
|
globalPendo = pendo;
|
|
52947
53077
|
const { ConfigReader } = pluginAPI;
|
|
52948
53078
|
ConfigReader.addOption(CAPTURE_NETWORK_CONFIG, [ConfigReader.sources.PENDO_CONFIG_SRC], false);
|
|
53079
|
+
ConfigReader.addOption(ALWAYS_CAPTURE_ERRORS_CONFIG, [ConfigReader.sources.PENDO_CONFIG_SRC], false);
|
|
52949
53080
|
const captureNetworkEnabled = ConfigReader.get(CAPTURE_NETWORK_CONFIG);
|
|
52950
53081
|
if (!captureNetworkEnabled)
|
|
52951
53082
|
return;
|
|
53083
|
+
alwaysCaptureErrors = !!ConfigReader.get(ALWAYS_CAPTURE_ERRORS_CONFIG);
|
|
52952
53084
|
buffer = new DevlogBuffer(pendo, pluginAPI);
|
|
52953
53085
|
transport = new DevlogTransport(pendo, pluginAPI);
|
|
52954
53086
|
sendQueue = new pluginAPI.SendQueue(transport.sendRequest.bind(transport));
|
|
@@ -53008,7 +53140,9 @@ function NetworkCapture() {
|
|
|
53008
53140
|
*/
|
|
53009
53141
|
ConfigReader.addOption(NETWORK_LOGS_CONFIG_CAPTURE_REQUEST_BODY, [ConfigReader.sources.SNIPPET_SRC], undefined);
|
|
53010
53142
|
/**
|
|
53011
|
-
* Callback function to process/modify response bodies before capturing them. If not provided, the response body
|
|
53143
|
+
* Callback function to process/modify response bodies before capturing them. If not provided, the response body
|
|
53144
|
+
* will not be captured, unless `alwaysCaptureDevLogErrors` is enabled: error response bodies are then captured
|
|
53145
|
+
* as-is until a callback is provided.
|
|
53012
53146
|
*
|
|
53013
53147
|
* @access public
|
|
53014
53148
|
* @category Config/Network Logs
|
|
@@ -53053,6 +53187,14 @@ function NetworkCapture() {
|
|
|
53053
53187
|
responseBodyCb = config.captureResponseBody;
|
|
53054
53188
|
}
|
|
53055
53189
|
}
|
|
53190
|
+
// error bodies are captured by default here; processBody still masks and truncates them
|
|
53191
|
+
function getResponseBodyCb(response) {
|
|
53192
|
+
if (responseBodyCb)
|
|
53193
|
+
return responseBodyCb;
|
|
53194
|
+
if (alwaysCaptureErrors && response.status >= 400)
|
|
53195
|
+
return (body) => body;
|
|
53196
|
+
return undefined;
|
|
53197
|
+
}
|
|
53056
53198
|
function buildExcludeRequestUrls() {
|
|
53057
53199
|
const requestUrls = pluginAPI.ConfigReader.get(NETWORK_LOGS_CONFIG_EXCLUDE_REQUEST_URLS);
|
|
53058
53200
|
if (!requestUrls || requestUrls.length === 0)
|
|
@@ -53100,13 +53242,24 @@ function NetworkCapture() {
|
|
|
53100
53242
|
pluginAPI.log.info(`[NetworkCapture] Network request capture ${shouldCapture ? 'started' : 'stopped'}${reason ? `: ${reason}` : ''}`);
|
|
53101
53243
|
}
|
|
53102
53244
|
function recordingStarted() {
|
|
53245
|
+
isRecordingActive = true;
|
|
53103
53246
|
return setCaptureState({ shouldCapture: true, reason: 'recording started' });
|
|
53104
53247
|
}
|
|
53105
53248
|
function recordingStopped() {
|
|
53249
|
+
isRecordingActive = false;
|
|
53250
|
+
if (alwaysCaptureErrors)
|
|
53251
|
+
return; // capture isn't tied to the recording lifecycle
|
|
53106
53252
|
return setCaptureState({ shouldCapture: false, reason: 'recording stopped' });
|
|
53107
53253
|
}
|
|
53254
|
+
// ingestion only keeps error responses when there's no recording, so don't send the rest
|
|
53255
|
+
function isErrorOnlyCapture() {
|
|
53256
|
+
return alwaysCaptureErrors && !isRecordingActive;
|
|
53257
|
+
}
|
|
53108
53258
|
function startCapture() {
|
|
53109
53259
|
pluginAPI.NetworkRequest.on({ request: handleRequest, response: handleResponse, error: handleError });
|
|
53260
|
+
if (alwaysCaptureErrors) {
|
|
53261
|
+
setCaptureState({ shouldCapture: true, reason: 'always capture dev log errors' });
|
|
53262
|
+
}
|
|
53110
53263
|
}
|
|
53111
53264
|
function teardown() {
|
|
53112
53265
|
if (sendInterval) {
|
|
@@ -53154,6 +53307,10 @@ function NetworkCapture() {
|
|
|
53154
53307
|
delete requestMap[response.requestId];
|
|
53155
53308
|
return;
|
|
53156
53309
|
}
|
|
53310
|
+
if (isErrorOnlyCapture() && !(response.status >= 400)) {
|
|
53311
|
+
delete requestMap[response.requestId];
|
|
53312
|
+
return;
|
|
53313
|
+
}
|
|
53157
53314
|
if (!buffer.hasTokenAvailable()) {
|
|
53158
53315
|
delete requestMap[response.requestId];
|
|
53159
53316
|
return;
|
|
@@ -53212,11 +53369,11 @@ function NetworkCapture() {
|
|
|
53212
53369
|
return '[Failed to process request body]';
|
|
53213
53370
|
}
|
|
53214
53371
|
}
|
|
53215
|
-
function processResponseBody({ response }) {
|
|
53216
|
-
if (!response || !response.body || !
|
|
53372
|
+
function processResponseBody({ response, bodyCb = responseBodyCb }) {
|
|
53373
|
+
if (!response || !response.body || !bodyCb)
|
|
53217
53374
|
return '';
|
|
53218
53375
|
try {
|
|
53219
|
-
const body =
|
|
53376
|
+
const body = bodyCb(response.body, { response });
|
|
53220
53377
|
const contentType = globalPendo._.get(response, 'headers.content-type', '');
|
|
53221
53378
|
return processBody(body, contentType);
|
|
53222
53379
|
}
|
|
@@ -53233,8 +53390,9 @@ function NetworkCapture() {
|
|
|
53233
53390
|
if (requestBodyCb) {
|
|
53234
53391
|
networkEvent.devLogRequestBody = processRequestBody({ request });
|
|
53235
53392
|
}
|
|
53236
|
-
|
|
53237
|
-
|
|
53393
|
+
const effectiveResponseBodyCb = getResponseBodyCb(response);
|
|
53394
|
+
if (effectiveResponseBodyCb) {
|
|
53395
|
+
networkEvent.devLogResponseBody = processResponseBody({ response, bodyCb: effectiveResponseBodyCb });
|
|
53238
53396
|
}
|
|
53239
53397
|
return networkEvent;
|
|
53240
53398
|
}
|
|
@@ -55315,12 +55473,18 @@ const WIDGET_SETTINGS_API = '/external/widget/settings';
|
|
|
55315
55473
|
class PredictApiService {
|
|
55316
55474
|
constructor({ baseUrl, token }) {
|
|
55317
55475
|
this.performRequest = (url, options) => __awaiter(this, void 0, void 0, function* () {
|
|
55476
|
+
var _a;
|
|
55318
55477
|
const response = yield fetch(url, options);
|
|
55319
|
-
|
|
55320
|
-
|
|
55478
|
+
if (!response.ok) {
|
|
55479
|
+
// nothing reads an error body, and leaving it undrained holds its
|
|
55480
|
+
// stream and socket open until the response is collected
|
|
55481
|
+
(_a = response.body) === null || _a === void 0 ? void 0 : _a.cancel().catch(() => { });
|
|
55482
|
+
return { ok: false, status: response.status, body: null };
|
|
55483
|
+
}
|
|
55484
|
+
return { ok: true, status: response.status, body: yield response.json() };
|
|
55321
55485
|
});
|
|
55322
55486
|
this.getWidgetSettingsById = (id, log) => __awaiter(this, void 0, void 0, function* () {
|
|
55323
|
-
var
|
|
55487
|
+
var _b, _c;
|
|
55324
55488
|
if (!id) {
|
|
55325
55489
|
log('[predict] no id provided');
|
|
55326
55490
|
return null;
|
|
@@ -55335,11 +55499,11 @@ class PredictApiService {
|
|
|
55335
55499
|
return null;
|
|
55336
55500
|
}
|
|
55337
55501
|
const data = body;
|
|
55338
|
-
if ((
|
|
55502
|
+
if ((_b = data === null || data === void 0 ? void 0 : data.errors) === null || _b === void 0 ? void 0 : _b.length) {
|
|
55339
55503
|
log(`[predict] error getting widget settings by id: ${data.errors}`);
|
|
55340
55504
|
return null;
|
|
55341
55505
|
}
|
|
55342
|
-
return (
|
|
55506
|
+
return (_c = data === null || data === void 0 ? void 0 : data.data) !== null && _c !== void 0 ? _c : null;
|
|
55343
55507
|
}
|
|
55344
55508
|
catch (error) {
|
|
55345
55509
|
log(`[predict] error getting widget settings by id: ${error}`);
|