@pendo/agent 2.337.3 → 2.338.2

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.
@@ -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
- pattern = new RegExp('^' + pattern + '$');
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;
@@ -649,10 +657,13 @@ function getPendoConfigValue(key) {
649
657
  }
650
658
 
651
659
  // Resolve the global object without relying on a bare `window` identifier.
652
- // `window` is only a bound global in a normal browser realm; when the agent is
653
- // loaded via the npm `loadAsModule` path (or inside a Web Worker) the module is
654
- // evaluated in a realm where `window` is undeclared, so referencing it bare
655
- // throws a ReferenceError at module-load. `globalThis` resolves in every realm.
660
+ // `window` is only a bound global in a normal browser realm; the compat builds
661
+ // bind it via their `(function(window, document){...})` wrapper, but the npm
662
+ // module build has no wrapper, so its module scope can be evaluated in realms
663
+ // where `window` is undeclared (a Web Worker, or a customer bundler re-chunking
664
+ // the module into worker-reachable code) and a bare reference throws a
665
+ // ReferenceError at module-load. `globalThis` resolves in every realm; the
666
+ // `self`/`window` fallbacks cover engines that predate it. APP-160669, APP-165809.
656
667
  function getGlobalScope() {
657
668
  if (typeof globalThis !== 'undefined') {
658
669
  return globalThis;
@@ -3916,7 +3927,13 @@ function isMobileUserAgent() {
3916
3927
  return (/Android|webOS|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i).test(getUA());
3917
3928
  }
3918
3929
  function isSafari(userAgent = getUA()) {
3919
- return userAgent.indexOf('Safari') >= 0 && !userAgent.indexOf('Chrome') >= 0;
3930
+ return userAgent.indexOf('Safari') >= 0 && !isChromium(userAgent);
3931
+ }
3932
+ // Every Chromium UA carries a trailing Safari/ token, so Chrome, Edge, Opera,
3933
+ // Brave and Electron all have to be excluded from isSafari(). Chromium builds
3934
+ // report Chromium/ without a Chrome/ token, hence both checks.
3935
+ function isChromium(userAgent = getUA()) {
3936
+ return userAgent.indexOf('Chrome') >= 0 || userAgent.indexOf('Chromium') >= 0;
3920
3937
  }
3921
3938
  const sniffer = {
3922
3939
  supportsHistoryApi: shouldWrapNativeHistory,
@@ -4029,8 +4046,8 @@ let SERVER = '';
4029
4046
  let ASSET_HOST = '';
4030
4047
  let ASSET_PATH = '';
4031
4048
  let DESIGNER_SERVER = '';
4032
- let VERSION = '2.337.3_';
4033
- let PACKAGE_VERSION = '2.337.3';
4049
+ let VERSION = '2.338.2_';
4050
+ let PACKAGE_VERSION = '2.338.2';
4034
4051
  let LOADER = 'xhr';
4035
4052
  /* eslint-enable web-sdk-eslint-rules/no-gulp-env-references */
4036
4053
  /**
@@ -4335,9 +4352,9 @@ function unwrapFirefoxObject(obj) {
4335
4352
  return obj;
4336
4353
  }
4337
4354
 
4338
- var decodeURIComponent = _.isFunction(window.decodeURIComponent) ? window.decodeURIComponent : _.identity;
4355
+ var decodeURIComponent = _.isFunction(globalScope.decodeURIComponent) ? globalScope.decodeURIComponent : _.identity;
4339
4356
 
4340
- var encodeURIComponent = _.isFunction(window.encodeURIComponent) ? window.encodeURIComponent : _.identity;
4357
+ var encodeURIComponent = _.isFunction(globalScope.encodeURIComponent) ? globalScope.encodeURIComponent : _.identity;
4341
4358
 
4342
4359
  class EventTarget {
4343
4360
  constructor() {
@@ -4447,12 +4464,6 @@ class LogEmitter extends EventTarget {
4447
4464
  }
4448
4465
  const log = new LogEmitter();
4449
4466
 
4450
- function escapeRegExp(string) {
4451
- if (!string)
4452
- return;
4453
- return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
4454
- }
4455
-
4456
4467
  const allowKeys = function () {
4457
4468
  var allowKeys = ConfigReader.get('storage.allowKeys') || '*';
4458
4469
  return _.isArray(allowKeys) ? _.indexBy(allowKeys) : allowKeys;
@@ -4919,18 +4930,6 @@ var agentStorage = (function () {
4919
4930
  };
4920
4931
  })();
4921
4932
 
4922
- /*
4923
- * @license MIT - https://github.com/taylorhakes/promise-polyfill
4924
- */
4925
- // Modified to not affect global environment: the `var` keeps the binding module-local
4926
- // so the polyfill never assigns to the real global `Promise`.
4927
- //
4928
- // Main-thread only. Reading `window` here is safe because rollup's `inject` is skipped
4929
- // for the nested worker build (see `skipPlugins` on webWorkerLoader in
4930
- // rollup/web-sdk.config.mjs): a worker realm has a native Promise and no `window`, so
4931
- // injecting this polyfill there was both unreachable and fatal — it threw
4932
- // `ReferenceError: window is not defined` at worker startup and stopped session replay
4933
- // with a WORKER_ERROR. APP-163474.
4934
4933
  var Promise$1;
4935
4934
 
4936
4935
  Promise$1 = (function (global, factory) {
@@ -4940,7 +4939,7 @@ Promise$1 = (function (global, factory) {
4940
4939
  }
4941
4940
 
4942
4941
  return nativePromise;
4943
- }(window, (function () {
4942
+ }(globalScope, (function () {
4944
4943
  /**
4945
4944
  * @this {Promise}
4946
4945
  */
@@ -6269,6 +6268,7 @@ function wasSessionCleared(evt) {
6269
6268
  //
6270
6269
  // NOTE: This, for now, at least is identified as an instanceof HTMLAnchorElement
6271
6270
 
6271
+
6272
6272
  function URLPolyfillFactory(doc) {
6273
6273
  return function URL(str) {
6274
6274
  var a = doc.createElement('a');
@@ -6300,11 +6300,11 @@ function URLPolyfillFactory(doc) {
6300
6300
  var URL = (function(global, factory) {
6301
6301
  var nativeURL = global.URL;
6302
6302
  if (typeof nativeURL !== 'function' || !/native/.test(nativeURL)) {
6303
- return factory(document);
6303
+ return factory(global.document);
6304
6304
  }
6305
6305
 
6306
6306
  return nativeURL;
6307
- })(window, URLPolyfillFactory);
6307
+ })(globalScope, URLPolyfillFactory);
6308
6308
 
6309
6309
  function hasComposedPath(evt) {
6310
6310
  return evt && typeof evt.composedPath === 'function';
@@ -10079,6 +10079,26 @@ var writeMessage = function (msg) {
10079
10079
  });
10080
10080
  return q.resolve(writeImgTag(url)).catch(_.noop);
10081
10081
  };
10082
+ /**
10083
+ * Releases a fetch response body we are never going to read. An undrained body
10084
+ * holds its stream, the underlying data pipe and the socket open until the
10085
+ * {Response} is collected, which may never happen in a long-lived embedded
10086
+ * renderer. Cancelling does not affect keepalive delivery.
10087
+ *
10088
+ * @access private
10089
+ * @param {Response} response to discard the body of
10090
+ * @returns {Response} the same response, for chaining
10091
+ */
10092
+ function cancelResponseBody(response) {
10093
+ try {
10094
+ if (response && response.body && _.isFunction(response.body.cancel)) {
10095
+ response.body.cancel().catch(_.noop);
10096
+ }
10097
+ }
10098
+ catch (e) {
10099
+ }
10100
+ return response;
10101
+ }
10082
10102
  /**
10083
10103
  * @access private
10084
10104
  * @param {string} msg to send
@@ -10107,7 +10127,9 @@ function writeErrorPOST(msg) {
10107
10127
  else {
10108
10128
  promise = ajax.postJSON(url, errorData);
10109
10129
  }
10110
- return promise.then(function () { log.info('successfully wrote error'); }, function (err) { log.info('error writing error:' + err); });
10130
+ return promise.then(function (response) {
10131
+ cancelResponseBody(response);
10132
+ }, function (err) { log.debug('error writing error:' + err); });
10111
10133
  }
10112
10134
  catch (e) {
10113
10135
  log.info('Failed to write error to server using POST endpoint: ' + e);
@@ -10129,6 +10151,10 @@ function writeMetricsPOST(payload) {
10129
10151
  keepalive: true,
10130
10152
  body: JSON.stringify(payload),
10131
10153
  headers: { 'Content-Type': 'application/json' }
10154
+ })
10155
+ .then(cancelResponseBody)
10156
+ .catch(function (err) {
10157
+ log.debug(`Failed to write metrics payload to server: ${err}`);
10132
10158
  });
10133
10159
  }
10134
10160
  else {
@@ -10156,13 +10182,23 @@ function writeImgTag(src) {
10156
10182
  image.src = src;
10157
10183
  return deferred.promise;
10158
10184
  }
10185
+ /**
10186
+ * Sends a fire-and-forget beacon request. The response body is cancelled before
10187
+ * the promise resolves, so callers get {Response.status}, {Response.ok} and the
10188
+ * headers but cannot read the body. Use {fetch} directly if you need to read one.
10189
+ *
10190
+ * @access private
10191
+ * @param {string} url to request
10192
+ * @param {Object} options passed to {fetch}, defaulting to a keepalive GET
10193
+ * @returns {Promise<Response>} with its body already cancelled
10194
+ */
10159
10195
  function fetchKeepalive(url, options = {}) {
10160
10196
  const defaults = {
10161
10197
  method: 'GET',
10162
10198
  keepalive: true
10163
10199
  };
10164
10200
  const fetchOptions = _.extend({}, defaults, options);
10165
- return fetch(url, fetchOptions);
10201
+ return fetch(url, fetchOptions).then(cancelResponseBody);
10166
10202
  }
10167
10203
  fetchKeepalive.supported = function () {
10168
10204
  try {
@@ -11614,7 +11650,7 @@ var WeakRef = (function(global, factory) {
11614
11650
  }
11615
11651
 
11616
11652
  return nativeWeakRef;
11617
- })(window, WeakRefFactory);
11653
+ })(globalScope, WeakRefFactory);
11618
11654
 
11619
11655
  const trimString = (str, limit) => {
11620
11656
  const len = str.length;
@@ -11788,6 +11824,13 @@ _.extend(dom, {
11788
11824
  });
11789
11825
  _.extend(dom.prototype, DomEvent.$, DomQuery.$);
11790
11826
 
11827
+ // Domains that Pendo owns, will be swapped in by build patches
11828
+ const pendoDomainPatterns = [
11829
+ /^https:\/\/((adopt|oem)\.)?((us1)\.)?(app|via|adopt|cdn|oem|novus-api|novus)(\.(au|eu|gov|hsbc|jpn|tv))?\.pendo\.io$/,
11830
+ /^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$/,
11831
+ /^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$/,
11832
+ /^https:\/\/(pendo-au|pendo-eu|pendo-govramp|pendo-hsbc|pendo-io|pendo-jp-prod|pendo-tv|pendo-us1)-static\.storage\.googleapis\.com$/
11833
+ ]; // eslint-disable-line web-sdk-eslint-rules/no-gulp-env-references
11791
11834
  function isTrustedOrigin(host) {
11792
11835
  if (!host)
11793
11836
  return false;
@@ -11801,13 +11844,6 @@ function isTrustedOrigin(host) {
11801
11844
  return true;
11802
11845
  if (host === getContentHost())
11803
11846
  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
11847
  var adoptHost = ConfigReader.get('adoptHost');
11812
11848
  if (adoptHost) {
11813
11849
  var fullHostname = 'https://' + adoptHost;
@@ -11817,10 +11853,17 @@ function isTrustedOrigin(host) {
11817
11853
  }
11818
11854
  if (urlMatchesPatterns(host, ConfigReader.get('allowedOriginServers', []), ConfigReader.get('allowedOriginServerHashes', [])))
11819
11855
  return true;
11820
- return _.any(patterns, function (pattern) {
11856
+ return _.any(pendoDomainPatterns, function (pattern) {
11821
11857
  return pattern.test(host);
11822
11858
  });
11823
11859
  }
11860
+ function isPendoDomain(origin) {
11861
+ if (!origin)
11862
+ return false;
11863
+ return _.any(pendoDomainPatterns, function (pattern) {
11864
+ return pattern.test(origin);
11865
+ });
11866
+ }
11824
11867
  function hasAllowedOriginServers() {
11825
11868
  const allowedOriginServers = ConfigReader.get('allowedOriginServers', []);
11826
11869
  return allowedOriginServers && allowedOriginServers.length > 0;
@@ -13158,6 +13201,7 @@ function sendWithImg(apiKey, silo) {
13158
13201
  return writeImgTag(buildGetRequestUrl(apiKey, silo));
13159
13202
  }
13160
13203
  function handleFetchResponse(response) {
13204
+ cancelResponseBody(response);
13161
13205
  if (response.status < 200 || response.status >= 300) {
13162
13206
  return q.reject(new Error(`received status code ${response.status}: ${response.statusText}`));
13163
13207
  }
@@ -26801,10 +26845,23 @@ var DesignerV2 = (function () {
26801
26845
  }
26802
26846
  return validTarget;
26803
26847
  }
26848
+ function hasSameOriginOpener() {
26849
+ if (!window.opener)
26850
+ return false;
26851
+ try {
26852
+ return window.opener.location.origin === window.location.origin;
26853
+ }
26854
+ catch (e) {
26855
+ return false;
26856
+ }
26857
+ }
26804
26858
  function launchOnToken(url) {
26805
26859
  if (isDesignerFrame(window.name)) {
26806
26860
  return;
26807
26861
  }
26862
+ if (isPendoDomain(window.location.origin) && !hasSameOriginOpener()) {
26863
+ return;
26864
+ }
26808
26865
  var tokenParamTest = /pendo-designer=([A-Za-z0-9-]+)/;
26809
26866
  if (!tokenParamTest.test(url))
26810
26867
  return;
@@ -26817,7 +26874,7 @@ var DesignerV2 = (function () {
26817
26874
  // clear stored designer state if we're launching from a token
26818
26875
  window.localStorage.removeItem('pendo-navigation-state');
26819
26876
  window.localStorage.removeItem('pendo-designer-mode');
26820
- launchInAppDesigner({
26877
+ DesignerV2.launchInAppDesigner({
26821
26878
  target: validTarget,
26822
26879
  host: validAssetHost,
26823
26880
  preloader: false,
@@ -29734,7 +29791,11 @@ var BuildingBlockGuides = (function () {
29734
29791
  // position and calculate its height before making it visible.
29735
29792
  // In the designer, changing visibility like this causes the guide to "flicker"
29736
29793
  // between re-renders
29737
- guideContainer.css({ visibility: 'hidden' });
29794
+ // Only hide the first match. Resource center module previews nest the module's
29795
+ // pendo-g- inside the home view's pendo-g- under the same id (via
29796
+ // correctGuideContainerId), and imageNodeHandler only un-hides the first match,
29797
+ // so hiding every match left the module content hidden forever.
29798
+ dom(guideContainer[0]).css({ visibility: 'hidden' });
29738
29799
  guideToAppend.css({ visibility: 'hidden' });
29739
29800
  const embedConfig = _.get(guide, 'attributes.embedConfig', null);
29740
29801
  var watermark = BuildingBlockWatermark.buildWatermark({
@@ -42841,10 +42902,13 @@ function GuideMarkdown() {
42841
42902
  }
42842
42903
 
42843
42904
  // Resolve the global object without relying on a bare `window` identifier.
42844
- // `window` is only a bound global in a normal browser realm; when the agent is
42845
- // loaded via the npm `loadAsModule` path (or inside a Web Worker) the module is
42846
- // evaluated in a realm where `window` is undeclared, so referencing it bare
42847
- // throws a ReferenceError at module-load. `globalThis` resolves in every realm.
42905
+ // `window` is only a bound global in a normal browser realm; the compat builds
42906
+ // bind it via their `(function(window, document){...})` wrapper, but the npm
42907
+ // module build has no wrapper, so its module scope can be evaluated in realms
42908
+ // where `window` is undeclared (a Web Worker, or a customer bundler re-chunking
42909
+ // the module into worker-reachable code) and a bare reference throws a
42910
+ // ReferenceError at module-load. `globalThis` resolves in every realm; the
42911
+ // `self`/`window` fallbacks cover engines that predate it. APP-160669, APP-165809.
42848
42912
  function getGlobalScope() {
42849
42913
  if (typeof globalThis !== 'undefined') {
42850
42914
  return globalThis;
@@ -42892,6 +42956,7 @@ var setTimeout$1 = setTimeout;
42892
42956
  //
42893
42957
  // NOTE: This, for now, at least is identified as an instanceof HTMLAnchorElement
42894
42958
 
42959
+
42895
42960
  function URLPolyfillFactory(doc) {
42896
42961
  return function URL(str) {
42897
42962
  var a = doc.createElement('a');
@@ -42923,24 +42988,12 @@ function URLPolyfillFactory(doc) {
42923
42988
  var URL$1 = (function(global, factory) {
42924
42989
  var nativeURL = global.URL;
42925
42990
  if (typeof nativeURL !== 'function' || !/native/.test(nativeURL)) {
42926
- return factory(document);
42991
+ return factory(global.document);
42927
42992
  }
42928
42993
 
42929
42994
  return nativeURL;
42930
- })(window, URLPolyfillFactory);
42995
+ })(globalScope, URLPolyfillFactory);
42931
42996
 
42932
- /*
42933
- * @license MIT - https://github.com/taylorhakes/promise-polyfill
42934
- */
42935
- // Modified to not affect global environment: the `var` keeps the binding module-local
42936
- // so the polyfill never assigns to the real global `Promise`.
42937
- //
42938
- // Main-thread only. Reading `window` here is safe because rollup's `inject` is skipped
42939
- // for the nested worker build (see `skipPlugins` on webWorkerLoader in
42940
- // rollup/web-sdk.config.mjs): a worker realm has a native Promise and no `window`, so
42941
- // injecting this polyfill there was both unreachable and fatal — it threw
42942
- // `ReferenceError: window is not defined` at worker startup and stopped session replay
42943
- // with a WORKER_ERROR. APP-163474.
42944
42997
  var Promise$1;
42945
42998
 
42946
42999
  Promise$1 = (function (global, factory) {
@@ -42950,7 +43003,7 @@ Promise$1 = (function (global, factory) {
42950
43003
  }
42951
43004
 
42952
43005
  return nativePromise;
42953
- }(window, (function () {
43006
+ }(globalScope, (function () {
42954
43007
  /**
42955
43008
  * @this {Promise}
42956
43009
  */
@@ -50694,6 +50747,7 @@ const RECORDING_CONFIG_ON_RECORDING_STOP = 'recording.onRecordingStop';
50694
50747
  const RECORDING_CONFIG_WORKER_OVERRIDE = 'recording.workerOverride';
50695
50748
  const RECORDING_CONFIG_TREAT_IFRAME_AS_ROOT = 'recording.treatIframeAsRoot';
50696
50749
  const RECORDING_CONFIG_DISABLE_UNLOAD = 'recording.disableUnload';
50750
+ const RECORDING_CONFIG_RECORD_CROSS_ORIGIN_IFRAMES = 'recording.recordCrossOriginIframes';
50697
50751
  const RESOURCE_CACHING = 'resourceCaching';
50698
50752
  const ONE_DAY_IN_MILLISECONDS = 24 * 60 * 60 * 1000;
50699
50753
  const ONE_MINUTE_IN_MILLISECONDS = 60 * 1000;
@@ -50805,6 +50859,26 @@ class SessionRecorder {
50805
50859
  * @type {boolean}
50806
50860
  */
50807
50861
  cf.addOption(RECORDING_CONFIG_TREAT_IFRAME_AS_ROOT, [cf.sources.SNIPPET_SRC], undefined);
50862
+ /**
50863
+ * Controls whether content embedded from another domain is included in replay. When enabled, the
50864
+ * embedded content shows up as part of the page. When disabled, it appears as a blank box.
50865
+ *
50866
+ * This only applies to embedded content that also has Pendo installed. Embeds from other companies
50867
+ * (Stripe, YouTube) are never captured either way — turning this off just stops Pendo from
50868
+ * messaging those frames at all.
50869
+ *
50870
+ * To capture embedded content as its own separate replay instead of part of the page it sits in,
50871
+ * see recording.treatIframeAsRoot.
50872
+ *
50873
+ * @access public
50874
+ * @category Config/Replay
50875
+ * @name recording.recordCrossOriginIframes
50876
+ * @default true
50877
+ * @type {boolean}
50878
+ */
50879
+ // Like recording.enabled, this defaults to undefined so that it only overrides the
50880
+ // value returned by the backend when it is explicitly set to true/false
50881
+ cf.addOption(RECORDING_CONFIG_RECORD_CROSS_ORIGIN_IFRAMES, [cf.sources.SNIPPET_SRC], undefined);
50808
50882
  cf.addOption(RESOURCE_CACHING, [cf.sources.PENDO_CONFIG_SRC], undefined);
50809
50883
  }
50810
50884
  initialize(pendo, PluginAPI) {
@@ -51024,6 +51098,9 @@ class SessionRecorder {
51024
51098
  });
51025
51099
  const blockedSelectors = ['.pendo-ignore', '.pendo-sr-ignore'].concat(recordingOptions.blockedSelectors || []);
51026
51100
  const hiddenSelectors = ['.pendo-sr-hide'].concat(recordingOptions.hiddenSelectors || []);
51101
+ // The snippet config value should take precedence over the value from the visitor config
51102
+ const snippetValue = this.api.ConfigReader.get(RECORDING_CONFIG_RECORD_CROSS_ORIGIN_IFRAMES);
51103
+ const recordCrossOriginIframes = this.pendo._.isBoolean(snippetValue) ? snippetValue : recordingOptions.recordCrossOriginIframes;
51027
51104
  const config = {
51028
51105
  maskInputFn: this.pendo._.partial(maskInput, maskOptions),
51029
51106
  maskTextFn: this.pendo._.partial(maskText, maskOptions),
@@ -51034,7 +51111,7 @@ class SessionRecorder {
51034
51111
  // we need to concat hidden selectors to re-use block selector logic
51035
51112
  blockSelector: blockedSelectors.concat(hiddenSelectors).join(','),
51036
51113
  hideSelector: hiddenSelectors.join(','),
51037
- recordCrossOriginIframes: recordingOptions.recordCrossOriginIframes != null ? recordingOptions.recordCrossOriginIframes : true,
51114
+ recordCrossOriginIframes: recordCrossOriginIframes != null ? recordCrossOriginIframes : true,
51038
51115
  emitFromIframe: !!this.api.ConfigReader.get(RECORDING_CONFIG_TREAT_IFRAME_AS_ROOT)
51039
51116
  };
51040
51117
  return config;
@@ -51790,6 +51867,20 @@ var WorkerFactory = /*#__PURE__*/createInlineWorkerFactory(/* rollup-plugin-web-
51790
51867
  }
51791
51868
 
51792
51869
  const ONE_HUNDRED_MB_IN_BYTES = 100 * 1024 * 1024;
51870
+ // An undrained response body holds its stream, the underlying data pipe and the
51871
+ // socket open until the response is collected, which in a worker that lives for
51872
+ // the whole session may never happen. Deliberately duplicated from
51873
+ // cancelResponseBody in src/transmit.js instead of imported: this is its own
51874
+ // bundle, and importing transmit would pull the rest of its dependencies in.
51875
+ function cancelResponseBody(response) {
51876
+ try {
51877
+ if (response && response.body && typeof response.body.cancel === 'function') {
51878
+ response.body.cancel().catch(function () { });
51879
+ }
51880
+ }
51881
+ catch (e) {
51882
+ }
51883
+ }
51793
51884
  function matchHostedResources(recordingEvent, stringifiedRecordingPayload) {
51794
51885
  const fontURLRegex = /https:\/\/[^"\\\s]+?\.(woff2?|ttf|otf|eot)(\?[^"\\\s]*)?\b/g;
51795
51886
  const matches = stringifiedRecordingPayload.match(fontURLRegex);
@@ -51864,6 +51955,7 @@ var WorkerFactory = /*#__PURE__*/createInlineWorkerFactory(/* rollup-plugin-web-
51864
51955
  method: 'POST',
51865
51956
  body
51866
51957
  }).then(function (response) {
51958
+ cancelResponseBody(response);
51867
51959
  if (response.status < 200 || response.status >= 300) {
51868
51960
  postMessage({
51869
51961
  error: new Error(`received status code ${response.status}: ${response.statusText}`),
@@ -52623,7 +52715,10 @@ function ConsoleCapture() {
52623
52715
  let transport;
52624
52716
  let isPtmPaused;
52625
52717
  let isCapturingConsoleLogs = false;
52718
+ let alwaysCaptureErrors = false;
52719
+ let isRecordingActive = false;
52626
52720
  const CAPTURE_CONSOLE_CONFIG = 'captureConsoleLogs';
52721
+ const ALWAYS_CAPTURE_ERRORS_CONFIG = 'alwaysCaptureDevLogErrors';
52627
52722
  const CONSOLE_METHODS = ['log', 'warn', 'error', 'info'];
52628
52723
  const DEV_LOG_SUB_TYPE = 'console';
52629
52724
  // deduplicate logs
@@ -52662,9 +52757,11 @@ function ConsoleCapture() {
52662
52757
  pluginAPI = PluginAPI;
52663
52758
  const { ConfigReader } = pluginAPI;
52664
52759
  ConfigReader.addOption(CAPTURE_CONSOLE_CONFIG, [ConfigReader.sources.PENDO_CONFIG_SRC], false);
52760
+ ConfigReader.addOption(ALWAYS_CAPTURE_ERRORS_CONFIG, [ConfigReader.sources.PENDO_CONFIG_SRC], false);
52665
52761
  const captureConsoleEnabled = ConfigReader.get(CAPTURE_CONSOLE_CONFIG);
52666
52762
  if (!captureConsoleEnabled)
52667
52763
  return;
52764
+ alwaysCaptureErrors = !!ConfigReader.get(ALWAYS_CAPTURE_ERRORS_CONFIG);
52668
52765
  globalPendo = pendo;
52669
52766
  buffer = new DevlogBuffer(pendo, pluginAPI);
52670
52767
  transport = new DevlogTransport(pendo, pluginAPI);
@@ -52708,11 +52805,19 @@ function ConsoleCapture() {
52708
52805
  pluginAPI.log.info(`[ConsoleCapture] Console log capture ${shouldCapture ? 'started' : 'stopped'}${reason ? `: ${reason}` : ''}`);
52709
52806
  }
52710
52807
  function recordingStarted() {
52808
+ isRecordingActive = true;
52711
52809
  setCaptureState({ shouldCapture: true, reason: 'recording started' });
52712
52810
  }
52713
52811
  function recordingStopped() {
52812
+ isRecordingActive = false;
52813
+ if (alwaysCaptureErrors)
52814
+ return; // capture isn't tied to the recording lifecycle
52714
52815
  setCaptureState({ shouldCapture: false, reason: 'recording stopped' });
52715
52816
  }
52817
+ // ingestion only keeps error logs when there's no recording, so don't send the rest
52818
+ function isErrorOnlyCapture() {
52819
+ return alwaysCaptureErrors && !isRecordingActive;
52820
+ }
52716
52821
  function readyHandler() {
52717
52822
  try {
52718
52823
  addIntercepts();
@@ -52721,6 +52826,10 @@ function ConsoleCapture() {
52721
52826
  catch (error) {
52722
52827
  pluginAPI.log.error('Error setting up console capture intercepts: ', error);
52723
52828
  teardown();
52829
+ return;
52830
+ }
52831
+ if (alwaysCaptureErrors) {
52832
+ setCaptureState({ shouldCapture: true, reason: 'always capture dev log errors' });
52724
52833
  }
52725
52834
  }
52726
52835
  function addIntercepts() {
@@ -52756,6 +52865,8 @@ function ConsoleCapture() {
52756
52865
  function createConsoleEvent(args, methodName, { skipStackTrace = false, skipScrubPII = false } = {}) {
52757
52866
  if (!isCapturingConsoleLogs || !args || args.length === 0 || !_.contains(CONSOLE_METHODS, methodName))
52758
52867
  return;
52868
+ if (isErrorOnlyCapture() && methodName !== 'error')
52869
+ return;
52759
52870
  // stringify args
52760
52871
  let message = _.compact(_.map(args, arg => {
52761
52872
  let stringifiedArg;
@@ -52869,8 +52980,11 @@ function NetworkCapture() {
52869
52980
  let responseBodyCb;
52870
52981
  let pendoDevlogBaseUrl;
52871
52982
  let isCapturingNetworkLogs = false;
52983
+ let alwaysCaptureErrors = false;
52984
+ let isRecordingActive = false;
52872
52985
  let excludeRequestUrls = [];
52873
52986
  const CAPTURE_NETWORK_CONFIG = 'captureNetworkRequests';
52987
+ const ALWAYS_CAPTURE_ERRORS_CONFIG = 'alwaysCaptureDevLogErrors';
52874
52988
  const NETWORK_SUB_TYPE = 'network';
52875
52989
  const NETWORK_LOGS_CONFIG = 'networkLogs';
52876
52990
  const NETWORK_LOGS_CONFIG_ALLOWED_REQUEST_HEADERS = 'networkLogs.allowedRequestHeaders';
@@ -52946,9 +53060,11 @@ function NetworkCapture() {
52946
53060
  globalPendo = pendo;
52947
53061
  const { ConfigReader } = pluginAPI;
52948
53062
  ConfigReader.addOption(CAPTURE_NETWORK_CONFIG, [ConfigReader.sources.PENDO_CONFIG_SRC], false);
53063
+ ConfigReader.addOption(ALWAYS_CAPTURE_ERRORS_CONFIG, [ConfigReader.sources.PENDO_CONFIG_SRC], false);
52949
53064
  const captureNetworkEnabled = ConfigReader.get(CAPTURE_NETWORK_CONFIG);
52950
53065
  if (!captureNetworkEnabled)
52951
53066
  return;
53067
+ alwaysCaptureErrors = !!ConfigReader.get(ALWAYS_CAPTURE_ERRORS_CONFIG);
52952
53068
  buffer = new DevlogBuffer(pendo, pluginAPI);
52953
53069
  transport = new DevlogTransport(pendo, pluginAPI);
52954
53070
  sendQueue = new pluginAPI.SendQueue(transport.sendRequest.bind(transport));
@@ -53008,7 +53124,9 @@ function NetworkCapture() {
53008
53124
  */
53009
53125
  ConfigReader.addOption(NETWORK_LOGS_CONFIG_CAPTURE_REQUEST_BODY, [ConfigReader.sources.SNIPPET_SRC], undefined);
53010
53126
  /**
53011
- * Callback function to process/modify response bodies before capturing them. If not provided, the response body will not be captured.
53127
+ * Callback function to process/modify response bodies before capturing them. If not provided, the response body
53128
+ * will not be captured, unless `alwaysCaptureDevLogErrors` is enabled: error response bodies are then captured
53129
+ * as-is until a callback is provided.
53012
53130
  *
53013
53131
  * @access public
53014
53132
  * @category Config/Network Logs
@@ -53053,6 +53171,14 @@ function NetworkCapture() {
53053
53171
  responseBodyCb = config.captureResponseBody;
53054
53172
  }
53055
53173
  }
53174
+ // error bodies are captured by default here; processBody still masks and truncates them
53175
+ function getResponseBodyCb(response) {
53176
+ if (responseBodyCb)
53177
+ return responseBodyCb;
53178
+ if (alwaysCaptureErrors && response.status >= 400)
53179
+ return (body) => body;
53180
+ return undefined;
53181
+ }
53056
53182
  function buildExcludeRequestUrls() {
53057
53183
  const requestUrls = pluginAPI.ConfigReader.get(NETWORK_LOGS_CONFIG_EXCLUDE_REQUEST_URLS);
53058
53184
  if (!requestUrls || requestUrls.length === 0)
@@ -53100,13 +53226,24 @@ function NetworkCapture() {
53100
53226
  pluginAPI.log.info(`[NetworkCapture] Network request capture ${shouldCapture ? 'started' : 'stopped'}${reason ? `: ${reason}` : ''}`);
53101
53227
  }
53102
53228
  function recordingStarted() {
53229
+ isRecordingActive = true;
53103
53230
  return setCaptureState({ shouldCapture: true, reason: 'recording started' });
53104
53231
  }
53105
53232
  function recordingStopped() {
53233
+ isRecordingActive = false;
53234
+ if (alwaysCaptureErrors)
53235
+ return; // capture isn't tied to the recording lifecycle
53106
53236
  return setCaptureState({ shouldCapture: false, reason: 'recording stopped' });
53107
53237
  }
53238
+ // ingestion only keeps error responses when there's no recording, so don't send the rest
53239
+ function isErrorOnlyCapture() {
53240
+ return alwaysCaptureErrors && !isRecordingActive;
53241
+ }
53108
53242
  function startCapture() {
53109
53243
  pluginAPI.NetworkRequest.on({ request: handleRequest, response: handleResponse, error: handleError });
53244
+ if (alwaysCaptureErrors) {
53245
+ setCaptureState({ shouldCapture: true, reason: 'always capture dev log errors' });
53246
+ }
53110
53247
  }
53111
53248
  function teardown() {
53112
53249
  if (sendInterval) {
@@ -53154,6 +53291,10 @@ function NetworkCapture() {
53154
53291
  delete requestMap[response.requestId];
53155
53292
  return;
53156
53293
  }
53294
+ if (isErrorOnlyCapture() && !(response.status >= 400)) {
53295
+ delete requestMap[response.requestId];
53296
+ return;
53297
+ }
53157
53298
  if (!buffer.hasTokenAvailable()) {
53158
53299
  delete requestMap[response.requestId];
53159
53300
  return;
@@ -53212,11 +53353,11 @@ function NetworkCapture() {
53212
53353
  return '[Failed to process request body]';
53213
53354
  }
53214
53355
  }
53215
- function processResponseBody({ response }) {
53216
- if (!response || !response.body || !responseBodyCb)
53356
+ function processResponseBody({ response, bodyCb = responseBodyCb }) {
53357
+ if (!response || !response.body || !bodyCb)
53217
53358
  return '';
53218
53359
  try {
53219
- const body = responseBodyCb(response.body, { response });
53360
+ const body = bodyCb(response.body, { response });
53220
53361
  const contentType = globalPendo._.get(response, 'headers.content-type', '');
53221
53362
  return processBody(body, contentType);
53222
53363
  }
@@ -53233,8 +53374,9 @@ function NetworkCapture() {
53233
53374
  if (requestBodyCb) {
53234
53375
  networkEvent.devLogRequestBody = processRequestBody({ request });
53235
53376
  }
53236
- if (responseBodyCb) {
53237
- networkEvent.devLogResponseBody = processResponseBody({ response });
53377
+ const effectiveResponseBodyCb = getResponseBodyCb(response);
53378
+ if (effectiveResponseBodyCb) {
53379
+ networkEvent.devLogResponseBody = processResponseBody({ response, bodyCb: effectiveResponseBodyCb });
53238
53380
  }
53239
53381
  return networkEvent;
53240
53382
  }
@@ -55309,18 +55451,24 @@ var underscore = {exports: {}};
55309
55451
  var underscoreExports = underscore.exports;
55310
55452
  var _ = /*@__PURE__*/getDefaultExportFromCjs(underscoreExports);
55311
55453
 
55312
- var encodeURIComponent = _.isFunction(window.encodeURIComponent) ? window.encodeURIComponent : _.identity;
55454
+ var encodeURIComponent = _.isFunction(globalScope.encodeURIComponent) ? globalScope.encodeURIComponent : _.identity;
55313
55455
 
55314
55456
  const WIDGET_SETTINGS_API = '/external/widget/settings';
55315
55457
  class PredictApiService {
55316
55458
  constructor({ baseUrl, token }) {
55317
55459
  this.performRequest = (url, options) => __awaiter(this, void 0, void 0, function* () {
55460
+ var _a;
55318
55461
  const response = yield fetch(url, options);
55319
- const body = response.ok ? yield response.json() : null;
55320
- return { ok: response.ok, status: response.status, body };
55462
+ if (!response.ok) {
55463
+ // nothing reads an error body, and leaving it undrained holds its
55464
+ // stream and socket open until the response is collected
55465
+ (_a = response.body) === null || _a === void 0 ? void 0 : _a.cancel().catch(() => { });
55466
+ return { ok: false, status: response.status, body: null };
55467
+ }
55468
+ return { ok: true, status: response.status, body: yield response.json() };
55321
55469
  });
55322
55470
  this.getWidgetSettingsById = (id, log) => __awaiter(this, void 0, void 0, function* () {
55323
- var _a, _b;
55471
+ var _b, _c;
55324
55472
  if (!id) {
55325
55473
  log('[predict] no id provided');
55326
55474
  return null;
@@ -55335,11 +55483,11 @@ class PredictApiService {
55335
55483
  return null;
55336
55484
  }
55337
55485
  const data = body;
55338
- if ((_a = data === null || data === void 0 ? void 0 : data.errors) === null || _a === void 0 ? void 0 : _a.length) {
55486
+ if ((_b = data === null || data === void 0 ? void 0 : data.errors) === null || _b === void 0 ? void 0 : _b.length) {
55339
55487
  log(`[predict] error getting widget settings by id: ${data.errors}`);
55340
55488
  return null;
55341
55489
  }
55342
- return (_b = data === null || data === void 0 ? void 0 : data.data) !== null && _b !== void 0 ? _b : null;
55490
+ return (_c = data === null || data === void 0 ? void 0 : data.data) !== null && _c !== void 0 ? _c : null;
55343
55491
  }
55344
55492
  catch (error) {
55345
55493
  log(`[predict] error getting widget settings by id: ${error}`);