Skip to content

Commit

Permalink
ref(react): Use inline types to avoid redux dependency (#2768)
Browse files Browse the repository at this point in the history
  • Loading branch information
jennmueng committed Jul 24, 2020
1 parent 7d7f0db commit 2fc1568
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -6,6 +6,7 @@
- [react] feat: Add instrumentation for React Router v3 (#2759)
- [apm/tracing] fix: Make sure fetch requests are being timed correctly (#2772)
- [apm/tracing] fix: Make sure pageload transactions start timestamps are correctly generated (#2773)
- [react] ref: Use inline types to avoid redux dependency. (#2768)

## 5.20.0

Expand Down
59 changes: 55 additions & 4 deletions packages/react/src/redux.ts
@@ -1,15 +1,60 @@
// @flow
import { configureScope } from '@sentry/minimal';
import { Scope } from '@sentry/types';
import { Action, AnyAction, PreloadedState, Reducer, StoreEnhancer, StoreEnhancerStoreCreator } from 'redux';

interface Action<T = any> {
type: T;
}

interface AnyAction extends Action {
[extraProps: string]: any;
}

type Reducer<S = any, A extends Action = AnyAction> = (state: S | undefined, action: A) => S;

type Dispatch<A extends Action = AnyAction> = <T extends A>(action: T, ...extraArgs: any[]) => T;

type ExtendState<State, Extension> = [Extension] extends [never] ? State : State & Extension;

type Unsubscribe = () => void;

interface Store<S = any, A extends Action = AnyAction, StateExt = never, Ext = {}> {
dispatch: Dispatch<A>;
getState(): S;
subscribe(listener: () => void): Unsubscribe;
replaceReducer<NewState, NewActions extends Action>(
nextReducer: Reducer<NewState, NewActions>,
): Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext;
}

declare const $CombinedState: unique symbol;

type CombinedState<S> = { readonly [$CombinedState]?: undefined } & S;

type PreloadedState<S> = Required<S> extends {
[$CombinedState]: undefined;
}
? S extends CombinedState<infer S1>
? { [K in keyof S1]?: S1[K] extends object ? PreloadedState<S1[K]> : S1[K] }
: never
: { [K in keyof S]: S[K] extends string | number | boolean | symbol ? S[K] : PreloadedState<S[K]> };

type StoreEnhancer<Ext = {}, StateExt = never> = (
next: StoreEnhancerStoreCreator<Ext, StateExt>,
) => StoreEnhancerStoreCreator<Ext, StateExt>;

type StoreEnhancerStoreCreator<Ext = {}, StateExt = never> = <S = any, A extends Action = AnyAction>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S>,
) => Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext;

export interface SentryEnhancerOptions {
/**
* Transforms the state before attaching it to an event.
* Use this to remove any private data before sending it to Sentry.
* Return null to not attach the state.
*/
stateTransformer(state: any | undefined): any | null;
stateTransformer<S>(state: S | undefined): (S & any) | null;
/**
* Transforms the action before sending it as a breadcrumb.
* Use this to remove any private data before sending it to Sentry.
Expand All @@ -19,7 +64,7 @@ export interface SentryEnhancerOptions {
/**
* Called on every state update, configure the Sentry Scope with the redux state.
*/
configureScopeWithState?(scope: Scope, state: any): void;
configureScopeWithState?<S>(scope: Scope, state: S): void;
}

const ACTION_BREADCRUMB_CATEGORY = 'redux.action';
Expand All @@ -29,9 +74,14 @@ const STATE_CONTEXT_KEY = 'redux.state';
const defaultOptions: SentryEnhancerOptions = {
actionTransformer: action => action,
// tslint:disable-next-line: no-unsafe-any
stateTransformer: state => state,
stateTransformer: state => state || null,
};

/**
* 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
*/
function createReduxEnhancer(enhancerOptions?: Partial<SentryEnhancerOptions>): StoreEnhancer {
const options = {
...defaultOptions,
Expand Down Expand Up @@ -59,6 +109,7 @@ function createReduxEnhancer(enhancerOptions?: Partial<SentryEnhancerOptions>):

/* Set latest state to scope */
const transformedState = options.stateTransformer(newState);
// tslint:disable-next-line: strict-type-predicates
if (typeof transformedState !== 'undefined' && transformedState !== null) {
// tslint:disable-next-line: no-unsafe-any
scope.setContext(STATE_CONTEXT_KEY, transformedState);
Expand Down

0 comments on commit 2fc1568

Please sign in to comment.