* Creates an enhancer that would be passed to Redux's createStore to log actions and the latest state to Sentry. * * @param enhancerOptions Options to pass to the enhancer
(enhancerOptions?: Partial<SentryEnhancerOptions>)
| 94 | * @param enhancerOptions Options to pass to the enhancer |
| 95 | */ |
| 96 | function createReduxEnhancer(enhancerOptions?: Partial<SentryEnhancerOptions>): any { |
| 97 | // Note: We return an any type as to not have type conflicts. |
| 98 | const options = { |
| 99 | ...defaultOptions, |
| 100 | ...enhancerOptions, |
| 101 | }; |
| 102 | |
| 103 | return (next: StoreEnhancerStoreCreator): StoreEnhancerStoreCreator => |
| 104 | <S = any, A extends Action = AnyAction>(reducer: Reducer<S, A>, initialState?: PreloadedState<S>) => { |
| 105 | options.attachReduxState && |
| 106 | getGlobalScope().addEventProcessor((event, hint) => { |
| 107 | try { |
| 108 | // @ts-expect-error try catch to reduce bundle size |
| 109 | if (event.type === undefined && event.contexts.state.state.type === 'redux') { |
| 110 | hint.attachments = [ |
| 111 | ...(hint.attachments || []), |
| 112 | // @ts-expect-error try catch to reduce bundle size |
| 113 | { filename: 'redux_state.json', data: JSON.stringify(event.contexts.state.state.value) }, |
| 114 | ]; |
| 115 | } |
| 116 | } catch { |
| 117 | // empty |
| 118 | } |
| 119 | return event; |
| 120 | }); |
| 121 | |
| 122 | function sentryWrapReducer(reducer: Reducer<S, A>): Reducer<S, A> { |
| 123 | return (state, action): S => { |
| 124 | const newState = reducer(state, action); |
| 125 | |
| 126 | const scope = getCurrentScope(); |
| 127 | |
| 128 | /* Action breadcrumbs */ |
| 129 | const transformedAction = options.actionTransformer(action); |
| 130 | if (typeof transformedAction !== 'undefined' && transformedAction !== null) { |
| 131 | addBreadcrumb({ |
| 132 | category: ACTION_BREADCRUMB_CATEGORY, |
| 133 | data: transformedAction, |
| 134 | type: ACTION_BREADCRUMB_TYPE, |
| 135 | }); |
| 136 | } |
| 137 | |
| 138 | /* Set latest state to scope */ |
| 139 | const transformedState = options.stateTransformer(newState); |
| 140 | if (typeof transformedState !== 'undefined' && transformedState !== null) { |
| 141 | const client = getClient(); |
| 142 | const options = client?.getOptions(); |
| 143 | const normalizationDepth = options?.normalizeDepth || 3; // default state normalization depth to 3 |
| 144 | |
| 145 | // Set the normalization depth of the redux state to the configured `normalizeDepth` option or a sane number as a fallback |
| 146 | const newStateContext = { state: { type: 'redux', value: transformedState } }; |
| 147 | setNormalizationDepthOverrideHint( |
| 148 | newStateContext, |
| 149 | 3 + // 3 layers for `state.value.transformedState` |
| 150 | normalizationDepth, // rest for the actual state |
| 151 | ); |
| 152 | |
| 153 | scope.setContext('state', newStateContext); |
no test coverage detected