@myinterview/widget-react 1.1.21-development-5e391f9 → 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/cjs/index.js CHANGED
@@ -2051,7 +2051,7 @@ function loadModule(moduleName) {
2051
2051
  * @returns A normalized version of the object, or `"**non-serializable**"` if any errors are thrown during normalization.
2052
2052
  */
2053
2053
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
2054
- function normalize(input, depth = +Infinity, maxProperties = +Infinity) {
2054
+ function normalize(input, depth = 100, maxProperties = +Infinity) {
2055
2055
  try {
2056
2056
  // since we're at the outermost level, we don't provide a key
2057
2057
  return visit('', input, depth, maxProperties);
@@ -2118,17 +2118,16 @@ function visit(
2118
2118
  return value ;
2119
2119
  }
2120
2120
 
2121
- // Do not normalize objects that we know have already been normalized. As a general rule, the
2122
- // "__sentry_skip_normalization__" property should only be used sparingly and only should only be set on objects that
2123
- // have already been normalized.
2124
- let overriddenDepth = depth;
2125
-
2126
- if (typeof (value )['__sentry_override_normalization_depth__'] === 'number') {
2127
- overriddenDepth = (value )['__sentry_override_normalization_depth__'] ;
2128
- }
2121
+ // We can set `__sentry_override_normalization_depth__` on an object to ensure that from there
2122
+ // We keep a certain amount of depth.
2123
+ // This should be used sparingly, e.g. we use it for the redux integration to ensure we get a certain amount of state.
2124
+ const remainingDepth =
2125
+ typeof (value )['__sentry_override_normalization_depth__'] === 'number'
2126
+ ? ((value )['__sentry_override_normalization_depth__'] )
2127
+ : depth;
2129
2128
 
2130
2129
  // We're also done if we've reached the max depth
2131
- if (overriddenDepth === 0) {
2130
+ if (remainingDepth === 0) {
2132
2131
  // At this point we know `serialized` is a string of the form `"[object XXXX]"`. Clean it up so it's just `"[XXXX]"`.
2133
2132
  return stringified.replace('object ', '');
2134
2133
  }
@@ -2144,7 +2143,7 @@ function visit(
2144
2143
  try {
2145
2144
  const jsonValue = valueWithToJSON.toJSON();
2146
2145
  // We need to normalize the return value of `.toJSON()` in case it has circular references
2147
- return visit('', jsonValue, overriddenDepth - 1, maxProperties, memo);
2146
+ return visit('', jsonValue, remainingDepth - 1, maxProperties, memo);
2148
2147
  } catch (err) {
2149
2148
  // pass (The built-in `toJSON` failed, but we can still try to do it ourselves)
2150
2149
  }
@@ -2173,7 +2172,7 @@ function visit(
2173
2172
 
2174
2173
  // Recursively visit all the child nodes
2175
2174
  const visitValue = visitable[visitKey];
2176
- normalized[visitKey] = visit(visitKey, visitValue, overriddenDepth - 1, maxProperties, memo);
2175
+ normalized[visitKey] = visit(visitKey, visitValue, remainingDepth - 1, maxProperties, memo);
2177
2176
 
2178
2177
  numAdded++;
2179
2178
  }
@@ -5823,7 +5822,7 @@ function getEventForEnvelopeItem(item, type) {
5823
5822
  return Array.isArray(item) ? (item )[1] : undefined;
5824
5823
  }
5825
5824
 
5826
- const SDK_VERSION = '7.49.0';
5825
+ const SDK_VERSION = '7.50.0';
5827
5826
 
5828
5827
  let originalFunctionToString;
5829
5828
 
@@ -5846,11 +5845,17 @@ class FunctionToString {constructor() { FunctionToString.prototype.__init.call(
5846
5845
  // eslint-disable-next-line @typescript-eslint/unbound-method
5847
5846
  originalFunctionToString = Function.prototype.toString;
5848
5847
 
5849
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
5850
- Function.prototype.toString = function ( ...args) {
5851
- const context = getOriginalFunction(this) || this;
5852
- return originalFunctionToString.apply(context, args);
5853
- };
5848
+ // intrinsics (like Function.prototype) might be immutable in some environments
5849
+ // e.g. Node with --frozen-intrinsics, XS (an embedded JavaScript engine) or SES (a JavaScript proposal)
5850
+ try {
5851
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
5852
+ Function.prototype.toString = function ( ...args) {
5853
+ const context = getOriginalFunction(this) || this;
5854
+ return originalFunctionToString.apply(context, args);
5855
+ };
5856
+ } catch (e) {
5857
+ // ignore errors here, just don't patch this
5858
+ }
5854
5859
  }
5855
5860
  } FunctionToString.__initStatic();
5856
5861
 
@@ -8294,11 +8299,14 @@ const REPLAY_SESSION_KEY = 'sentryReplaySession';
8294
8299
  const REPLAY_EVENT_NAME = 'replay_event';
8295
8300
  const UNABLE_TO_SEND_REPLAY = 'Unable to send Replay';
8296
8301
 
8297
- // The idle limit for a session
8298
- const SESSION_IDLE_DURATION = 300000; // 5 minutes in ms
8302
+ // The idle limit for a session after which recording is paused.
8303
+ const SESSION_IDLE_PAUSE_DURATION = 300000; // 5 minutes in ms
8304
+
8305
+ // The idle limit for a session after which the session expires.
8306
+ const SESSION_IDLE_EXPIRE_DURATION = 900000; // 15 minutes in ms
8299
8307
 
8300
8308
  // The maximum length of a session
8301
- const MAX_SESSION_LIFE = 3600000; // 60 minutes
8309
+ const MAX_SESSION_LIFE = 3600000; // 60 minutes in ms
8302
8310
 
8303
8311
  /** Default flush delays */
8304
8312
  const DEFAULT_FLUSH_MIN_DELAY = 5000;
@@ -8307,7 +8315,7 @@ const DEFAULT_FLUSH_MIN_DELAY = 5000;
8307
8315
  const DEFAULT_FLUSH_MAX_DELAY = 5500;
8308
8316
 
8309
8317
  /* How long to wait for error checkouts */
8310
- const ERROR_CHECKOUT_TIME = 60000;
8318
+ const BUFFER_CHECKOUT_TIME = 60000;
8311
8319
 
8312
8320
  const RETRY_BASE_INTERVAL = 5000;
8313
8321
  const RETRY_MAX_COUNT = 3;
@@ -8315,6 +8323,9 @@ const RETRY_MAX_COUNT = 3;
8315
8323
  /* The max (uncompressed) size in bytes of a network body. Any body larger than this will be truncated. */
8316
8324
  const NETWORK_BODY_MAX_SIZE = 150000;
8317
8325
 
8326
+ /* The max size of a single console arg that is captured. Any arg larger than this will be truncated. */
8327
+ const CONSOLE_ARG_MAX_SIZE = 5000;
8328
+
8318
8329
  var NodeType$1;
8319
8330
  (function (NodeType) {
8320
8331
  NodeType[NodeType["Document"] = 0] = "Document";
@@ -11979,6 +11990,31 @@ function createEventBuffer({ useCompression }) {
11979
11990
  return new EventBufferArray();
11980
11991
  }
11981
11992
 
11993
+ /**
11994
+ * Removes the session from Session Storage and unsets session in replay instance
11995
+ */
11996
+ function clearSession(replay) {
11997
+ deleteSession();
11998
+ replay.session = undefined;
11999
+ }
12000
+
12001
+ /**
12002
+ * Deletes a session from storage
12003
+ */
12004
+ function deleteSession() {
12005
+ const hasSessionStorage = 'sessionStorage' in WINDOW;
12006
+
12007
+ if (!hasSessionStorage) {
12008
+ return;
12009
+ }
12010
+
12011
+ try {
12012
+ WINDOW.sessionStorage.removeItem(REPLAY_SESSION_KEY);
12013
+ } catch (e) {
12014
+ // Ignore potential SecurityError exceptions
12015
+ }
12016
+ }
12017
+
11982
12018
  /**
11983
12019
  * Given an initial timestamp and an expiry duration, checks to see if current
11984
12020
  * time should be considered as expired.
@@ -12009,11 +12045,26 @@ function isSessionExpired(session, timeouts, targetTime = +new Date()) {
12009
12045
  // First, check that maximum session length has not been exceeded
12010
12046
  isExpired(session.started, timeouts.maxSessionLife, targetTime) ||
12011
12047
  // check that the idle timeout has not been exceeded (i.e. user has
12012
- // performed an action within the last `idleTimeout` ms)
12013
- isExpired(session.lastActivity, timeouts.sessionIdle, targetTime)
12048
+ // performed an action within the last `sessionIdleExpire` ms)
12049
+ isExpired(session.lastActivity, timeouts.sessionIdleExpire, targetTime)
12014
12050
  );
12015
12051
  }
12016
12052
 
12053
+ /**
12054
+ * Given a sample rate, returns true if replay should be sampled.
12055
+ *
12056
+ * 1.0 = 100% sampling
12057
+ * 0.0 = 0% sampling
12058
+ */
12059
+ function isSampled(sampleRate) {
12060
+ if (sampleRate === undefined) {
12061
+ return false;
12062
+ }
12063
+
12064
+ // Math.random() returns a number in range of 0 to 1 (inclusive of 0, but not 1)
12065
+ return Math.random() < sampleRate;
12066
+ }
12067
+
12017
12068
  /**
12018
12069
  * Save a session to session storage.
12019
12070
  */
@@ -12030,21 +12081,6 @@ function saveSession(session) {
12030
12081
  }
12031
12082
  }
12032
12083
 
12033
- /**
12034
- * Given a sample rate, returns true if replay should be sampled.
12035
- *
12036
- * 1.0 = 100% sampling
12037
- * 0.0 = 0% sampling
12038
- */
12039
- function isSampled(sampleRate) {
12040
- if (sampleRate === undefined) {
12041
- return false;
12042
- }
12043
-
12044
- // Math.random() returns a number in range of 0 to 1 (inclusive of 0, but not 1)
12045
- return Math.random() < sampleRate;
12046
- }
12047
-
12048
12084
  /**
12049
12085
  * Get a session with defaults & applied sampling.
12050
12086
  */
@@ -12063,14 +12099,15 @@ function makeSession(session) {
12063
12099
  lastActivity,
12064
12100
  segmentId,
12065
12101
  sampled,
12102
+ shouldRefresh: true,
12066
12103
  };
12067
12104
  }
12068
12105
 
12069
12106
  /**
12070
12107
  * Get the sampled status for a session based on sample rates & current sampled status.
12071
12108
  */
12072
- function getSessionSampleType(sessionSampleRate, errorSampleRate) {
12073
- return isSampled(sessionSampleRate) ? 'session' : isSampled(errorSampleRate) ? 'error' : false;
12109
+ function getSessionSampleType(sessionSampleRate, allowBuffering) {
12110
+ return isSampled(sessionSampleRate) ? 'session' : allowBuffering ? 'buffer' : false;
12074
12111
  }
12075
12112
 
12076
12113
  /**
@@ -12078,8 +12115,8 @@ function getSessionSampleType(sessionSampleRate, errorSampleRate) {
12078
12115
  * that all replays will be saved to as attachments. Currently, we only expect
12079
12116
  * one of these Sentry events per "replay session".
12080
12117
  */
12081
- function createSession({ sessionSampleRate, errorSampleRate, stickySession = false }) {
12082
- const sampled = getSessionSampleType(sessionSampleRate, errorSampleRate);
12118
+ function createSession({ sessionSampleRate, allowBuffering, stickySession = false }) {
12119
+ const sampled = getSessionSampleType(sessionSampleRate, allowBuffering);
12083
12120
  const session = makeSession({
12084
12121
  sampled,
12085
12122
  });
@@ -12127,7 +12164,7 @@ function getSession({
12127
12164
  currentSession,
12128
12165
  stickySession,
12129
12166
  sessionSampleRate,
12130
- errorSampleRate,
12167
+ allowBuffering,
12131
12168
  }) {
12132
12169
  // If session exists and is passed, use it instead of always hitting session storage
12133
12170
  const session = currentSession || (stickySession && fetchSession());
@@ -12140,8 +12177,9 @@ function getSession({
12140
12177
 
12141
12178
  if (!isExpired) {
12142
12179
  return { type: 'saved', session };
12143
- } else if (session.sampled === 'error') {
12144
- // Error samples should not be re-created when expired, but instead we stop when the replay is done
12180
+ } else if (!session.shouldRefresh) {
12181
+ // In this case, stop
12182
+ // This is the case if we have an error session that is completed (=triggered an error)
12145
12183
  const discardedSession = makeSession({ sampled: false });
12146
12184
  return { type: 'new', session: discardedSession };
12147
12185
  } else {
@@ -12153,7 +12191,7 @@ function getSession({
12153
12191
  const newSession = createSession({
12154
12192
  stickySession,
12155
12193
  sessionSampleRate,
12156
- errorSampleRate,
12194
+ allowBuffering,
12157
12195
  });
12158
12196
 
12159
12197
  return { type: 'new', session: newSession };
@@ -12187,7 +12225,7 @@ async function addEvent(
12187
12225
  // page has been left open and idle for a long period of time and user
12188
12226
  // comes back to trigger a new session. The performance entries rely on
12189
12227
  // `performance.timeOrigin`, which is when the page first opened.
12190
- if (timestampInMs + replay.timeouts.sessionIdle < Date.now()) {
12228
+ if (timestampInMs + replay.timeouts.sessionIdlePause < Date.now()) {
12191
12229
  return null;
12192
12230
  }
12193
12231
 
@@ -12202,7 +12240,7 @@ async function addEvent(
12202
12240
  return await replay.eventBuffer.addEvent(event, isCheckout);
12203
12241
  } catch (error) {
12204
12242
  (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error(error);
12205
- replay.stop('addEvent');
12243
+ await replay.stop('addEvent');
12206
12244
 
12207
12245
  const client = getCurrentHub().getClient();
12208
12246
 
@@ -12269,23 +12307,17 @@ function handleAfterSendEvent(replay) {
12269
12307
  // Trigger error recording
12270
12308
  // Need to be very careful that this does not cause an infinite loop
12271
12309
  if (
12272
- replay.recordingMode === 'error' &&
12310
+ replay.recordingMode === 'buffer' &&
12273
12311
  event.exception &&
12274
12312
  event.message !== UNABLE_TO_SEND_REPLAY // ignore this error because otherwise we could loop indefinitely with trying to capture replay and failing
12275
12313
  ) {
12276
- setTimeout(async () => {
12277
- // Allow flush to complete before resuming as a session recording, otherwise
12278
- // the checkout from `startRecording` may be included in the payload.
12279
- // Prefer to keep the error replay as a separate (and smaller) segment
12280
- // than the session replay.
12281
- await replay.flushImmediate();
12282
-
12283
- if (replay.stopRecording()) {
12284
- // Reset all "capture on error" configuration before
12285
- // starting a new recording
12286
- replay.recordingMode = 'session';
12287
- replay.startRecording();
12288
- }
12314
+ if (!isSampled(replay.getOptions().errorSampleRate)) {
12315
+ return;
12316
+ }
12317
+
12318
+ setTimeout(() => {
12319
+ // Capture current event buffer as new replay
12320
+ void replay.sendBufferedReplayOrFlush();
12289
12321
  });
12290
12322
  }
12291
12323
  };
@@ -13240,6 +13272,17 @@ function makeNetworkReplayBreadcrumb(
13240
13272
  return result;
13241
13273
  }
13242
13274
 
13275
+ /** Build the request or response part of a replay network breadcrumb that was skipped. */
13276
+ function buildSkippedNetworkRequestOrResponse(bodySize) {
13277
+ return {
13278
+ headers: {},
13279
+ size: bodySize,
13280
+ _meta: {
13281
+ warnings: ['URL_SKIPPED'],
13282
+ },
13283
+ };
13284
+ }
13285
+
13243
13286
  /** Build the request or response part of a replay network breadcrumb. */
13244
13287
  function buildNetworkRequestOrResponse(
13245
13288
  headers,
@@ -13320,8 +13363,8 @@ function normalizeNetworkBody(body)
13320
13363
  };
13321
13364
  } catch (e3) {
13322
13365
  return {
13323
- body,
13324
- warnings: ['INVALID_JSON'],
13366
+ body: exceedsSizeLimit ? `${body.slice(0, NETWORK_BODY_MAX_SIZE)}…` : body,
13367
+ warnings: exceedsSizeLimit ? ['INVALID_JSON', 'TEXT_TRUNCATED'] : ['INVALID_JSON'],
13325
13368
  };
13326
13369
  }
13327
13370
  }
@@ -13340,6 +13383,11 @@ function _strIsProbablyJson(str) {
13340
13383
  return (first === '[' && last === ']') || (first === '{' && last === '}');
13341
13384
  }
13342
13385
 
13386
+ /** Match an URL against a list of strings/Regex. */
13387
+ function urlMatches(url, urls) {
13388
+ return stringMatchesSomePattern(url, urls);
13389
+ }
13390
+
13343
13391
  /**
13344
13392
  * Capture a fetch breadcrumb to a replay.
13345
13393
  * This adds additional data (where approriate).
@@ -13399,33 +13447,37 @@ async function _prepareFetchData(
13399
13447
  const {
13400
13448
  url,
13401
13449
  method,
13402
- status_code: statusCode,
13450
+ status_code: statusCode = 0,
13403
13451
  request_body_size: requestBodySize,
13404
13452
  response_body_size: responseBodySize,
13405
13453
  } = breadcrumb.data;
13406
13454
 
13407
- const request = _getRequestInfo(options, hint.input, requestBodySize);
13408
- const response = await _getResponseInfo(options, hint.response, responseBodySize);
13455
+ const captureDetails = urlMatches(url, options.networkDetailAllowUrls);
13456
+
13457
+ const request = captureDetails
13458
+ ? _getRequestInfo(options, hint.input, requestBodySize)
13459
+ : buildSkippedNetworkRequestOrResponse(requestBodySize);
13460
+ const response = await _getResponseInfo(captureDetails, options, hint.response, responseBodySize);
13409
13461
 
13410
13462
  return {
13411
13463
  startTimestamp,
13412
13464
  endTimestamp,
13413
13465
  url,
13414
13466
  method,
13415
- statusCode: statusCode || 0,
13467
+ statusCode,
13416
13468
  request,
13417
13469
  response,
13418
13470
  };
13419
13471
  }
13420
13472
 
13421
13473
  function _getRequestInfo(
13422
- { captureBodies, requestHeaders },
13474
+ { networkCaptureBodies, networkRequestHeaders },
13423
13475
  input,
13424
13476
  requestBodySize,
13425
13477
  ) {
13426
- const headers = getRequestHeaders(input, requestHeaders);
13478
+ const headers = getRequestHeaders(input, networkRequestHeaders);
13427
13479
 
13428
- if (!captureBodies) {
13480
+ if (!networkCaptureBodies) {
13429
13481
  return buildNetworkRequestOrResponse(headers, requestBodySize, undefined);
13430
13482
  }
13431
13483
 
@@ -13436,19 +13488,24 @@ function _getRequestInfo(
13436
13488
  }
13437
13489
 
13438
13490
  async function _getResponseInfo(
13491
+ captureDetails,
13439
13492
  {
13440
- captureBodies,
13493
+ networkCaptureBodies,
13441
13494
  textEncoder,
13442
- responseHeaders,
13495
+ networkResponseHeaders,
13443
13496
  }
13444
13497
 
13445
13498
  ,
13446
13499
  response,
13447
13500
  responseBodySize,
13448
13501
  ) {
13449
- const headers = getAllHeaders(response.headers, responseHeaders);
13502
+ if (!captureDetails && responseBodySize !== undefined) {
13503
+ return buildSkippedNetworkRequestOrResponse(responseBodySize);
13504
+ }
13450
13505
 
13451
- if (!captureBodies && responseBodySize !== undefined) {
13506
+ const headers = getAllHeaders(response.headers, networkResponseHeaders);
13507
+
13508
+ if (!networkCaptureBodies && responseBodySize !== undefined) {
13452
13509
  return buildNetworkRequestOrResponse(headers, responseBodySize, undefined);
13453
13510
  }
13454
13511
 
@@ -13463,7 +13520,11 @@ async function _getResponseInfo(
13463
13520
  ? getBodySize(bodyText, textEncoder)
13464
13521
  : responseBodySize;
13465
13522
 
13466
- if (captureBodies) {
13523
+ if (!captureDetails) {
13524
+ return buildSkippedNetworkRequestOrResponse(size);
13525
+ }
13526
+
13527
+ if (networkCaptureBodies) {
13467
13528
  return buildNetworkRequestOrResponse(headers, size, bodyText);
13468
13529
  }
13469
13530
 
@@ -13596,28 +13657,44 @@ function _prepareXhrData(
13596
13657
  const {
13597
13658
  url,
13598
13659
  method,
13599
- status_code: statusCode,
13660
+ status_code: statusCode = 0,
13600
13661
  request_body_size: requestBodySize,
13601
13662
  response_body_size: responseBodySize,
13602
13663
  } = breadcrumb.data;
13603
13664
 
13604
- const xhrInfo = xhr[SENTRY_XHR_DATA_KEY];
13605
- const requestHeaders = xhrInfo ? getAllowedHeaders(xhrInfo.request_headers, options.requestHeaders) : {};
13606
- const responseHeaders = getAllowedHeaders(getResponseHeaders(xhr), options.responseHeaders);
13607
-
13608
13665
  if (!url) {
13609
13666
  return null;
13610
13667
  }
13611
13668
 
13669
+ if (!urlMatches(url, options.networkDetailAllowUrls)) {
13670
+ const request = buildSkippedNetworkRequestOrResponse(requestBodySize);
13671
+ const response = buildSkippedNetworkRequestOrResponse(responseBodySize);
13672
+ return {
13673
+ startTimestamp,
13674
+ endTimestamp,
13675
+ url,
13676
+ method,
13677
+ statusCode,
13678
+ request,
13679
+ response,
13680
+ };
13681
+ }
13682
+
13683
+ const xhrInfo = xhr[SENTRY_XHR_DATA_KEY];
13684
+ const networkRequestHeaders = xhrInfo
13685
+ ? getAllowedHeaders(xhrInfo.request_headers, options.networkRequestHeaders)
13686
+ : {};
13687
+ const networkResponseHeaders = getAllowedHeaders(getResponseHeaders(xhr), options.networkResponseHeaders);
13688
+
13612
13689
  const request = buildNetworkRequestOrResponse(
13613
- requestHeaders,
13690
+ networkRequestHeaders,
13614
13691
  requestBodySize,
13615
- options.captureBodies ? getBodyString(input) : undefined,
13692
+ options.networkCaptureBodies ? getBodyString(input) : undefined,
13616
13693
  );
13617
13694
  const response = buildNetworkRequestOrResponse(
13618
- responseHeaders,
13695
+ networkResponseHeaders,
13619
13696
  responseBodySize,
13620
- options.captureBodies ? hint.xhr.responseText : undefined,
13697
+ options.networkCaptureBodies ? hint.xhr.responseText : undefined,
13621
13698
  );
13622
13699
 
13623
13700
  return {
@@ -13625,7 +13702,7 @@ function _prepareXhrData(
13625
13702
  endTimestamp,
13626
13703
  url,
13627
13704
  method,
13628
- statusCode: statusCode || 0,
13705
+ statusCode,
13629
13706
  request,
13630
13707
  response,
13631
13708
  };
@@ -13657,10 +13734,16 @@ function handleNetworkBreadcrumbs(replay) {
13657
13734
  try {
13658
13735
  const textEncoder = new TextEncoder();
13659
13736
 
13737
+ const { networkDetailAllowUrls, networkCaptureBodies, networkRequestHeaders, networkResponseHeaders } =
13738
+ replay.getOptions();
13739
+
13660
13740
  const options = {
13661
13741
  replay,
13662
13742
  textEncoder,
13663
- ...replay.getExperimentalOptions().network,
13743
+ networkDetailAllowUrls,
13744
+ networkCaptureBodies,
13745
+ networkRequestHeaders,
13746
+ networkResponseHeaders,
13664
13747
  };
13665
13748
 
13666
13749
  if (client && client.on) {
@@ -13768,9 +13851,66 @@ function handleScope(scope) {
13768
13851
  return null;
13769
13852
  }
13770
13853
 
13854
+ if (newBreadcrumb.category === 'console') {
13855
+ return normalizeConsoleBreadcrumb(newBreadcrumb);
13856
+ }
13857
+
13771
13858
  return createBreadcrumb(newBreadcrumb);
13772
13859
  }
13773
13860
 
13861
+ /** exported for tests only */
13862
+ function normalizeConsoleBreadcrumb(breadcrumb) {
13863
+ const args = breadcrumb.data && breadcrumb.data.arguments;
13864
+
13865
+ if (!Array.isArray(args) || args.length === 0) {
13866
+ return createBreadcrumb(breadcrumb);
13867
+ }
13868
+
13869
+ let isTruncated = false;
13870
+
13871
+ // Avoid giant args captures
13872
+ const normalizedArgs = args.map(arg => {
13873
+ if (!arg) {
13874
+ return arg;
13875
+ }
13876
+ if (typeof arg === 'string') {
13877
+ if (arg.length > CONSOLE_ARG_MAX_SIZE) {
13878
+ isTruncated = true;
13879
+ return `${arg.slice(0, CONSOLE_ARG_MAX_SIZE)}…`;
13880
+ }
13881
+
13882
+ return arg;
13883
+ }
13884
+ if (typeof arg === 'object') {
13885
+ try {
13886
+ const normalizedArg = normalize(arg, 7);
13887
+ const stringified = JSON.stringify(normalizedArg);
13888
+ if (stringified.length > CONSOLE_ARG_MAX_SIZE) {
13889
+ const fixedJson = fixJson(stringified.slice(0, CONSOLE_ARG_MAX_SIZE));
13890
+ const json = JSON.parse(fixedJson);
13891
+ // We only set this after JSON.parse() was successfull, so we know we didn't run into `catch`
13892
+ isTruncated = true;
13893
+ return json;
13894
+ }
13895
+ return normalizedArg;
13896
+ } catch (e) {
13897
+ // fall back to default
13898
+ }
13899
+ }
13900
+
13901
+ return arg;
13902
+ });
13903
+
13904
+ return createBreadcrumb({
13905
+ ...breadcrumb,
13906
+ data: {
13907
+ ...breadcrumb.data,
13908
+ arguments: normalizedArgs,
13909
+ ...(isTruncated ? { _meta: { warnings: ['CONSOLE_ARG_TRUNCATED'] } } : {}),
13910
+ },
13911
+ });
13912
+ }
13913
+
13774
13914
  /**
13775
13915
  * Add global listeners that cannot be removed.
13776
13916
  */
@@ -13795,7 +13935,7 @@ function addGlobalListeners(replay) {
13795
13935
  client.on('afterSendEvent', handleAfterSendEvent(replay));
13796
13936
  client.on('createDsc', (dsc) => {
13797
13937
  const replayId = replay.getSessionId();
13798
- if (replayId) {
13938
+ if (replayId && replay.isEnabled()) {
13799
13939
  dsc.replay_id = replayId;
13800
13940
  }
13801
13941
  });
@@ -14103,7 +14243,7 @@ function getHandleRecordingEmit(replay) {
14103
14243
  // when an error occurs. Clear any state that happens before this current
14104
14244
  // checkout. This needs to happen before `addEvent()` which updates state
14105
14245
  // dependent on this reset.
14106
- if (replay.recordingMode === 'error' && isCheckout) {
14246
+ if (replay.recordingMode === 'buffer' && isCheckout) {
14107
14247
  replay.setInitialState();
14108
14248
  }
14109
14249
 
@@ -14129,7 +14269,7 @@ function getHandleRecordingEmit(replay) {
14129
14269
 
14130
14270
  // See note above re: session start needs to reflect the most recent
14131
14271
  // checkout.
14132
- if (replay.recordingMode === 'error' && replay.session) {
14272
+ if (replay.recordingMode === 'buffer' && replay.session) {
14133
14273
  const { earliestEvent } = replay.getContext();
14134
14274
  if (earliestEvent) {
14135
14275
  replay.session.started = earliestEvent;
@@ -14478,9 +14618,11 @@ class ReplayContainer {
14478
14618
  __init2() {this.performanceEvents = [];}
14479
14619
 
14480
14620
  /**
14481
- * Recording can happen in one of two modes:
14482
- * * session: Record the whole session, sending it continuously
14483
- * * error: Always keep the last 60s of recording, and when an error occurs, send it immediately
14621
+ * Recording can happen in one of three modes:
14622
+ * - session: Record the whole session, sending it continuously
14623
+ * - buffer: Always keep the last 60s of recording, requires:
14624
+ * - having replaysOnErrorSampleRate > 0 to capture replay when an error occurs
14625
+ * - or calling `flush()` to send the replay
14484
14626
  */
14485
14627
  __init3() {this.recordingMode = 'session';}
14486
14628
 
@@ -14489,7 +14631,8 @@ class ReplayContainer {
14489
14631
  * @hidden
14490
14632
  */
14491
14633
  __init4() {this.timeouts = {
14492
- sessionIdle: SESSION_IDLE_DURATION,
14634
+ sessionIdlePause: SESSION_IDLE_PAUSE_DURATION,
14635
+ sessionIdleExpire: SESSION_IDLE_EXPIRE_DURATION,
14493
14636
  maxSessionLife: MAX_SESSION_LIFE,
14494
14637
  }; }
14495
14638
 
@@ -14550,8 +14693,6 @@ class ReplayContainer {
14550
14693
  this._debouncedFlush = debounce(() => this._flush(), this._options.flushMinDelay, {
14551
14694
  maxWait: this._options.flushMaxDelay,
14552
14695
  });
14553
-
14554
- this._experimentalOptions = _getExperimentalOptions(options);
14555
14696
  }
14556
14697
 
14557
14698
  /** Get the event context. */
@@ -14575,58 +14716,102 @@ class ReplayContainer {
14575
14716
  }
14576
14717
 
14577
14718
  /**
14578
- * Get the experimental options.
14579
- * THIS IS INTERNAL AND SUBJECT TO CHANGE!
14580
- * @hidden
14719
+ * Initializes the plugin based on sampling configuration. Should not be
14720
+ * called outside of constructor.
14581
14721
  */
14582
- getExperimentalOptions() {
14583
- return this._experimentalOptions;
14722
+ initializeSampling() {
14723
+ const { errorSampleRate, sessionSampleRate } = this._options;
14724
+
14725
+ // If neither sample rate is > 0, then do nothing - user will need to call one of
14726
+ // `start()` or `startBuffering` themselves.
14727
+ if (errorSampleRate <= 0 && sessionSampleRate <= 0) {
14728
+ return;
14729
+ }
14730
+
14731
+ // Otherwise if there is _any_ sample rate set, try to load an existing
14732
+ // session, or create a new one.
14733
+ const isSessionSampled = this._loadAndCheckSession();
14734
+
14735
+ if (!isSessionSampled) {
14736
+ // This should only occur if `errorSampleRate` is 0 and was unsampled for
14737
+ // session-based replay. In this case there is nothing to do.
14738
+ return;
14739
+ }
14740
+
14741
+ if (!this.session) {
14742
+ // This should not happen, something wrong has occurred
14743
+ this._handleException(new Error('Unable to initialize and create session'));
14744
+ return;
14745
+ }
14746
+
14747
+ if (this.session.sampled && this.session.sampled !== 'session') {
14748
+ // If not sampled as session-based, then recording mode will be `buffer`
14749
+ // Note that we don't explicitly check if `sampled === 'buffer'` because we
14750
+ // could have sessions from Session storage that are still `error` from
14751
+ // prior SDK version.
14752
+ this.recordingMode = 'buffer';
14753
+ }
14754
+
14755
+ this._initializeRecording();
14584
14756
  }
14585
14757
 
14586
14758
  /**
14587
- * Initializes the plugin.
14759
+ * Start a replay regardless of sampling rate. Calling this will always
14760
+ * create a new session. Will throw an error if replay is already in progress.
14588
14761
  *
14589
14762
  * Creates or loads a session, attaches listeners to varying events (DOM,
14590
14763
  * _performanceObserver, Recording, Sentry SDK, etc)
14591
14764
  */
14592
14765
  start() {
14593
- this.setInitialState();
14594
-
14595
- if (!this._loadAndCheckSession()) {
14596
- return;
14766
+ if (this._isEnabled && this.recordingMode === 'session') {
14767
+ throw new Error('Replay recording is already in progress');
14597
14768
  }
14598
14769
 
14599
- // If there is no session, then something bad has happened - can't continue
14600
- if (!this.session) {
14601
- this._handleException(new Error('No session found'));
14602
- return;
14770
+ if (this._isEnabled && this.recordingMode === 'buffer') {
14771
+ throw new Error('Replay buffering is in progress, call `flush()` to save the replay');
14603
14772
  }
14604
14773
 
14605
- if (!this.session.sampled) {
14606
- // If session was not sampled, then we do not initialize the integration at all.
14607
- return;
14608
- }
14774
+ const previousSessionId = this.session && this.session.id;
14775
+
14776
+ const { session } = getSession({
14777
+ timeouts: this.timeouts,
14778
+ stickySession: Boolean(this._options.stickySession),
14779
+ currentSession: this.session,
14780
+ // This is intentional: create a new session-based replay when calling `start()`
14781
+ sessionSampleRate: 1,
14782
+ allowBuffering: false,
14783
+ });
14609
14784
 
14610
- // If session is sampled for errors, then we need to set the recordingMode
14611
- // to 'error', which will configure recording with different options.
14612
- if (this.session.sampled === 'error') {
14613
- this.recordingMode = 'error';
14785
+ session.previousSessionId = previousSessionId;
14786
+ this.session = session;
14787
+
14788
+ this._initializeRecording();
14789
+ }
14790
+
14791
+ /**
14792
+ * Start replay buffering. Buffers until `flush()` is called or, if
14793
+ * `replaysOnErrorSampleRate` > 0, an error occurs.
14794
+ */
14795
+ startBuffering() {
14796
+ if (this._isEnabled) {
14797
+ throw new Error('Replay recording is already in progress');
14614
14798
  }
14615
14799
 
14616
- // setup() is generally called on page load or manually - in both cases we
14617
- // should treat it as an activity
14618
- this._updateSessionActivity();
14800
+ const previousSessionId = this.session && this.session.id;
14619
14801
 
14620
- this.eventBuffer = createEventBuffer({
14621
- useCompression: this._options.useCompression,
14802
+ const { session } = getSession({
14803
+ timeouts: this.timeouts,
14804
+ stickySession: Boolean(this._options.stickySession),
14805
+ currentSession: this.session,
14806
+ sessionSampleRate: 0,
14807
+ allowBuffering: true,
14622
14808
  });
14623
14809
 
14624
- this._addListeners();
14625
-
14626
- // Need to set as enabled before we start recording, as `record()` can trigger a flush with a new checkout
14627
- this._isEnabled = true;
14810
+ session.previousSessionId = previousSessionId;
14811
+ this.session = session;
14628
14812
 
14629
- this.startRecording();
14813
+ this.recordingMode = 'buffer';
14814
+ this._initializeRecording();
14630
14815
  }
14631
14816
 
14632
14817
  /**
@@ -14641,7 +14826,7 @@ class ReplayContainer {
14641
14826
  // When running in error sampling mode, we need to overwrite `checkoutEveryNms`
14642
14827
  // Without this, it would record forever, until an error happens, which we don't want
14643
14828
  // instead, we'll always keep the last 60 seconds of replay before an error happened
14644
- ...(this.recordingMode === 'error' && { checkoutEveryNms: ERROR_CHECKOUT_TIME }),
14829
+ ...(this.recordingMode === 'buffer' && { checkoutEveryNms: BUFFER_CHECKOUT_TIME }),
14645
14830
  emit: getHandleRecordingEmit(this),
14646
14831
  onMutation: this._onMutationHandler,
14647
14832
  });
@@ -14652,17 +14837,18 @@ class ReplayContainer {
14652
14837
 
14653
14838
  /**
14654
14839
  * Stops the recording, if it was running.
14655
- * Returns true if it was stopped, else false.
14840
+ *
14841
+ * Returns true if it was previously stopped, or is now stopped,
14842
+ * otherwise false.
14656
14843
  */
14657
14844
  stopRecording() {
14658
14845
  try {
14659
14846
  if (this._stopRecording) {
14660
14847
  this._stopRecording();
14661
14848
  this._stopRecording = undefined;
14662
- return true;
14663
14849
  }
14664
14850
 
14665
- return false;
14851
+ return true;
14666
14852
  } catch (err) {
14667
14853
  this._handleException(err);
14668
14854
  return false;
@@ -14673,7 +14859,7 @@ class ReplayContainer {
14673
14859
  * Currently, this needs to be manually called (e.g. for tests). Sentry SDK
14674
14860
  * does not support a teardown
14675
14861
  */
14676
- stop(reason) {
14862
+ async stop(reason) {
14677
14863
  if (!this._isEnabled) {
14678
14864
  return;
14679
14865
  }
@@ -14689,12 +14875,24 @@ class ReplayContainer {
14689
14875
  log(msg);
14690
14876
  }
14691
14877
 
14878
+ // We can't move `_isEnabled` after awaiting a flush, otherwise we can
14879
+ // enter into an infinite loop when `stop()` is called while flushing.
14692
14880
  this._isEnabled = false;
14693
14881
  this._removeListeners();
14694
14882
  this.stopRecording();
14883
+
14884
+ this._debouncedFlush.cancel();
14885
+ // See comment above re: `_isEnabled`, we "force" a flush, ignoring the
14886
+ // `_isEnabled` state of the plugin since it was disabled above.
14887
+ await this._flush({ force: true });
14888
+
14889
+ // After flush, destroy event buffer
14695
14890
  this.eventBuffer && this.eventBuffer.destroy();
14696
14891
  this.eventBuffer = null;
14697
- this._debouncedFlush.cancel();
14892
+
14893
+ // Clear session from session storage, note this means if a new session
14894
+ // is started after, it will not have `previousSessionId`
14895
+ clearSession(this);
14698
14896
  } catch (err) {
14699
14897
  this._handleException(err);
14700
14898
  }
@@ -14725,6 +14923,45 @@ class ReplayContainer {
14725
14923
  this.startRecording();
14726
14924
  }
14727
14925
 
14926
+ /**
14927
+ * If not in "session" recording mode, flush event buffer which will create a new replay.
14928
+ * Unless `continueRecording` is false, the replay will continue to record and
14929
+ * behave as a "session"-based replay.
14930
+ *
14931
+ * Otherwise, queue up a flush.
14932
+ */
14933
+ async sendBufferedReplayOrFlush({ continueRecording = true } = {}) {
14934
+ if (this.recordingMode === 'session') {
14935
+ return this.flushImmediate();
14936
+ }
14937
+
14938
+ // Allow flush to complete before resuming as a session recording, otherwise
14939
+ // the checkout from `startRecording` may be included in the payload.
14940
+ // Prefer to keep the error replay as a separate (and smaller) segment
14941
+ // than the session replay.
14942
+ await this.flushImmediate();
14943
+
14944
+ const hasStoppedRecording = this.stopRecording();
14945
+
14946
+ if (!continueRecording || !hasStoppedRecording) {
14947
+ return;
14948
+ }
14949
+
14950
+ // Re-start recording, but in "session" recording mode
14951
+
14952
+ // Reset all "capture on error" configuration before
14953
+ // starting a new recording
14954
+ this.recordingMode = 'session';
14955
+
14956
+ // Once this session ends, we do not want to refresh it
14957
+ if (this.session) {
14958
+ this.session.shouldRefresh = false;
14959
+ this._maybeSaveSession();
14960
+ }
14961
+
14962
+ this.startRecording();
14963
+ }
14964
+
14728
14965
  /**
14729
14966
  * We want to batch uploads of replay events. Save events only if
14730
14967
  * `<flushMinDelay>` milliseconds have elapsed since the last event
@@ -14734,12 +14971,12 @@ class ReplayContainer {
14734
14971
  * processing and hand back control to caller.
14735
14972
  */
14736
14973
  addUpdate(cb) {
14737
- // We need to always run `cb` (e.g. in the case of `this.recordingMode == 'error'`)
14974
+ // We need to always run `cb` (e.g. in the case of `this.recordingMode == 'buffer'`)
14738
14975
  const cbResult = cb();
14739
14976
 
14740
14977
  // If this option is turned on then we will only want to call `flush`
14741
14978
  // explicitly
14742
- if (this.recordingMode === 'error') {
14979
+ if (this.recordingMode === 'buffer') {
14743
14980
  return;
14744
14981
  }
14745
14982
 
@@ -14811,12 +15048,12 @@ class ReplayContainer {
14811
15048
  const oldSessionId = this.getSessionId();
14812
15049
 
14813
15050
  // Prevent starting a new session if the last user activity is older than
14814
- // SESSION_IDLE_DURATION. Otherwise non-user activity can trigger a new
15051
+ // SESSION_IDLE_PAUSE_DURATION. Otherwise non-user activity can trigger a new
14815
15052
  // session+recording. This creates noisy replays that do not have much
14816
15053
  // content in them.
14817
15054
  if (
14818
15055
  this._lastActivity &&
14819
- isExpired(this._lastActivity, this.timeouts.sessionIdle) &&
15056
+ isExpired(this._lastActivity, this.timeouts.sessionIdlePause) &&
14820
15057
  this.session &&
14821
15058
  this.session.sampled === 'session'
14822
15059
  ) {
@@ -14866,6 +15103,30 @@ class ReplayContainer {
14866
15103
  this._context.urls.push(url);
14867
15104
  }
14868
15105
 
15106
+ /**
15107
+ * Initialize and start all listeners to varying events (DOM,
15108
+ * Performance Observer, Recording, Sentry SDK, etc)
15109
+ */
15110
+ _initializeRecording() {
15111
+ this.setInitialState();
15112
+
15113
+ // this method is generally called on page load or manually - in both cases
15114
+ // we should treat it as an activity
15115
+ this._updateSessionActivity();
15116
+
15117
+ this.eventBuffer = createEventBuffer({
15118
+ useCompression: this._options.useCompression,
15119
+ });
15120
+
15121
+ this._removeListeners();
15122
+ this._addListeners();
15123
+
15124
+ // Need to set as enabled before we start recording, as `record()` can trigger a flush with a new checkout
15125
+ this._isEnabled = true;
15126
+
15127
+ this.startRecording();
15128
+ }
15129
+
14869
15130
  /** A wrapper to conditionally capture exceptions. */
14870
15131
  _handleException(error) {
14871
15132
  (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error('[Replay]', error);
@@ -14885,7 +15146,7 @@ class ReplayContainer {
14885
15146
  stickySession: Boolean(this._options.stickySession),
14886
15147
  currentSession: this.session,
14887
15148
  sessionSampleRate: this._options.sessionSampleRate,
14888
- errorSampleRate: this._options.errorSampleRate,
15149
+ allowBuffering: this._options.errorSampleRate > 0,
14889
15150
  });
14890
15151
 
14891
15152
  // If session was newly created (i.e. was not loaded from storage), then
@@ -14902,7 +15163,7 @@ class ReplayContainer {
14902
15163
  this.session = session;
14903
15164
 
14904
15165
  if (!this.session.sampled) {
14905
- this.stop('session unsampled');
15166
+ void this.stop('session unsampled');
14906
15167
  return false;
14907
15168
  }
14908
15169
 
@@ -15026,7 +15287,7 @@ class ReplayContainer {
15026
15287
  const isSessionActive = this.checkAndHandleExpiredSession();
15027
15288
 
15028
15289
  if (!isSessionActive) {
15029
- // If the user has come back to the page within SESSION_IDLE_DURATION
15290
+ // If the user has come back to the page within SESSION_IDLE_PAUSE_DURATION
15030
15291
  // ms, we will re-use the existing session, otherwise create a new
15031
15292
  // session
15032
15293
  (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Replay] Document has become active, but session has expired');
@@ -15100,7 +15361,7 @@ class ReplayContainer {
15100
15361
  * Only flush if `this.recordingMode === 'session'`
15101
15362
  */
15102
15363
  _conditionalFlush() {
15103
- if (this.recordingMode === 'error') {
15364
+ if (this.recordingMode === 'buffer') {
15104
15365
  return;
15105
15366
  }
15106
15367
 
@@ -15195,7 +15456,7 @@ class ReplayContainer {
15195
15456
  // This means we retried 3 times and all of them failed,
15196
15457
  // or we ran into a problem we don't want to retry, like rate limiting.
15197
15458
  // In this case, we want to completely stop the replay - otherwise, we may get inconsistent segments
15198
- this.stop('sendReplay');
15459
+ void this.stop('sendReplay');
15199
15460
 
15200
15461
  const client = getCurrentHub().getClient();
15201
15462
 
@@ -15209,8 +15470,12 @@ class ReplayContainer {
15209
15470
  * Flush recording data to Sentry. Creates a lock so that only a single flush
15210
15471
  * can be active at a time. Do not call this directly.
15211
15472
  */
15212
- __init16() {this._flush = async () => {
15213
- if (!this._isEnabled) {
15473
+ __init16() {this._flush = async ({
15474
+ force = false,
15475
+ }
15476
+
15477
+ = {}) => {
15478
+ if (!this._isEnabled && !force) {
15214
15479
  // This can happen if e.g. the replay was stopped because of exceeding the retry limit
15215
15480
  return;
15216
15481
  }
@@ -15291,23 +15556,6 @@ class ReplayContainer {
15291
15556
  };}
15292
15557
  }
15293
15558
 
15294
- function _getExperimentalOptions(options) {
15295
- const requestHeaders = options._experiments.captureRequestHeaders || [];
15296
- const responseHeaders = options._experiments.captureResponseHeaders || [];
15297
- const captureBodies = options._experiments.captureNetworkBodies || false;
15298
-
15299
- // Add defaults
15300
- const defaultHeaders = ['content-length', 'content-type', 'accept'];
15301
-
15302
- return {
15303
- network: {
15304
- captureBodies,
15305
- requestHeaders: [...defaultHeaders, ...requestHeaders.map(header => header.toLowerCase())],
15306
- responseHeaders: [...defaultHeaders, ...responseHeaders.map(header => header.toLowerCase())],
15307
- },
15308
- };
15309
- }
15310
-
15311
15559
  function getOption(
15312
15560
  selectors,
15313
15561
  defaultSelectors,
@@ -15411,6 +15659,8 @@ function isElectronNodeRenderer() {
15411
15659
  const MEDIA_SELECTORS =
15412
15660
  'img,image,svg,video,object,picture,embed,map,audio,link[rel="icon"],link[rel="apple-touch-icon"]';
15413
15661
 
15662
+ const DEFAULT_NETWORK_HEADERS = ['content-length', 'content-type', 'accept'];
15663
+
15414
15664
  let _initialized = false;
15415
15665
 
15416
15666
  /**
@@ -15451,6 +15701,11 @@ class Replay {
15451
15701
  maskAllInputs = true,
15452
15702
  blockAllMedia = true,
15453
15703
 
15704
+ networkDetailAllowUrls = [],
15705
+ networkCaptureBodies = true,
15706
+ networkRequestHeaders = [],
15707
+ networkResponseHeaders = [],
15708
+
15454
15709
  mask = [],
15455
15710
  unmask = [],
15456
15711
  block = [],
@@ -15509,6 +15764,11 @@ class Replay {
15509
15764
  errorSampleRate,
15510
15765
  useCompression,
15511
15766
  blockAllMedia,
15767
+ networkDetailAllowUrls,
15768
+ networkCaptureBodies,
15769
+ networkRequestHeaders: _getMergedNetworkHeaders(networkRequestHeaders),
15770
+ networkResponseHeaders: _getMergedNetworkHeaders(networkResponseHeaders),
15771
+
15512
15772
  _experiments,
15513
15773
  };
15514
15774
 
@@ -15562,14 +15822,7 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`,
15562
15822
  }
15563
15823
 
15564
15824
  /**
15565
- * We previously used to create a transaction in `setupOnce` and it would
15566
- * potentially create a transaction before some native SDK integrations have run
15567
- * and applied their own global event processor. An example is:
15568
- * https://github.com/getsentry/sentry-javascript/blob/b47ceafbdac7f8b99093ce6023726ad4687edc48/packages/browser/src/integrations/useragent.ts
15569
- *
15570
- * So we call `replay.setup` in next event loop as a workaround to wait for other
15571
- * global event processors to finish. This is no longer needed, but keeping it
15572
- * here to avoid any future issues.
15825
+ * Setup and initialize replay container
15573
15826
  */
15574
15827
  setupOnce() {
15575
15828
  if (!isBrowser()) {
@@ -15578,12 +15831,20 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`,
15578
15831
 
15579
15832
  this._setup();
15580
15833
 
15581
- // XXX: See method comments above
15582
- setTimeout(() => this.start());
15834
+ // Once upon a time, we tried to create a transaction in `setupOnce` and it would
15835
+ // potentially create a transaction before some native SDK integrations have run
15836
+ // and applied their own global event processor. An example is:
15837
+ // https://github.com/getsentry/sentry-javascript/blob/b47ceafbdac7f8b99093ce6023726ad4687edc48/packages/browser/src/integrations/useragent.ts
15838
+ //
15839
+ // So we call `this._initialize()` in next event loop as a workaround to wait for other
15840
+ // global event processors to finish. This is no longer needed, but keeping it
15841
+ // here to avoid any future issues.
15842
+ setTimeout(() => this._initialize());
15583
15843
  }
15584
15844
 
15585
15845
  /**
15586
- * Initializes the plugin.
15846
+ * Start a replay regardless of sampling rate. Calling this will always
15847
+ * create a new session. Will throw an error if replay is already in progress.
15587
15848
  *
15588
15849
  * Creates or loads a session, attaches listeners to varying events (DOM,
15589
15850
  * PerformanceObserver, Recording, Sentry SDK, etc)
@@ -15596,27 +15857,43 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`,
15596
15857
  this._replay.start();
15597
15858
  }
15598
15859
 
15860
+ /**
15861
+ * Start replay buffering. Buffers until `flush()` is called or, if
15862
+ * `replaysOnErrorSampleRate` > 0, until an error occurs.
15863
+ */
15864
+ startBuffering() {
15865
+ if (!this._replay) {
15866
+ return;
15867
+ }
15868
+
15869
+ this._replay.startBuffering();
15870
+ }
15871
+
15599
15872
  /**
15600
15873
  * Currently, this needs to be manually called (e.g. for tests). Sentry SDK
15601
15874
  * does not support a teardown
15602
15875
  */
15603
15876
  stop() {
15604
15877
  if (!this._replay) {
15605
- return;
15878
+ return Promise.resolve();
15606
15879
  }
15607
15880
 
15608
- this._replay.stop();
15881
+ return this._replay.stop();
15609
15882
  }
15610
15883
 
15611
15884
  /**
15612
- * Immediately send all pending events.
15885
+ * If not in "session" recording mode, flush event buffer which will create a new replay.
15886
+ * Unless `continueRecording` is false, the replay will continue to record and
15887
+ * behave as a "session"-based replay.
15888
+ *
15889
+ * Otherwise, queue up a flush.
15613
15890
  */
15614
- flush() {
15891
+ flush(options) {
15615
15892
  if (!this._replay || !this._replay.isEnabled()) {
15616
- return;
15893
+ return Promise.resolve();
15617
15894
  }
15618
15895
 
15619
- return this._replay.flushImmediate();
15896
+ return this._replay.sendBufferedReplayOrFlush(options);
15620
15897
  }
15621
15898
 
15622
15899
  /**
@@ -15629,6 +15906,16 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`,
15629
15906
 
15630
15907
  return this._replay.getSessionId();
15631
15908
  }
15909
+ /**
15910
+ * Initializes replay.
15911
+ */
15912
+ _initialize() {
15913
+ if (!this._replay) {
15914
+ return;
15915
+ }
15916
+
15917
+ this._replay.initializeSampling();
15918
+ }
15632
15919
 
15633
15920
  /** Setup the integration. */
15634
15921
  _setup() {
@@ -15678,6 +15965,10 @@ function loadReplayOptionsFromClient(initialOptions) {
15678
15965
  return finalOptions;
15679
15966
  }
15680
15967
 
15968
+ function _getMergedNetworkHeaders(headers) {
15969
+ return [...DEFAULT_NETWORK_HEADERS, ...headers.map(header => header.toLowerCase())];
15970
+ }
15971
+
15681
15972
  /**
15682
15973
  * Polyfill for the optional chain operator, `?.`, given previous conversion of the expression into an array of values,
15683
15974
  * descriptors, and functions.
@@ -16115,6 +16406,9 @@ class Postgres {
16115
16406
  const span = _optionalChain([parentSpan, 'optionalAccess', _6 => _6.startChild, 'call', _7 => _7({
16116
16407
  description: typeof config === 'string' ? config : (config ).text,
16117
16408
  op: 'db',
16409
+ data: {
16410
+ 'db.system': 'postgresql',
16411
+ },
16118
16412
  })]);
16119
16413
 
16120
16414
  if (typeof callback === 'function') {
@@ -16191,6 +16485,9 @@ class Mysql {constructor() { Mysql.prototype.__init.call(this); }
16191
16485
  const span = _optionalChain([parentSpan, 'optionalAccess', _4 => _4.startChild, 'call', _5 => _5({
16192
16486
  description: typeof options === 'string' ? options : (options ).sql,
16193
16487
  op: 'db',
16488
+ data: {
16489
+ 'db.system': 'mysql',
16490
+ },
16194
16491
  })]);
16195
16492
 
16196
16493
  if (typeof callback === 'function') {
@@ -16412,6 +16709,7 @@ class Mongo {
16412
16709
  collectionName: collection.collectionName,
16413
16710
  dbName: collection.dbName,
16414
16711
  namespace: collection.namespace,
16712
+ 'db.system': 'mongodb',
16415
16713
  };
16416
16714
  const spanContext = {
16417
16715
  op: 'db',
@@ -16500,7 +16798,10 @@ class Prisma {
16500
16798
  this._client.$use((params, next) => {
16501
16799
  const action = params.action;
16502
16800
  const model = params.model;
16503
- return trace({ name: model ? `${model} ${action}` : action, op: 'db.sql.prisma' }, () => next(params));
16801
+ return trace(
16802
+ { name: model ? `${model} ${action}` : action, op: 'db.sql.prisma', data: { 'db.system': 'prisma' } },
16803
+ () => next(params),
16804
+ );
16504
16805
  });
16505
16806
  }
16506
16807
  } Prisma.__initStatic();
@@ -29664,7 +29965,7 @@ const configGenerator = () => {
29664
29965
  let release;
29665
29966
  try {
29666
29967
  environment !== null && environment !== void 0 ? environment : (environment = "staging");
29667
- release !== null && release !== void 0 ? release : (release = "1.1.21");
29968
+ release !== null && release !== void 0 ? release : (release = "1.1.22");
29668
29969
  }
29669
29970
  catch (_a) {
29670
29971
  console.error('sentry configGenerator error');