Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ref: Use inline types to avoid redux dependency #2768

Merged
merged 3 commits into from Jul 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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