@myinterview/widget-react 1.1.21-development-eca8c5c → 1.1.22-development-2bfa0c3

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/esm/index.js CHANGED
@@ -2025,7 +2025,7 @@ function loadModule(moduleName) {
2025
2025
  * @returns A normalized version of the object, or `"**non-serializable**"` if any errors are thrown during normalization.
2026
2026
  */
2027
2027
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
2028
- function normalize(input, depth = +Infinity, maxProperties = +Infinity) {
2028
+ function normalize(input, depth = 100, maxProperties = +Infinity) {
2029
2029
  try {
2030
2030
  // since we're at the outermost level, we don't provide a key
2031
2031
  return visit('', input, depth, maxProperties);
@@ -2092,17 +2092,16 @@ function visit(
2092
2092
  return value ;
2093
2093
  }
2094
2094
 
2095
- // Do not normalize objects that we know have already been normalized. As a general rule, the
2096
- // "__sentry_skip_normalization__" property should only be used sparingly and only should only be set on objects that
2097
- // have already been normalized.
2098
- let overriddenDepth = depth;
2099
-
2100
- if (typeof (value )['__sentry_override_normalization_depth__'] === 'number') {
2101
- overriddenDepth = (value )['__sentry_override_normalization_depth__'] ;
2102
- }
2095
+ // We can set `__sentry_override_normalization_depth__` on an object to ensure that from there
2096
+ // We keep a certain amount of depth.
2097
+ // This should be used sparingly, e.g. we use it for the redux integration to ensure we get a certain amount of state.
2098
+ const remainingDepth =
2099
+ typeof (value )['__sentry_override_normalization_depth__'] === 'number'
2100
+ ? ((value )['__sentry_override_normalization_depth__'] )
2101
+ : depth;
2103
2102
 
2104
2103
  // We're also done if we've reached the max depth
2105
- if (overriddenDepth === 0) {
2104
+ if (remainingDepth === 0) {
2106
2105
  // At this point we know `serialized` is a string of the form `"[object XXXX]"`. Clean it up so it's just `"[XXXX]"`.
2107
2106
  return stringified.replace('object ', '');
2108
2107
  }
@@ -2118,7 +2117,7 @@ function visit(
2118
2117
  try {
2119
2118
  const jsonValue = valueWithToJSON.toJSON();
2120
2119
  // We need to normalize the return value of `.toJSON()` in case it has circular references
2121
- return visit('', jsonValue, overriddenDepth - 1, maxProperties, memo);
2120
+ return visit('', jsonValue, remainingDepth - 1, maxProperties, memo);
2122
2121
  } catch (err) {
2123
2122
  // pass (The built-in `toJSON` failed, but we can still try to do it ourselves)
2124
2123
  }
@@ -2147,7 +2146,7 @@ function visit(
2147
2146
 
2148
2147
  // Recursively visit all the child nodes
2149
2148
  const visitValue = visitable[visitKey];
2150
- normalized[visitKey] = visit(visitKey, visitValue, overriddenDepth - 1, maxProperties, memo);
2149
+ normalized[visitKey] = visit(visitKey, visitValue, remainingDepth - 1, maxProperties, memo);
2151
2150
 
2152
2151
  numAdded++;
2153
2152
  }
@@ -5797,7 +5796,7 @@ function getEventForEnvelopeItem(item, type) {
5797
5796
  return Array.isArray(item) ? (item )[1] : undefined;
5798
5797
  }
5799
5798
 
5800
- const SDK_VERSION = '7.49.0';
5799
+ const SDK_VERSION = '7.50.0';
5801
5800
 
5802
5801
  let originalFunctionToString;
5803
5802
 
@@ -5820,11 +5819,17 @@ class FunctionToString {constructor() { FunctionToString.prototype.__init.call(
5820
5819
  // eslint-disable-next-line @typescript-eslint/unbound-method
5821
5820
  originalFunctionToString = Function.prototype.toString;
5822
5821
 
5823
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
5824
- Function.prototype.toString = function ( ...args) {
5825
- const context = getOriginalFunction(this) || this;
5826
- return originalFunctionToString.apply(context, args);
5827
- };
5822
+ // intrinsics (like Function.prototype) might be immutable in some environments
5823
+ // e.g. Node with --frozen-intrinsics, XS (an embedded JavaScript engine) or SES (a JavaScript proposal)
5824
+ try {
5825
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
5826
+ Function.prototype.toString = function ( ...args) {
5827
+ const context = getOriginalFunction(this) || this;
5828
+ return originalFunctionToString.apply(context, args);
5829
+ };
5830
+ } catch (e) {
5831
+ // ignore errors here, just don't patch this
5832
+ }
5828
5833
  }
5829
5834
  } FunctionToString.__initStatic();
5830
5835
 
@@ -8268,11 +8273,14 @@ const REPLAY_SESSION_KEY = 'sentryReplaySession';
8268
8273
  const REPLAY_EVENT_NAME = 'replay_event';
8269
8274
  const UNABLE_TO_SEND_REPLAY = 'Unable to send Replay';
8270
8275
 
8271
- // The idle limit for a session
8272
- const SESSION_IDLE_DURATION = 300000; // 5 minutes in ms
8276
+ // The idle limit for a session after which recording is paused.
8277
+ const SESSION_IDLE_PAUSE_DURATION = 300000; // 5 minutes in ms
8278
+
8279
+ // The idle limit for a session after which the session expires.
8280
+ const SESSION_IDLE_EXPIRE_DURATION = 900000; // 15 minutes in ms
8273
8281
 
8274
8282
  // The maximum length of a session
8275
- const MAX_SESSION_LIFE = 3600000; // 60 minutes
8283
+ const MAX_SESSION_LIFE = 3600000; // 60 minutes in ms
8276
8284
 
8277
8285
  /** Default flush delays */
8278
8286
  const DEFAULT_FLUSH_MIN_DELAY = 5000;
@@ -8281,7 +8289,7 @@ const DEFAULT_FLUSH_MIN_DELAY = 5000;
8281
8289
  const DEFAULT_FLUSH_MAX_DELAY = 5500;
8282
8290
 
8283
8291
  /* How long to wait for error checkouts */
8284
- const ERROR_CHECKOUT_TIME = 60000;
8292
+ const BUFFER_CHECKOUT_TIME = 60000;
8285
8293
 
8286
8294
  const RETRY_BASE_INTERVAL = 5000;
8287
8295
  const RETRY_MAX_COUNT = 3;
@@ -8289,6 +8297,9 @@ const RETRY_MAX_COUNT = 3;
8289
8297
  /* The max (uncompressed) size in bytes of a network body. Any body larger than this will be truncated. */
8290
8298
  const NETWORK_BODY_MAX_SIZE = 150000;
8291
8299
 
8300
+ /* The max size of a single console arg that is captured. Any arg larger than this will be truncated. */
8301
+ const CONSOLE_ARG_MAX_SIZE = 5000;
8302
+
8292
8303
  var NodeType$1;
8293
8304
  (function (NodeType) {
8294
8305
  NodeType[NodeType["Document"] = 0] = "Document";
@@ -11953,6 +11964,31 @@ function createEventBuffer({ useCompression }) {
11953
11964
  return new EventBufferArray();
11954
11965
  }
11955
11966
 
11967
+ /**
11968
+ * Removes the session from Session Storage and unsets session in replay instance
11969
+ */
11970
+ function clearSession(replay) {
11971
+ deleteSession();
11972
+ replay.session = undefined;
11973
+ }
11974
+
11975
+ /**
11976
+ * Deletes a session from storage
11977
+ */
11978
+ function deleteSession() {
11979
+ const hasSessionStorage = 'sessionStorage' in WINDOW;
11980
+
11981
+ if (!hasSessionStorage) {
11982
+ return;
11983
+ }
11984
+
11985
+ try {
11986
+ WINDOW.sessionStorage.removeItem(REPLAY_SESSION_KEY);
11987
+ } catch (e) {
11988
+ // Ignore potential SecurityError exceptions
11989
+ }
11990
+ }
11991
+
11956
11992
  /**
11957
11993
  * Given an initial timestamp and an expiry duration, checks to see if current
11958
11994
  * time should be considered as expired.
@@ -11983,11 +12019,26 @@ function isSessionExpired(session, timeouts, targetTime = +new Date()) {
11983
12019
  // First, check that maximum session length has not been exceeded
11984
12020
  isExpired(session.started, timeouts.maxSessionLife, targetTime) ||
11985
12021
  // check that the idle timeout has not been exceeded (i.e. user has
11986
- // performed an action within the last `idleTimeout` ms)
11987
- isExpired(session.lastActivity, timeouts.sessionIdle, targetTime)
12022
+ // performed an action within the last `sessionIdleExpire` ms)
12023
+ isExpired(session.lastActivity, timeouts.sessionIdleExpire, targetTime)
11988
12024
  );
11989
12025
  }
11990
12026
 
12027
+ /**
12028
+ * Given a sample rate, returns true if replay should be sampled.
12029
+ *
12030
+ * 1.0 = 100% sampling
12031
+ * 0.0 = 0% sampling
12032
+ */
12033
+ function isSampled(sampleRate) {
12034
+ if (sampleRate === undefined) {
12035
+ return false;
12036
+ }
12037
+
12038
+ // Math.random() returns a number in range of 0 to 1 (inclusive of 0, but not 1)
12039
+ return Math.random() < sampleRate;
12040
+ }
12041
+
11991
12042
  /**
11992
12043
  * Save a session to session storage.
11993
12044
  */
@@ -12004,21 +12055,6 @@ function saveSession(session) {
12004
12055
  }
12005
12056
  }
12006
12057
 
12007
- /**
12008
- * Given a sample rate, returns true if replay should be sampled.
12009
- *
12010
- * 1.0 = 100% sampling
12011
- * 0.0 = 0% sampling
12012
- */
12013
- function isSampled(sampleRate) {
12014
- if (sampleRate === undefined) {
12015
- return false;
12016
- }
12017
-
12018
- // Math.random() returns a number in range of 0 to 1 (inclusive of 0, but not 1)
12019
- return Math.random() < sampleRate;
12020
- }
12021
-
12022
12058
  /**
12023
12059
  * Get a session with defaults & applied sampling.
12024
12060
  */
@@ -12037,14 +12073,15 @@ function makeSession(session) {
12037
12073
  lastActivity,
12038
12074
  segmentId,
12039
12075
  sampled,
12076
+ shouldRefresh: true,
12040
12077
  };
12041
12078
  }
12042
12079
 
12043
12080
  /**
12044
12081
  * Get the sampled status for a session based on sample rates & current sampled status.
12045
12082
  */
12046
- function getSessionSampleType(sessionSampleRate, errorSampleRate) {
12047
- return isSampled(sessionSampleRate) ? 'session' : isSampled(errorSampleRate) ? 'error' : false;
12083
+ function getSessionSampleType(sessionSampleRate, allowBuffering) {
12084
+ return isSampled(sessionSampleRate) ? 'session' : allowBuffering ? 'buffer' : false;
12048
12085
  }
12049
12086
 
12050
12087
  /**
@@ -12052,8 +12089,8 @@ function getSessionSampleType(sessionSampleRate, errorSampleRate) {
12052
12089
  * that all replays will be saved to as attachments. Currently, we only expect
12053
12090
  * one of these Sentry events per "replay session".
12054
12091
  */
12055
- function createSession({ sessionSampleRate, errorSampleRate, stickySession = false }) {
12056
- const sampled = getSessionSampleType(sessionSampleRate, errorSampleRate);
12092
+ function createSession({ sessionSampleRate, allowBuffering, stickySession = false }) {
12093
+ const sampled = getSessionSampleType(sessionSampleRate, allowBuffering);
12057
12094
  const session = makeSession({
12058
12095
  sampled,
12059
12096
  });
@@ -12101,7 +12138,7 @@ function getSession({
12101
12138
  currentSession,
12102
12139
  stickySession,
12103
12140
  sessionSampleRate,
12104
- errorSampleRate,
12141
+ allowBuffering,
12105
12142
  }) {
12106
12143
  // If session exists and is passed, use it instead of always hitting session storage
12107
12144
  const session = currentSession || (stickySession && fetchSession());
@@ -12114,8 +12151,9 @@ function getSession({
12114
12151
 
12115
12152
  if (!isExpired) {
12116
12153
  return { type: 'saved', session };
12117
- } else if (session.sampled === 'error') {
12118
- // Error samples should not be re-created when expired, but instead we stop when the replay is done
12154
+ } else if (!session.shouldRefresh) {
12155
+ // In this case, stop
12156
+ // This is the case if we have an error session that is completed (=triggered an error)
12119
12157
  const discardedSession = makeSession({ sampled: false });
12120
12158
  return { type: 'new', session: discardedSession };
12121
12159
  } else {
@@ -12127,7 +12165,7 @@ function getSession({
12127
12165
  const newSession = createSession({
12128
12166
  stickySession,
12129
12167
  sessionSampleRate,
12130
- errorSampleRate,
12168
+ allowBuffering,
12131
12169
  });
12132
12170
 
12133
12171
  return { type: 'new', session: newSession };
@@ -12161,7 +12199,7 @@ async function addEvent(
12161
12199
  // page has been left open and idle for a long period of time and user
12162
12200
  // comes back to trigger a new session. The performance entries rely on
12163
12201
  // `performance.timeOrigin`, which is when the page first opened.
12164
- if (timestampInMs + replay.timeouts.sessionIdle < Date.now()) {
12202
+ if (timestampInMs + replay.timeouts.sessionIdlePause < Date.now()) {
12165
12203
  return null;
12166
12204
  }
12167
12205
 
@@ -12176,7 +12214,7 @@ async function addEvent(
12176
12214
  return await replay.eventBuffer.addEvent(event, isCheckout);
12177
12215
  } catch (error) {
12178
12216
  (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error(error);
12179
- replay.stop('addEvent');
12217
+ await replay.stop('addEvent');
12180
12218
 
12181
12219
  const client = getCurrentHub().getClient();
12182
12220
 
@@ -12243,23 +12281,17 @@ function handleAfterSendEvent(replay) {
12243
12281
  // Trigger error recording
12244
12282
  // Need to be very careful that this does not cause an infinite loop
12245
12283
  if (
12246
- replay.recordingMode === 'error' &&
12284
+ replay.recordingMode === 'buffer' &&
12247
12285
  event.exception &&
12248
12286
  event.message !== UNABLE_TO_SEND_REPLAY // ignore this error because otherwise we could loop indefinitely with trying to capture replay and failing
12249
12287
  ) {
12250
- setTimeout(async () => {
12251
- // Allow flush to complete before resuming as a session recording, otherwise
12252
- // the checkout from `startRecording` may be included in the payload.
12253
- // Prefer to keep the error replay as a separate (and smaller) segment
12254
- // than the session replay.
12255
- await replay.flushImmediate();
12256
-
12257
- if (replay.stopRecording()) {
12258
- // Reset all "capture on error" configuration before
12259
- // starting a new recording
12260
- replay.recordingMode = 'session';
12261
- replay.startRecording();
12262
- }
12288
+ if (!isSampled(replay.getOptions().errorSampleRate)) {
12289
+ return;
12290
+ }
12291
+
12292
+ setTimeout(() => {
12293
+ // Capture current event buffer as new replay
12294
+ void replay.sendBufferedReplayOrFlush();
12263
12295
  });
12264
12296
  }
12265
12297
  };
@@ -13214,6 +13246,17 @@ function makeNetworkReplayBreadcrumb(
13214
13246
  return result;
13215
13247
  }
13216
13248
 
13249
+ /** Build the request or response part of a replay network breadcrumb that was skipped. */
13250
+ function buildSkippedNetworkRequestOrResponse(bodySize) {
13251
+ return {
13252
+ headers: {},
13253
+ size: bodySize,
13254
+ _meta: {
13255
+ warnings: ['URL_SKIPPED'],
13256
+ },
13257
+ };
13258
+ }
13259
+
13217
13260
  /** Build the request or response part of a replay network breadcrumb. */
13218
13261
  function buildNetworkRequestOrResponse(
13219
13262
  headers,
@@ -13294,8 +13337,8 @@ function normalizeNetworkBody(body)
13294
13337
  };
13295
13338
  } catch (e3) {
13296
13339
  return {
13297
- body,
13298
- warnings: ['INVALID_JSON'],
13340
+ body: exceedsSizeLimit ? `${body.slice(0, NETWORK_BODY_MAX_SIZE)}…` : body,
13341
+ warnings: exceedsSizeLimit ? ['INVALID_JSON', 'TEXT_TRUNCATED'] : ['INVALID_JSON'],
13299
13342
  };
13300
13343
  }
13301
13344
  }
@@ -13314,6 +13357,11 @@ function _strIsProbablyJson(str) {
13314
13357
  return (first === '[' && last === ']') || (first === '{' && last === '}');
13315
13358
  }
13316
13359
 
13360
+ /** Match an URL against a list of strings/Regex. */
13361
+ function urlMatches(url, urls) {
13362
+ return stringMatchesSomePattern(url, urls);
13363
+ }
13364
+
13317
13365
  /**
13318
13366
  * Capture a fetch breadcrumb to a replay.
13319
13367
  * This adds additional data (where approriate).
@@ -13373,33 +13421,37 @@ async function _prepareFetchData(
13373
13421
  const {
13374
13422
  url,
13375
13423
  method,
13376
- status_code: statusCode,
13424
+ status_code: statusCode = 0,
13377
13425
  request_body_size: requestBodySize,
13378
13426
  response_body_size: responseBodySize,
13379
13427
  } = breadcrumb.data;
13380
13428
 
13381
- const request = _getRequestInfo(options, hint.input, requestBodySize);
13382
- const response = await _getResponseInfo(options, hint.response, responseBodySize);
13429
+ const captureDetails = urlMatches(url, options.networkDetailAllowUrls);
13430
+
13431
+ const request = captureDetails
13432
+ ? _getRequestInfo(options, hint.input, requestBodySize)
13433
+ : buildSkippedNetworkRequestOrResponse(requestBodySize);
13434
+ const response = await _getResponseInfo(captureDetails, options, hint.response, responseBodySize);
13383
13435
 
13384
13436
  return {
13385
13437
  startTimestamp,
13386
13438
  endTimestamp,
13387
13439
  url,
13388
13440
  method,
13389
- statusCode: statusCode || 0,
13441
+ statusCode,
13390
13442
  request,
13391
13443
  response,
13392
13444
  };
13393
13445
  }
13394
13446
 
13395
13447
  function _getRequestInfo(
13396
- { captureBodies, requestHeaders },
13448
+ { networkCaptureBodies, networkRequestHeaders },
13397
13449
  input,
13398
13450
  requestBodySize,
13399
13451
  ) {
13400
- const headers = getRequestHeaders(input, requestHeaders);
13452
+ const headers = getRequestHeaders(input, networkRequestHeaders);
13401
13453
 
13402
- if (!captureBodies) {
13454
+ if (!networkCaptureBodies) {
13403
13455
  return buildNetworkRequestOrResponse(headers, requestBodySize, undefined);
13404
13456
  }
13405
13457
 
@@ -13410,19 +13462,24 @@ function _getRequestInfo(
13410
13462
  }
13411
13463
 
13412
13464
  async function _getResponseInfo(
13465
+ captureDetails,
13413
13466
  {
13414
- captureBodies,
13467
+ networkCaptureBodies,
13415
13468
  textEncoder,
13416
- responseHeaders,
13469
+ networkResponseHeaders,
13417
13470
  }
13418
13471
 
13419
13472
  ,
13420
13473
  response,
13421
13474
  responseBodySize,
13422
13475
  ) {
13423
- const headers = getAllHeaders(response.headers, responseHeaders);
13476
+ if (!captureDetails && responseBodySize !== undefined) {
13477
+ return buildSkippedNetworkRequestOrResponse(responseBodySize);
13478
+ }
13424
13479
 
13425
- if (!captureBodies && responseBodySize !== undefined) {
13480
+ const headers = getAllHeaders(response.headers, networkResponseHeaders);
13481
+
13482
+ if (!networkCaptureBodies && responseBodySize !== undefined) {
13426
13483
  return buildNetworkRequestOrResponse(headers, responseBodySize, undefined);
13427
13484
  }
13428
13485
 
@@ -13437,7 +13494,11 @@ async function _getResponseInfo(
13437
13494
  ? getBodySize(bodyText, textEncoder)
13438
13495
  : responseBodySize;
13439
13496
 
13440
- if (captureBodies) {
13497
+ if (!captureDetails) {
13498
+ return buildSkippedNetworkRequestOrResponse(size);
13499
+ }
13500
+
13501
+ if (networkCaptureBodies) {
13441
13502
  return buildNetworkRequestOrResponse(headers, size, bodyText);
13442
13503
  }
13443
13504
 
@@ -13570,28 +13631,44 @@ function _prepareXhrData(
13570
13631
  const {
13571
13632
  url,
13572
13633
  method,
13573
- status_code: statusCode,
13634
+ status_code: statusCode = 0,
13574
13635
  request_body_size: requestBodySize,
13575
13636
  response_body_size: responseBodySize,
13576
13637
  } = breadcrumb.data;
13577
13638
 
13578
- const xhrInfo = xhr[SENTRY_XHR_DATA_KEY];
13579
- const requestHeaders = xhrInfo ? getAllowedHeaders(xhrInfo.request_headers, options.requestHeaders) : {};
13580
- const responseHeaders = getAllowedHeaders(getResponseHeaders(xhr), options.responseHeaders);
13581
-
13582
13639
  if (!url) {
13583
13640
  return null;
13584
13641
  }
13585
13642
 
13643
+ if (!urlMatches(url, options.networkDetailAllowUrls)) {
13644
+ const request = buildSkippedNetworkRequestOrResponse(requestBodySize);
13645
+ const response = buildSkippedNetworkRequestOrResponse(responseBodySize);
13646
+ return {
13647
+ startTimestamp,
13648
+ endTimestamp,
13649
+ url,
13650
+ method,
13651
+ statusCode,
13652
+ request,
13653
+ response,
13654
+ };
13655
+ }
13656
+
13657
+ const xhrInfo = xhr[SENTRY_XHR_DATA_KEY];
13658
+ const networkRequestHeaders = xhrInfo
13659
+ ? getAllowedHeaders(xhrInfo.request_headers, options.networkRequestHeaders)
13660
+ : {};
13661
+ const networkResponseHeaders = getAllowedHeaders(getResponseHeaders(xhr), options.networkResponseHeaders);
13662
+
13586
13663
  const request = buildNetworkRequestOrResponse(
13587
- requestHeaders,
13664
+ networkRequestHeaders,
13588
13665
  requestBodySize,
13589
- options.captureBodies ? getBodyString(input) : undefined,
13666
+ options.networkCaptureBodies ? getBodyString(input) : undefined,
13590
13667
  );
13591
13668
  const response = buildNetworkRequestOrResponse(
13592
- responseHeaders,
13669
+ networkResponseHeaders,
13593
13670
  responseBodySize,
13594
- options.captureBodies ? hint.xhr.responseText : undefined,
13671
+ options.networkCaptureBodies ? hint.xhr.responseText : undefined,
13595
13672
  );
13596
13673
 
13597
13674
  return {
@@ -13599,7 +13676,7 @@ function _prepareXhrData(
13599
13676
  endTimestamp,
13600
13677
  url,
13601
13678
  method,
13602
- statusCode: statusCode || 0,
13679
+ statusCode,
13603
13680
  request,
13604
13681
  response,
13605
13682
  };
@@ -13631,10 +13708,16 @@ function handleNetworkBreadcrumbs(replay) {
13631
13708
  try {
13632
13709
  const textEncoder = new TextEncoder();
13633
13710
 
13711
+ const { networkDetailAllowUrls, networkCaptureBodies, networkRequestHeaders, networkResponseHeaders } =
13712
+ replay.getOptions();
13713
+
13634
13714
  const options = {
13635
13715
  replay,
13636
13716
  textEncoder,
13637
- ...replay.getExperimentalOptions().network,
13717
+ networkDetailAllowUrls,
13718
+ networkCaptureBodies,
13719
+ networkRequestHeaders,
13720
+ networkResponseHeaders,
13638
13721
  };
13639
13722
 
13640
13723
  if (client && client.on) {
@@ -13742,9 +13825,66 @@ function handleScope(scope) {
13742
13825
  return null;
13743
13826
  }
13744
13827
 
13828
+ if (newBreadcrumb.category === 'console') {
13829
+ return normalizeConsoleBreadcrumb(newBreadcrumb);
13830
+ }
13831
+
13745
13832
  return createBreadcrumb(newBreadcrumb);
13746
13833
  }
13747
13834
 
13835
+ /** exported for tests only */
13836
+ function normalizeConsoleBreadcrumb(breadcrumb) {
13837
+ const args = breadcrumb.data && breadcrumb.data.arguments;
13838
+
13839
+ if (!Array.isArray(args) || args.length === 0) {
13840
+ return createBreadcrumb(breadcrumb);
13841
+ }
13842
+
13843
+ let isTruncated = false;
13844
+
13845
+ // Avoid giant args captures
13846
+ const normalizedArgs = args.map(arg => {
13847
+ if (!arg) {
13848
+ return arg;
13849
+ }
13850
+ if (typeof arg === 'string') {
13851
+ if (arg.length > CONSOLE_ARG_MAX_SIZE) {
13852
+ isTruncated = true;
13853
+ return `${arg.slice(0, CONSOLE_ARG_MAX_SIZE)}…`;
13854
+ }
13855
+
13856
+ return arg;
13857
+ }
13858
+ if (typeof arg === 'object') {
13859
+ try {
13860
+ const normalizedArg = normalize(arg, 7);
13861
+ const stringified = JSON.stringify(normalizedArg);
13862
+ if (stringified.length > CONSOLE_ARG_MAX_SIZE) {
13863
+ const fixedJson = fixJson(stringified.slice(0, CONSOLE_ARG_MAX_SIZE));
13864
+ const json = JSON.parse(fixedJson);
13865
+ // We only set this after JSON.parse() was successfull, so we know we didn't run into `catch`
13866
+ isTruncated = true;
13867
+ return json;
13868
+ }
13869
+ return normalizedArg;
13870
+ } catch (e) {
13871
+ // fall back to default
13872
+ }
13873
+ }
13874
+
13875
+ return arg;
13876
+ });
13877
+
13878
+ return createBreadcrumb({
13879
+ ...breadcrumb,
13880
+ data: {
13881
+ ...breadcrumb.data,
13882
+ arguments: normalizedArgs,
13883
+ ...(isTruncated ? { _meta: { warnings: ['CONSOLE_ARG_TRUNCATED'] } } : {}),
13884
+ },
13885
+ });
13886
+ }
13887
+
13748
13888
  /**
13749
13889
  * Add global listeners that cannot be removed.
13750
13890
  */
@@ -13769,7 +13909,7 @@ function addGlobalListeners(replay) {
13769
13909
  client.on('afterSendEvent', handleAfterSendEvent(replay));
13770
13910
  client.on('createDsc', (dsc) => {
13771
13911
  const replayId = replay.getSessionId();
13772
- if (replayId) {
13912
+ if (replayId && replay.isEnabled()) {
13773
13913
  dsc.replay_id = replayId;
13774
13914
  }
13775
13915
  });
@@ -14077,7 +14217,7 @@ function getHandleRecordingEmit(replay) {
14077
14217
  // when an error occurs. Clear any state that happens before this current
14078
14218
  // checkout. This needs to happen before `addEvent()` which updates state
14079
14219
  // dependent on this reset.
14080
- if (replay.recordingMode === 'error' && isCheckout) {
14220
+ if (replay.recordingMode === 'buffer' && isCheckout) {
14081
14221
  replay.setInitialState();
14082
14222
  }
14083
14223
 
@@ -14103,7 +14243,7 @@ function getHandleRecordingEmit(replay) {
14103
14243
 
14104
14244
  // See note above re: session start needs to reflect the most recent
14105
14245
  // checkout.
14106
- if (replay.recordingMode === 'error' && replay.session) {
14246
+ if (replay.recordingMode === 'buffer' && replay.session) {
14107
14247
  const { earliestEvent } = replay.getContext();
14108
14248
  if (earliestEvent) {
14109
14249
  replay.session.started = earliestEvent;
@@ -14452,9 +14592,11 @@ class ReplayContainer {
14452
14592
  __init2() {this.performanceEvents = [];}
14453
14593
 
14454
14594
  /**
14455
- * Recording can happen in one of two modes:
14456
- * * session: Record the whole session, sending it continuously
14457
- * * error: Always keep the last 60s of recording, and when an error occurs, send it immediately
14595
+ * Recording can happen in one of three modes:
14596
+ * - session: Record the whole session, sending it continuously
14597
+ * - buffer: Always keep the last 60s of recording, requires:
14598
+ * - having replaysOnErrorSampleRate > 0 to capture replay when an error occurs
14599
+ * - or calling `flush()` to send the replay
14458
14600
  */
14459
14601
  __init3() {this.recordingMode = 'session';}
14460
14602
 
@@ -14463,7 +14605,8 @@ class ReplayContainer {
14463
14605
  * @hidden
14464
14606
  */
14465
14607
  __init4() {this.timeouts = {
14466
- sessionIdle: SESSION_IDLE_DURATION,
14608
+ sessionIdlePause: SESSION_IDLE_PAUSE_DURATION,
14609
+ sessionIdleExpire: SESSION_IDLE_EXPIRE_DURATION,
14467
14610
  maxSessionLife: MAX_SESSION_LIFE,
14468
14611
  }; }
14469
14612
 
@@ -14524,8 +14667,6 @@ class ReplayContainer {
14524
14667
  this._debouncedFlush = debounce(() => this._flush(), this._options.flushMinDelay, {
14525
14668
  maxWait: this._options.flushMaxDelay,
14526
14669
  });
14527
-
14528
- this._experimentalOptions = _getExperimentalOptions(options);
14529
14670
  }
14530
14671
 
14531
14672
  /** Get the event context. */
@@ -14549,58 +14690,102 @@ class ReplayContainer {
14549
14690
  }
14550
14691
 
14551
14692
  /**
14552
- * Get the experimental options.
14553
- * THIS IS INTERNAL AND SUBJECT TO CHANGE!
14554
- * @hidden
14693
+ * Initializes the plugin based on sampling configuration. Should not be
14694
+ * called outside of constructor.
14555
14695
  */
14556
- getExperimentalOptions() {
14557
- return this._experimentalOptions;
14696
+ initializeSampling() {
14697
+ const { errorSampleRate, sessionSampleRate } = this._options;
14698
+
14699
+ // If neither sample rate is > 0, then do nothing - user will need to call one of
14700
+ // `start()` or `startBuffering` themselves.
14701
+ if (errorSampleRate <= 0 && sessionSampleRate <= 0) {
14702
+ return;
14703
+ }
14704
+
14705
+ // Otherwise if there is _any_ sample rate set, try to load an existing
14706
+ // session, or create a new one.
14707
+ const isSessionSampled = this._loadAndCheckSession();
14708
+
14709
+ if (!isSessionSampled) {
14710
+ // This should only occur if `errorSampleRate` is 0 and was unsampled for
14711
+ // session-based replay. In this case there is nothing to do.
14712
+ return;
14713
+ }
14714
+
14715
+ if (!this.session) {
14716
+ // This should not happen, something wrong has occurred
14717
+ this._handleException(new Error('Unable to initialize and create session'));
14718
+ return;
14719
+ }
14720
+
14721
+ if (this.session.sampled && this.session.sampled !== 'session') {
14722
+ // If not sampled as session-based, then recording mode will be `buffer`
14723
+ // Note that we don't explicitly check if `sampled === 'buffer'` because we
14724
+ // could have sessions from Session storage that are still `error` from
14725
+ // prior SDK version.
14726
+ this.recordingMode = 'buffer';
14727
+ }
14728
+
14729
+ this._initializeRecording();
14558
14730
  }
14559
14731
 
14560
14732
  /**
14561
- * Initializes the plugin.
14733
+ * Start a replay regardless of sampling rate. Calling this will always
14734
+ * create a new session. Will throw an error if replay is already in progress.
14562
14735
  *
14563
14736
  * Creates or loads a session, attaches listeners to varying events (DOM,
14564
14737
  * _performanceObserver, Recording, Sentry SDK, etc)
14565
14738
  */
14566
14739
  start() {
14567
- this.setInitialState();
14568
-
14569
- if (!this._loadAndCheckSession()) {
14570
- return;
14740
+ if (this._isEnabled && this.recordingMode === 'session') {
14741
+ throw new Error('Replay recording is already in progress');
14571
14742
  }
14572
14743
 
14573
- // If there is no session, then something bad has happened - can't continue
14574
- if (!this.session) {
14575
- this._handleException(new Error('No session found'));
14576
- return;
14744
+ if (this._isEnabled && this.recordingMode === 'buffer') {
14745
+ throw new Error('Replay buffering is in progress, call `flush()` to save the replay');
14577
14746
  }
14578
14747
 
14579
- if (!this.session.sampled) {
14580
- // If session was not sampled, then we do not initialize the integration at all.
14581
- return;
14582
- }
14748
+ const previousSessionId = this.session && this.session.id;
14749
+
14750
+ const { session } = getSession({
14751
+ timeouts: this.timeouts,
14752
+ stickySession: Boolean(this._options.stickySession),
14753
+ currentSession: this.session,
14754
+ // This is intentional: create a new session-based replay when calling `start()`
14755
+ sessionSampleRate: 1,
14756
+ allowBuffering: false,
14757
+ });
14583
14758
 
14584
- // If session is sampled for errors, then we need to set the recordingMode
14585
- // to 'error', which will configure recording with different options.
14586
- if (this.session.sampled === 'error') {
14587
- this.recordingMode = 'error';
14759
+ session.previousSessionId = previousSessionId;
14760
+ this.session = session;
14761
+
14762
+ this._initializeRecording();
14763
+ }
14764
+
14765
+ /**
14766
+ * Start replay buffering. Buffers until `flush()` is called or, if
14767
+ * `replaysOnErrorSampleRate` > 0, an error occurs.
14768
+ */
14769
+ startBuffering() {
14770
+ if (this._isEnabled) {
14771
+ throw new Error('Replay recording is already in progress');
14588
14772
  }
14589
14773
 
14590
- // setup() is generally called on page load or manually - in both cases we
14591
- // should treat it as an activity
14592
- this._updateSessionActivity();
14774
+ const previousSessionId = this.session && this.session.id;
14593
14775
 
14594
- this.eventBuffer = createEventBuffer({
14595
- useCompression: this._options.useCompression,
14776
+ const { session } = getSession({
14777
+ timeouts: this.timeouts,
14778
+ stickySession: Boolean(this._options.stickySession),
14779
+ currentSession: this.session,
14780
+ sessionSampleRate: 0,
14781
+ allowBuffering: true,
14596
14782
  });
14597
14783
 
14598
- this._addListeners();
14599
-
14600
- // Need to set as enabled before we start recording, as `record()` can trigger a flush with a new checkout
14601
- this._isEnabled = true;
14784
+ session.previousSessionId = previousSessionId;
14785
+ this.session = session;
14602
14786
 
14603
- this.startRecording();
14787
+ this.recordingMode = 'buffer';
14788
+ this._initializeRecording();
14604
14789
  }
14605
14790
 
14606
14791
  /**
@@ -14615,7 +14800,7 @@ class ReplayContainer {
14615
14800
  // When running in error sampling mode, we need to overwrite `checkoutEveryNms`
14616
14801
  // Without this, it would record forever, until an error happens, which we don't want
14617
14802
  // instead, we'll always keep the last 60 seconds of replay before an error happened
14618
- ...(this.recordingMode === 'error' && { checkoutEveryNms: ERROR_CHECKOUT_TIME }),
14803
+ ...(this.recordingMode === 'buffer' && { checkoutEveryNms: BUFFER_CHECKOUT_TIME }),
14619
14804
  emit: getHandleRecordingEmit(this),
14620
14805
  onMutation: this._onMutationHandler,
14621
14806
  });
@@ -14626,17 +14811,18 @@ class ReplayContainer {
14626
14811
 
14627
14812
  /**
14628
14813
  * Stops the recording, if it was running.
14629
- * Returns true if it was stopped, else false.
14814
+ *
14815
+ * Returns true if it was previously stopped, or is now stopped,
14816
+ * otherwise false.
14630
14817
  */
14631
14818
  stopRecording() {
14632
14819
  try {
14633
14820
  if (this._stopRecording) {
14634
14821
  this._stopRecording();
14635
14822
  this._stopRecording = undefined;
14636
- return true;
14637
14823
  }
14638
14824
 
14639
- return false;
14825
+ return true;
14640
14826
  } catch (err) {
14641
14827
  this._handleException(err);
14642
14828
  return false;
@@ -14647,7 +14833,7 @@ class ReplayContainer {
14647
14833
  * Currently, this needs to be manually called (e.g. for tests). Sentry SDK
14648
14834
  * does not support a teardown
14649
14835
  */
14650
- stop(reason) {
14836
+ async stop(reason) {
14651
14837
  if (!this._isEnabled) {
14652
14838
  return;
14653
14839
  }
@@ -14663,12 +14849,24 @@ class ReplayContainer {
14663
14849
  log(msg);
14664
14850
  }
14665
14851
 
14852
+ // We can't move `_isEnabled` after awaiting a flush, otherwise we can
14853
+ // enter into an infinite loop when `stop()` is called while flushing.
14666
14854
  this._isEnabled = false;
14667
14855
  this._removeListeners();
14668
14856
  this.stopRecording();
14857
+
14858
+ this._debouncedFlush.cancel();
14859
+ // See comment above re: `_isEnabled`, we "force" a flush, ignoring the
14860
+ // `_isEnabled` state of the plugin since it was disabled above.
14861
+ await this._flush({ force: true });
14862
+
14863
+ // After flush, destroy event buffer
14669
14864
  this.eventBuffer && this.eventBuffer.destroy();
14670
14865
  this.eventBuffer = null;
14671
- this._debouncedFlush.cancel();
14866
+
14867
+ // Clear session from session storage, note this means if a new session
14868
+ // is started after, it will not have `previousSessionId`
14869
+ clearSession(this);
14672
14870
  } catch (err) {
14673
14871
  this._handleException(err);
14674
14872
  }
@@ -14699,6 +14897,45 @@ class ReplayContainer {
14699
14897
  this.startRecording();
14700
14898
  }
14701
14899
 
14900
+ /**
14901
+ * If not in "session" recording mode, flush event buffer which will create a new replay.
14902
+ * Unless `continueRecording` is false, the replay will continue to record and
14903
+ * behave as a "session"-based replay.
14904
+ *
14905
+ * Otherwise, queue up a flush.
14906
+ */
14907
+ async sendBufferedReplayOrFlush({ continueRecording = true } = {}) {
14908
+ if (this.recordingMode === 'session') {
14909
+ return this.flushImmediate();
14910
+ }
14911
+
14912
+ // Allow flush to complete before resuming as a session recording, otherwise
14913
+ // the checkout from `startRecording` may be included in the payload.
14914
+ // Prefer to keep the error replay as a separate (and smaller) segment
14915
+ // than the session replay.
14916
+ await this.flushImmediate();
14917
+
14918
+ const hasStoppedRecording = this.stopRecording();
14919
+
14920
+ if (!continueRecording || !hasStoppedRecording) {
14921
+ return;
14922
+ }
14923
+
14924
+ // Re-start recording, but in "session" recording mode
14925
+
14926
+ // Reset all "capture on error" configuration before
14927
+ // starting a new recording
14928
+ this.recordingMode = 'session';
14929
+
14930
+ // Once this session ends, we do not want to refresh it
14931
+ if (this.session) {
14932
+ this.session.shouldRefresh = false;
14933
+ this._maybeSaveSession();
14934
+ }
14935
+
14936
+ this.startRecording();
14937
+ }
14938
+
14702
14939
  /**
14703
14940
  * We want to batch uploads of replay events. Save events only if
14704
14941
  * `<flushMinDelay>` milliseconds have elapsed since the last event
@@ -14708,12 +14945,12 @@ class ReplayContainer {
14708
14945
  * processing and hand back control to caller.
14709
14946
  */
14710
14947
  addUpdate(cb) {
14711
- // We need to always run `cb` (e.g. in the case of `this.recordingMode == 'error'`)
14948
+ // We need to always run `cb` (e.g. in the case of `this.recordingMode == 'buffer'`)
14712
14949
  const cbResult = cb();
14713
14950
 
14714
14951
  // If this option is turned on then we will only want to call `flush`
14715
14952
  // explicitly
14716
- if (this.recordingMode === 'error') {
14953
+ if (this.recordingMode === 'buffer') {
14717
14954
  return;
14718
14955
  }
14719
14956
 
@@ -14785,12 +15022,12 @@ class ReplayContainer {
14785
15022
  const oldSessionId = this.getSessionId();
14786
15023
 
14787
15024
  // Prevent starting a new session if the last user activity is older than
14788
- // SESSION_IDLE_DURATION. Otherwise non-user activity can trigger a new
15025
+ // SESSION_IDLE_PAUSE_DURATION. Otherwise non-user activity can trigger a new
14789
15026
  // session+recording. This creates noisy replays that do not have much
14790
15027
  // content in them.
14791
15028
  if (
14792
15029
  this._lastActivity &&
14793
- isExpired(this._lastActivity, this.timeouts.sessionIdle) &&
15030
+ isExpired(this._lastActivity, this.timeouts.sessionIdlePause) &&
14794
15031
  this.session &&
14795
15032
  this.session.sampled === 'session'
14796
15033
  ) {
@@ -14840,6 +15077,30 @@ class ReplayContainer {
14840
15077
  this._context.urls.push(url);
14841
15078
  }
14842
15079
 
15080
+ /**
15081
+ * Initialize and start all listeners to varying events (DOM,
15082
+ * Performance Observer, Recording, Sentry SDK, etc)
15083
+ */
15084
+ _initializeRecording() {
15085
+ this.setInitialState();
15086
+
15087
+ // this method is generally called on page load or manually - in both cases
15088
+ // we should treat it as an activity
15089
+ this._updateSessionActivity();
15090
+
15091
+ this.eventBuffer = createEventBuffer({
15092
+ useCompression: this._options.useCompression,
15093
+ });
15094
+
15095
+ this._removeListeners();
15096
+ this._addListeners();
15097
+
15098
+ // Need to set as enabled before we start recording, as `record()` can trigger a flush with a new checkout
15099
+ this._isEnabled = true;
15100
+
15101
+ this.startRecording();
15102
+ }
15103
+
14843
15104
  /** A wrapper to conditionally capture exceptions. */
14844
15105
  _handleException(error) {
14845
15106
  (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error('[Replay]', error);
@@ -14859,7 +15120,7 @@ class ReplayContainer {
14859
15120
  stickySession: Boolean(this._options.stickySession),
14860
15121
  currentSession: this.session,
14861
15122
  sessionSampleRate: this._options.sessionSampleRate,
14862
- errorSampleRate: this._options.errorSampleRate,
15123
+ allowBuffering: this._options.errorSampleRate > 0,
14863
15124
  });
14864
15125
 
14865
15126
  // If session was newly created (i.e. was not loaded from storage), then
@@ -14876,7 +15137,7 @@ class ReplayContainer {
14876
15137
  this.session = session;
14877
15138
 
14878
15139
  if (!this.session.sampled) {
14879
- this.stop('session unsampled');
15140
+ void this.stop('session unsampled');
14880
15141
  return false;
14881
15142
  }
14882
15143
 
@@ -15000,7 +15261,7 @@ class ReplayContainer {
15000
15261
  const isSessionActive = this.checkAndHandleExpiredSession();
15001
15262
 
15002
15263
  if (!isSessionActive) {
15003
- // If the user has come back to the page within SESSION_IDLE_DURATION
15264
+ // If the user has come back to the page within SESSION_IDLE_PAUSE_DURATION
15004
15265
  // ms, we will re-use the existing session, otherwise create a new
15005
15266
  // session
15006
15267
  (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Replay] Document has become active, but session has expired');
@@ -15074,7 +15335,7 @@ class ReplayContainer {
15074
15335
  * Only flush if `this.recordingMode === 'session'`
15075
15336
  */
15076
15337
  _conditionalFlush() {
15077
- if (this.recordingMode === 'error') {
15338
+ if (this.recordingMode === 'buffer') {
15078
15339
  return;
15079
15340
  }
15080
15341
 
@@ -15169,7 +15430,7 @@ class ReplayContainer {
15169
15430
  // This means we retried 3 times and all of them failed,
15170
15431
  // or we ran into a problem we don't want to retry, like rate limiting.
15171
15432
  // In this case, we want to completely stop the replay - otherwise, we may get inconsistent segments
15172
- this.stop('sendReplay');
15433
+ void this.stop('sendReplay');
15173
15434
 
15174
15435
  const client = getCurrentHub().getClient();
15175
15436
 
@@ -15183,8 +15444,12 @@ class ReplayContainer {
15183
15444
  * Flush recording data to Sentry. Creates a lock so that only a single flush
15184
15445
  * can be active at a time. Do not call this directly.
15185
15446
  */
15186
- __init16() {this._flush = async () => {
15187
- if (!this._isEnabled) {
15447
+ __init16() {this._flush = async ({
15448
+ force = false,
15449
+ }
15450
+
15451
+ = {}) => {
15452
+ if (!this._isEnabled && !force) {
15188
15453
  // This can happen if e.g. the replay was stopped because of exceeding the retry limit
15189
15454
  return;
15190
15455
  }
@@ -15265,23 +15530,6 @@ class ReplayContainer {
15265
15530
  };}
15266
15531
  }
15267
15532
 
15268
- function _getExperimentalOptions(options) {
15269
- const requestHeaders = options._experiments.captureRequestHeaders || [];
15270
- const responseHeaders = options._experiments.captureResponseHeaders || [];
15271
- const captureBodies = options._experiments.captureNetworkBodies || false;
15272
-
15273
- // Add defaults
15274
- const defaultHeaders = ['content-length', 'content-type', 'accept'];
15275
-
15276
- return {
15277
- network: {
15278
- captureBodies,
15279
- requestHeaders: [...defaultHeaders, ...requestHeaders.map(header => header.toLowerCase())],
15280
- responseHeaders: [...defaultHeaders, ...responseHeaders.map(header => header.toLowerCase())],
15281
- },
15282
- };
15283
- }
15284
-
15285
15533
  function getOption(
15286
15534
  selectors,
15287
15535
  defaultSelectors,
@@ -15385,6 +15633,8 @@ function isElectronNodeRenderer() {
15385
15633
  const MEDIA_SELECTORS =
15386
15634
  'img,image,svg,video,object,picture,embed,map,audio,link[rel="icon"],link[rel="apple-touch-icon"]';
15387
15635
 
15636
+ const DEFAULT_NETWORK_HEADERS = ['content-length', 'content-type', 'accept'];
15637
+
15388
15638
  let _initialized = false;
15389
15639
 
15390
15640
  /**
@@ -15425,6 +15675,11 @@ class Replay {
15425
15675
  maskAllInputs = true,
15426
15676
  blockAllMedia = true,
15427
15677
 
15678
+ networkDetailAllowUrls = [],
15679
+ networkCaptureBodies = true,
15680
+ networkRequestHeaders = [],
15681
+ networkResponseHeaders = [],
15682
+
15428
15683
  mask = [],
15429
15684
  unmask = [],
15430
15685
  block = [],
@@ -15483,6 +15738,11 @@ class Replay {
15483
15738
  errorSampleRate,
15484
15739
  useCompression,
15485
15740
  blockAllMedia,
15741
+ networkDetailAllowUrls,
15742
+ networkCaptureBodies,
15743
+ networkRequestHeaders: _getMergedNetworkHeaders(networkRequestHeaders),
15744
+ networkResponseHeaders: _getMergedNetworkHeaders(networkResponseHeaders),
15745
+
15486
15746
  _experiments,
15487
15747
  };
15488
15748
 
@@ -15536,14 +15796,7 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`,
15536
15796
  }
15537
15797
 
15538
15798
  /**
15539
- * We previously used to create a transaction in `setupOnce` and it would
15540
- * potentially create a transaction before some native SDK integrations have run
15541
- * and applied their own global event processor. An example is:
15542
- * https://github.com/getsentry/sentry-javascript/blob/b47ceafbdac7f8b99093ce6023726ad4687edc48/packages/browser/src/integrations/useragent.ts
15543
- *
15544
- * So we call `replay.setup` in next event loop as a workaround to wait for other
15545
- * global event processors to finish. This is no longer needed, but keeping it
15546
- * here to avoid any future issues.
15799
+ * Setup and initialize replay container
15547
15800
  */
15548
15801
  setupOnce() {
15549
15802
  if (!isBrowser()) {
@@ -15552,12 +15805,20 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`,
15552
15805
 
15553
15806
  this._setup();
15554
15807
 
15555
- // XXX: See method comments above
15556
- setTimeout(() => this.start());
15808
+ // Once upon a time, we tried to create a transaction in `setupOnce` and it would
15809
+ // potentially create a transaction before some native SDK integrations have run
15810
+ // and applied their own global event processor. An example is:
15811
+ // https://github.com/getsentry/sentry-javascript/blob/b47ceafbdac7f8b99093ce6023726ad4687edc48/packages/browser/src/integrations/useragent.ts
15812
+ //
15813
+ // So we call `this._initialize()` in next event loop as a workaround to wait for other
15814
+ // global event processors to finish. This is no longer needed, but keeping it
15815
+ // here to avoid any future issues.
15816
+ setTimeout(() => this._initialize());
15557
15817
  }
15558
15818
 
15559
15819
  /**
15560
- * Initializes the plugin.
15820
+ * Start a replay regardless of sampling rate. Calling this will always
15821
+ * create a new session. Will throw an error if replay is already in progress.
15561
15822
  *
15562
15823
  * Creates or loads a session, attaches listeners to varying events (DOM,
15563
15824
  * PerformanceObserver, Recording, Sentry SDK, etc)
@@ -15570,27 +15831,43 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`,
15570
15831
  this._replay.start();
15571
15832
  }
15572
15833
 
15834
+ /**
15835
+ * Start replay buffering. Buffers until `flush()` is called or, if
15836
+ * `replaysOnErrorSampleRate` > 0, until an error occurs.
15837
+ */
15838
+ startBuffering() {
15839
+ if (!this._replay) {
15840
+ return;
15841
+ }
15842
+
15843
+ this._replay.startBuffering();
15844
+ }
15845
+
15573
15846
  /**
15574
15847
  * Currently, this needs to be manually called (e.g. for tests). Sentry SDK
15575
15848
  * does not support a teardown
15576
15849
  */
15577
15850
  stop() {
15578
15851
  if (!this._replay) {
15579
- return;
15852
+ return Promise.resolve();
15580
15853
  }
15581
15854
 
15582
- this._replay.stop();
15855
+ return this._replay.stop();
15583
15856
  }
15584
15857
 
15585
15858
  /**
15586
- * Immediately send all pending events.
15859
+ * If not in "session" recording mode, flush event buffer which will create a new replay.
15860
+ * Unless `continueRecording` is false, the replay will continue to record and
15861
+ * behave as a "session"-based replay.
15862
+ *
15863
+ * Otherwise, queue up a flush.
15587
15864
  */
15588
- flush() {
15865
+ flush(options) {
15589
15866
  if (!this._replay || !this._replay.isEnabled()) {
15590
- return;
15867
+ return Promise.resolve();
15591
15868
  }
15592
15869
 
15593
- return this._replay.flushImmediate();
15870
+ return this._replay.sendBufferedReplayOrFlush(options);
15594
15871
  }
15595
15872
 
15596
15873
  /**
@@ -15603,6 +15880,16 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`,
15603
15880
 
15604
15881
  return this._replay.getSessionId();
15605
15882
  }
15883
+ /**
15884
+ * Initializes replay.
15885
+ */
15886
+ _initialize() {
15887
+ if (!this._replay) {
15888
+ return;
15889
+ }
15890
+
15891
+ this._replay.initializeSampling();
15892
+ }
15606
15893
 
15607
15894
  /** Setup the integration. */
15608
15895
  _setup() {
@@ -15652,6 +15939,10 @@ function loadReplayOptionsFromClient(initialOptions) {
15652
15939
  return finalOptions;
15653
15940
  }
15654
15941
 
15942
+ function _getMergedNetworkHeaders(headers) {
15943
+ return [...DEFAULT_NETWORK_HEADERS, ...headers.map(header => header.toLowerCase())];
15944
+ }
15945
+
15655
15946
  /**
15656
15947
  * Polyfill for the optional chain operator, `?.`, given previous conversion of the expression into an array of values,
15657
15948
  * descriptors, and functions.
@@ -16089,6 +16380,9 @@ class Postgres {
16089
16380
  const span = _optionalChain([parentSpan, 'optionalAccess', _6 => _6.startChild, 'call', _7 => _7({
16090
16381
  description: typeof config === 'string' ? config : (config ).text,
16091
16382
  op: 'db',
16383
+ data: {
16384
+ 'db.system': 'postgresql',
16385
+ },
16092
16386
  })]);
16093
16387
 
16094
16388
  if (typeof callback === 'function') {
@@ -16165,6 +16459,9 @@ class Mysql {constructor() { Mysql.prototype.__init.call(this); }
16165
16459
  const span = _optionalChain([parentSpan, 'optionalAccess', _4 => _4.startChild, 'call', _5 => _5({
16166
16460
  description: typeof options === 'string' ? options : (options ).sql,
16167
16461
  op: 'db',
16462
+ data: {
16463
+ 'db.system': 'mysql',
16464
+ },
16168
16465
  })]);
16169
16466
 
16170
16467
  if (typeof callback === 'function') {
@@ -16386,6 +16683,7 @@ class Mongo {
16386
16683
  collectionName: collection.collectionName,
16387
16684
  dbName: collection.dbName,
16388
16685
  namespace: collection.namespace,
16686
+ 'db.system': 'mongodb',
16389
16687
  };
16390
16688
  const spanContext = {
16391
16689
  op: 'db',
@@ -16474,7 +16772,10 @@ class Prisma {
16474
16772
  this._client.$use((params, next) => {
16475
16773
  const action = params.action;
16476
16774
  const model = params.model;
16477
- return trace({ name: model ? `${model} ${action}` : action, op: 'db.sql.prisma' }, () => next(params));
16775
+ return trace(
16776
+ { name: model ? `${model} ${action}` : action, op: 'db.sql.prisma', data: { 'db.system': 'prisma' } },
16777
+ () => next(params),
16778
+ );
16478
16779
  });
16479
16780
  }
16480
16781
  } Prisma.__initStatic();
@@ -29638,7 +29939,7 @@ const configGenerator = () => {
29638
29939
  let release;
29639
29940
  try {
29640
29941
  environment !== null && environment !== void 0 ? environment : (environment = "staging");
29641
- release !== null && release !== void 0 ? release : (release = "1.1.21");
29942
+ release !== null && release !== void 0 ? release : (release = "1.1.22");
29642
29943
  }
29643
29944
  catch (_a) {
29644
29945
  console.error('sentry configGenerator error');