redux.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import { getGlobalScope, getCurrentScope, getClient } from '@sentry/core';
  2. import { addNonEnumerableProperty } from '@sentry/utils';
  3. /* eslint-disable @typescript-eslint/no-explicit-any */
  4. const ACTION_BREADCRUMB_CATEGORY = 'redux.action';
  5. const ACTION_BREADCRUMB_TYPE = 'info';
  6. const defaultOptions = {
  7. attachReduxState: true,
  8. actionTransformer: action => action,
  9. stateTransformer: state => state || null,
  10. };
  11. /**
  12. * Creates an enhancer that would be passed to Redux's createStore to log actions and the latest state to Sentry.
  13. *
  14. * @param enhancerOptions Options to pass to the enhancer
  15. */
  16. function createReduxEnhancer(enhancerOptions) {
  17. // Note: We return an any type as to not have type conflicts.
  18. const options = {
  19. ...defaultOptions,
  20. ...enhancerOptions,
  21. };
  22. return (next) =>
  23. (reducer, initialState) => {
  24. options.attachReduxState &&
  25. getGlobalScope().addEventProcessor((event, hint) => {
  26. try {
  27. // @ts-expect-error try catch to reduce bundle size
  28. if (event.type === undefined && event.contexts.state.state.type === 'redux') {
  29. hint.attachments = [
  30. ...(hint.attachments || []),
  31. // @ts-expect-error try catch to reduce bundle size
  32. { filename: 'redux_state.json', data: JSON.stringify(event.contexts.state.state.value) },
  33. ];
  34. }
  35. } catch (_) {
  36. // empty
  37. }
  38. return event;
  39. });
  40. const sentryReducer = (state, action) => {
  41. const newState = reducer(state, action);
  42. const scope = getCurrentScope();
  43. /* Action breadcrumbs */
  44. const transformedAction = options.actionTransformer(action);
  45. if (typeof transformedAction !== 'undefined' && transformedAction !== null) {
  46. scope.addBreadcrumb({
  47. category: ACTION_BREADCRUMB_CATEGORY,
  48. data: transformedAction,
  49. type: ACTION_BREADCRUMB_TYPE,
  50. });
  51. }
  52. /* Set latest state to scope */
  53. const transformedState = options.stateTransformer(newState);
  54. if (typeof transformedState !== 'undefined' && transformedState !== null) {
  55. const client = getClient();
  56. const options = client && client.getOptions();
  57. const normalizationDepth = (options && options.normalizeDepth) || 3; // default state normalization depth to 3
  58. // Set the normalization depth of the redux state to the configured `normalizeDepth` option or a sane number as a fallback
  59. const newStateContext = { state: { type: 'redux', value: transformedState } };
  60. addNonEnumerableProperty(
  61. newStateContext,
  62. '__sentry_override_normalization_depth__',
  63. 3 + // 3 layers for `state.value.transformedState`
  64. normalizationDepth, // rest for the actual state
  65. );
  66. scope.setContext('state', newStateContext);
  67. } else {
  68. scope.setContext('state', null);
  69. }
  70. /* Allow user to configure scope with latest state */
  71. const { configureScopeWithState } = options;
  72. if (typeof configureScopeWithState === 'function') {
  73. configureScopeWithState(scope, newState);
  74. }
  75. return newState;
  76. };
  77. return next(sentryReducer, initialState);
  78. };
  79. }
  80. export { createReduxEnhancer };
  81. //# sourceMappingURL=redux.js.map