Skip to content

Instantly share code, notes, and snippets.

@Thanaen
Last active July 26, 2022 20:18
Show Gist options
  • Save Thanaen/abfcc669f28765651f8e0ff59ea62527 to your computer and use it in GitHub Desktop.
Save Thanaen/abfcc669f28765651f8e0ff59ea62527 to your computer and use it in GitHub Desktop.
Zustand Sentry Middleware
import type { StateCreator, StoreMutatorIdentifier } from 'zustand';
import { configureScope } from '@sentry/browser';
type PopArgument<T extends (...a: never[]) => unknown> = T extends (
...a: [...infer A, infer _]
) => infer R
? (...a: A) => R
: never;
interface SentryMiddlewareConfig<T> {
stateTransformer?: (state: T) => object;
}
type SentryMiddleware = <
T extends object,
Mps extends [StoreMutatorIdentifier, unknown][] = [],
Mcs extends [StoreMutatorIdentifier, unknown][] = [],
>(
f: StateCreator<T, Mps, Mcs>,
config?: SentryMiddlewareConfig<T>,
) => StateCreator<T, Mps, Mcs>;
type SentryMiddlewareImpl = <T extends object>(
f: PopArgument<StateCreator<T, [], []>>,
config?: SentryMiddlewareConfig<T>,
) => PopArgument<StateCreator<T, [], []>>;
const sentryMiddleware: SentryMiddlewareImpl = (config, sentryConfig) => (set, get, api) =>
config(
(...args) => {
set(...args);
const newState = get();
configureScope((scope) => {
if (newState) {
const transformedState = sentryConfig?.stateTransformer
? sentryConfig.stateTransformer(newState)
: newState;
scope.setContext('state', { type: 'zustand', value: transformedState as Record<string, unknown> });
} else {
scope.setContext('state', null);
}
});
},
get,
api,
);
const typedSentryMiddleware = sentryMiddleware as unknown as SentryMiddleware;
export default typedSentryMiddleware;
// A method to edit the state before sending to Sentry (remove sensitive data, functions, etc)
const stateTransformer = (state: BearState) => {
const cleanedState = {
...state,
bears: state.bears > 0 ? "There are some bears, but I won't tell you how many!" : "No bears here"
};
// In zustand, actions are accessible from the store's state
// We might want to remove them before sending the state to Sentry
return Object.fromEntries(
Object.entries(cleanedState).filter(
([key]) => typeof cleanedState[key as keyof typeof cleanedState] !== 'function',
),
);
};
export default stateTransformer;
// Example of how to use the middleware
import create from "zustand";
import sentryMiddleware from './sentryMiddleware';
import stateTransformer from './stateTransformer';
interface BearState {
bears: number;
increase: (by: number) => void;
}
const useStore = create<BearState>()(
typedSentryMiddleware((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
}), { stateTransformer: stateTransformer })
);
export default useStore;
@Thanaen
Copy link
Author

Thanaen commented Jul 26, 2022

Improved following feedback on this issue getsentry/sentry-javascript#5430

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment