useReplayInit.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. import {useEffect} from 'react';
  2. import type {BrowserClientReplayOptions} from '@sentry/core';
  3. import type {replayIntegration} from '@sentry/react';
  4. import {getClient} from '@sentry/react';
  5. import {isStaticString} from 'sentry/locale';
  6. import type {Organization} from 'sentry/types/organization';
  7. import {useUser} from 'sentry/utils/useUser';
  8. interface Props {
  9. organization: Organization | null;
  10. }
  11. // Single replayRef across the whole app, even if this hook is called multiple times
  12. let replayRef: ReturnType<typeof replayIntegration> | null;
  13. /**
  14. * Load the Sentry Replay integration based on the feature flag.
  15. *
  16. * Can't use `useOrganization` because it throws on
  17. * `/settings/account/api/auth-token/` because organization is not *immediately*
  18. * set in context
  19. */
  20. export default function useReplayInit({organization}: Props) {
  21. const user = useUser();
  22. useEffect(() => {
  23. async function init(sessionSampleRate: number, errorSampleRate: number) {
  24. const {replayIntegration} = await import('@sentry/react');
  25. if (!replayRef) {
  26. const client = getClient();
  27. if (!client) {
  28. return;
  29. }
  30. const options = client.getOptions() as BrowserClientReplayOptions;
  31. options.replaysSessionSampleRate = sessionSampleRate;
  32. options.replaysOnErrorSampleRate = errorSampleRate;
  33. replayRef = replayIntegration({
  34. maskAllText: true,
  35. _experiments: {
  36. captureExceptions: true,
  37. traceInternals: true,
  38. },
  39. networkDetailAllowUrls: ['/api/0/'],
  40. networkDetailDenyUrls: [
  41. '/api/0/customers/',
  42. '/api/0/invoices/',
  43. /\/api\/0\/projects\/[^/]*\/[^/]*\/replays\/[^/]*\/recording-segments\//,
  44. ],
  45. networkRequestHeaders: ['sentry-trace', 'origin'],
  46. networkResponseHeaders: [
  47. 'access-control-allow-headers',
  48. 'access-control-allow-methods',
  49. 'access-control-allow-origin',
  50. 'access-control-expose-headers',
  51. 'allow',
  52. 'link',
  53. 'x-sentry-rate-limit-concurrentlimit',
  54. 'x-sentry-rate-limit-concurrentremaining',
  55. 'x-sentry-rate-limit-limit',
  56. 'x-sentry-rate-limit-remaining',
  57. 'x-sentry-rate-limit-reset',
  58. 'x-served-by',
  59. ],
  60. maskFn: (text: string) =>
  61. isStaticString(text) ? text : text.replace(/[\S]/g, '*'),
  62. slowClickIgnoreSelectors: [
  63. '[aria-label*="download" i]',
  64. '[aria-label*="export" i]',
  65. ],
  66. });
  67. if (organization?.features.includes('session-replay-enable-canvas')) {
  68. const {replayCanvasIntegration} = await import('@sentry/react');
  69. client.addIntegration!(replayCanvasIntegration());
  70. }
  71. client.addIntegration!(replayRef);
  72. }
  73. }
  74. if (process.env.NODE_ENV !== 'production' || process.env.IS_ACCEPTANCE_TEST) {
  75. return;
  76. }
  77. if (!organization || !user) {
  78. return;
  79. }
  80. const sessionSampleRate = user.isStaff ? 1.0 : 0.05;
  81. const errorSampleRate = 0.5;
  82. init(sessionSampleRate, errorSampleRate);
  83. // NOTE: if this component is unmounted (e.g. when org is switched), we will continue to record!
  84. // This can be changed by calling `stop/start()` on unmount/mount respectively.
  85. }, [organization, user]);
  86. }