@sentry/react 10.24.0 → 10.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/error.js.map +1 -1
- package/build/cjs/errorboundary.js.map +1 -1
- package/build/cjs/profiler.js.map +1 -1
- package/build/cjs/reactrouter-compat-utils/instrumentation.js +133 -68
- package/build/cjs/reactrouter-compat-utils/instrumentation.js.map +1 -1
- package/build/cjs/reactrouter-compat-utils/lazy-routes.js.map +1 -1
- package/build/cjs/reactrouter-compat-utils/utils.js +51 -42
- package/build/cjs/reactrouter-compat-utils/utils.js.map +1 -1
- package/build/cjs/reactrouter.js.map +1 -1
- package/build/cjs/reactrouterv3.js.map +1 -1
- package/build/cjs/redux.js.map +1 -1
- package/build/cjs/tanstackrouter.js.map +1 -1
- package/build/esm/error.js.map +1 -1
- package/build/esm/errorboundary.js.map +1 -1
- package/build/esm/package.json +1 -1
- package/build/esm/profiler.js.map +1 -1
- package/build/esm/reactrouter-compat-utils/instrumentation.js +133 -68
- package/build/esm/reactrouter-compat-utils/instrumentation.js.map +1 -1
- package/build/esm/reactrouter-compat-utils/lazy-routes.js.map +1 -1
- package/build/esm/reactrouter-compat-utils/utils.js +51 -42
- package/build/esm/reactrouter-compat-utils/utils.js.map +1 -1
- package/build/esm/reactrouter.js.map +1 -1
- package/build/esm/reactrouterv3.js.map +1 -1
- package/build/esm/redux.js.map +1 -1
- package/build/esm/tanstackrouter.js.map +1 -1
- package/build/types/reactrouter-compat-utils/instrumentation.d.ts +0 -4
- package/build/types/reactrouter-compat-utils/instrumentation.d.ts.map +1 -1
- package/build/types/reactrouter-compat-utils/utils.d.ts.map +1 -1
- package/build/types-ts3.8/reactrouter-compat-utils/instrumentation.d.ts +0 -4
- package/package.json +3 -3
package/build/cjs/redux.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redux.js","sources":["../../src/redux.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type { Scope } from '@sentry/core';\nimport { addBreadcrumb, addNonEnumerableProperty, getClient, getCurrentScope, getGlobalScope } from '@sentry/core';\n\ninterface Action<T = any> {\n type: T;\n}\n\ninterface AnyAction extends Action {\n [extraProps: string]: any;\n}\n\ntype Reducer<S = any, A extends Action = AnyAction> = (state: S | undefined, action: A) => S;\n\ntype Dispatch<A extends Action = AnyAction> = <T extends A>(action: T, ...extraArgs: any[]) => T;\n\ntype ExtendState<State, Extension> = [Extension] extends [never] ? State : State & Extension;\n\ntype Unsubscribe = () => void;\n\ninterface Store<S = any, A extends Action = AnyAction, StateExt = never, Ext = Record<string, unknown>> {\n dispatch: Dispatch<A>;\n getState(): S;\n subscribe(listener: () => void): Unsubscribe;\n replaceReducer<NewState, NewActions extends Action>(\n nextReducer: Reducer<NewState, NewActions>,\n ): Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext;\n}\n\ndeclare const $CombinedState: unique symbol;\n\ntype CombinedState<S> = { readonly [$CombinedState]?: undefined } & S;\n\ntype PreloadedState<S> =\n Required<S> extends {\n [$CombinedState]: undefined;\n }\n ? S extends CombinedState<infer S1>\n ? { [K in keyof S1]?: S1[K] extends Record<string, unknown> ? PreloadedState<S1[K]> : S1[K] }\n : never\n : { [K in keyof S]: S[K] extends string | number | boolean | symbol ? S[K] : PreloadedState<S[K]> };\n\ntype StoreEnhancerStoreCreator<Ext = Record<string, unknown>, StateExt = never> = <\n S = any,\n A extends Action = AnyAction,\n>(\n reducer: Reducer<S, A>,\n preloadedState?: PreloadedState<S>,\n) => Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext;\n\nexport interface SentryEnhancerOptions<S = any> {\n /**\n * Redux state in attachments or not.\n * @default true\n */\n attachReduxState?: boolean;\n\n /**\n * Transforms the state before attaching it to an event.\n * Use this to remove any private data before sending it to Sentry.\n * Return null to not attach the state.\n */\n stateTransformer(state: S | undefined): (S & any) | null;\n /**\n * Transforms the action before sending it as a breadcrumb.\n * Use this to remove any private data before sending it to Sentry.\n * Return null to not send the breadcrumb.\n */\n actionTransformer(action: AnyAction): AnyAction | null;\n /**\n * Called on every state update, configure the Sentry Scope with the redux state.\n */\n configureScopeWithState?(scope: Scope, state: S): void;\n}\n\nconst ACTION_BREADCRUMB_CATEGORY = 'redux.action';\nconst ACTION_BREADCRUMB_TYPE = 'info';\n\nconst defaultOptions: SentryEnhancerOptions = {\n attachReduxState: true,\n actionTransformer: action => action,\n stateTransformer: state => state || null,\n};\n\n/**\n * Creates an enhancer that would be passed to Redux's createStore to log actions and the latest state to Sentry.\n *\n * @param enhancerOptions Options to pass to the enhancer\n */\nfunction createReduxEnhancer(enhancerOptions?: Partial<SentryEnhancerOptions>): any {\n // Note: We return an any type as to not have type conflicts.\n const options = {\n ...defaultOptions,\n ...enhancerOptions,\n };\n\n return (next: StoreEnhancerStoreCreator): StoreEnhancerStoreCreator =>\n <S = any, A extends Action = AnyAction>(reducer: Reducer<S, A>, initialState?: PreloadedState<S>) => {\n options.attachReduxState &&\n getGlobalScope().addEventProcessor((event, hint) => {\n try {\n // @ts-expect-error try catch to reduce bundle size\n if (event.type === undefined && event.contexts.state.state.type === 'redux') {\n hint.attachments = [\n ...(hint.attachments || []),\n // @ts-expect-error try catch to reduce bundle size\n { filename: 'redux_state.json', data: JSON.stringify(event.contexts.state.state.value) },\n ];\n }\n } catch {\n // empty\n }\n return event;\n });\n\n function sentryWrapReducer(reducer: Reducer<S, A>): Reducer<S, A> {\n return (state, action): S => {\n const newState = reducer(state, action);\n\n const scope = getCurrentScope();\n\n /* Action breadcrumbs */\n const transformedAction = options.actionTransformer(action);\n if (typeof transformedAction !== 'undefined' && transformedAction !== null) {\n addBreadcrumb({\n category: ACTION_BREADCRUMB_CATEGORY,\n data: transformedAction,\n type: ACTION_BREADCRUMB_TYPE,\n });\n }\n\n /* Set latest state to scope */\n const transformedState = options.stateTransformer(newState);\n if (typeof transformedState !== 'undefined' && transformedState !== null) {\n const client = getClient();\n const options = client?.getOptions();\n const normalizationDepth = options?.normalizeDepth || 3; // default state normalization depth to 3\n\n // Set the normalization depth of the redux state to the configured `normalizeDepth` option or a sane number as a fallback\n const newStateContext = { state: { type: 'redux', value: transformedState } };\n addNonEnumerableProperty(\n newStateContext,\n '__sentry_override_normalization_depth__',\n 3 + // 3 layers for `state.value.transformedState`\n normalizationDepth, // rest for the actual state\n );\n\n scope.setContext('state', newStateContext);\n } else {\n scope.setContext('state', null);\n }\n\n /* Allow user to configure scope with latest state */\n const { configureScopeWithState } = options;\n if (typeof configureScopeWithState === 'function') {\n configureScopeWithState(scope, newState);\n }\n\n return newState;\n };\n }\n\n const store = next(sentryWrapReducer(reducer), initialState);\n\n // eslint-disable-next-line @typescript-eslint/unbound-method\n store.replaceReducer = new Proxy(store.replaceReducer, {\n apply: function (target, thisArg, args) {\n target.apply(thisArg, [sentryWrapReducer(args[0])]);\n },\n });\n\n return store;\n };\n}\n\nexport { createReduxEnhancer };\n"],"names":["getGlobalScope","getCurrentScope","addBreadcrumb","getClient","addNonEnumerableProperty"],"mappings":";;;;AA2EA,MAAM,0BAAA,GAA6B,cAAc;AACjD,MAAM,sBAAA,GAAyB,MAAM;;AAErC,MAAM,cAAc,GAA0B;AAC9C,EAAE,gBAAgB,EAAE,IAAI;AACxB,EAAE,iBAAiB,EAAE,MAAA,IAAU,MAAM;AACrC,EAAE,gBAAgB,EAAE,KAAA,IAAS,KAAA,IAAS,IAAI;AAC1C,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA,SAAS,mBAAmB,CAAC,eAAe,EAAwC;AACpF;AACA,EAAE,MAAM,UAAU;AAClB,IAAI,GAAG,cAAc;AACrB,IAAI,GAAG,eAAe;AACtB,GAAG;;AAEH,EAAE,OAAO,CAAC,IAAI;AACd,IAAI,CAAwC,OAAO,EAAiB,YAAY,KAAyB;AACzG,MAAM,OAAO,CAAC,gBAAA;AACd,QAAQA,mBAAc,EAAE,CAAC,iBAAiB,CAAC,CAAC,KAAK,EAAE,IAAI,KAAK;AAC5D,UAAU,IAAI;AACd;AACA,YAAY,IAAI,KAAK,CAAC,IAAA,KAAS,aAAa,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,IAAA,KAAS,OAAO,EAAE;AACzF,cAAc,IAAI,CAAC,WAAA,GAAc;AACjC,gBAAgB,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;AAC3C;AACA,gBAAgB,EAAE,QAAQ,EAAE,kBAAkB,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAA,EAAG;AACxG,eAAe;AACf
|
|
1
|
+
{"version":3,"file":"redux.js","sources":["../../src/redux.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type { Scope } from '@sentry/core';\nimport { addBreadcrumb, addNonEnumerableProperty, getClient, getCurrentScope, getGlobalScope } from '@sentry/core';\n\ninterface Action<T = any> {\n type: T;\n}\n\ninterface AnyAction extends Action {\n [extraProps: string]: any;\n}\n\ntype Reducer<S = any, A extends Action = AnyAction> = (state: S | undefined, action: A) => S;\n\ntype Dispatch<A extends Action = AnyAction> = <T extends A>(action: T, ...extraArgs: any[]) => T;\n\ntype ExtendState<State, Extension> = [Extension] extends [never] ? State : State & Extension;\n\ntype Unsubscribe = () => void;\n\ninterface Store<S = any, A extends Action = AnyAction, StateExt = never, Ext = Record<string, unknown>> {\n dispatch: Dispatch<A>;\n getState(): S;\n subscribe(listener: () => void): Unsubscribe;\n replaceReducer<NewState, NewActions extends Action>(\n nextReducer: Reducer<NewState, NewActions>,\n ): Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext;\n}\n\ndeclare const $CombinedState: unique symbol;\n\ntype CombinedState<S> = { readonly [$CombinedState]?: undefined } & S;\n\ntype PreloadedState<S> =\n Required<S> extends {\n [$CombinedState]: undefined;\n }\n ? S extends CombinedState<infer S1>\n ? { [K in keyof S1]?: S1[K] extends Record<string, unknown> ? PreloadedState<S1[K]> : S1[K] }\n : never\n : { [K in keyof S]: S[K] extends string | number | boolean | symbol ? S[K] : PreloadedState<S[K]> };\n\ntype StoreEnhancerStoreCreator<Ext = Record<string, unknown>, StateExt = never> = <\n S = any,\n A extends Action = AnyAction,\n>(\n reducer: Reducer<S, A>,\n preloadedState?: PreloadedState<S>,\n) => Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext;\n\nexport interface SentryEnhancerOptions<S = any> {\n /**\n * Redux state in attachments or not.\n * @default true\n */\n attachReduxState?: boolean;\n\n /**\n * Transforms the state before attaching it to an event.\n * Use this to remove any private data before sending it to Sentry.\n * Return null to not attach the state.\n */\n stateTransformer(state: S | undefined): (S & any) | null;\n /**\n * Transforms the action before sending it as a breadcrumb.\n * Use this to remove any private data before sending it to Sentry.\n * Return null to not send the breadcrumb.\n */\n actionTransformer(action: AnyAction): AnyAction | null;\n /**\n * Called on every state update, configure the Sentry Scope with the redux state.\n */\n configureScopeWithState?(scope: Scope, state: S): void;\n}\n\nconst ACTION_BREADCRUMB_CATEGORY = 'redux.action';\nconst ACTION_BREADCRUMB_TYPE = 'info';\n\nconst defaultOptions: SentryEnhancerOptions = {\n attachReduxState: true,\n actionTransformer: action => action,\n stateTransformer: state => state || null,\n};\n\n/**\n * Creates an enhancer that would be passed to Redux's createStore to log actions and the latest state to Sentry.\n *\n * @param enhancerOptions Options to pass to the enhancer\n */\nfunction createReduxEnhancer(enhancerOptions?: Partial<SentryEnhancerOptions>): any {\n // Note: We return an any type as to not have type conflicts.\n const options = {\n ...defaultOptions,\n ...enhancerOptions,\n };\n\n return (next: StoreEnhancerStoreCreator): StoreEnhancerStoreCreator =>\n <S = any, A extends Action = AnyAction>(reducer: Reducer<S, A>, initialState?: PreloadedState<S>) => {\n options.attachReduxState &&\n getGlobalScope().addEventProcessor((event, hint) => {\n try {\n // @ts-expect-error try catch to reduce bundle size\n if (event.type === undefined && event.contexts.state.state.type === 'redux') {\n hint.attachments = [\n ...(hint.attachments || []),\n // @ts-expect-error try catch to reduce bundle size\n { filename: 'redux_state.json', data: JSON.stringify(event.contexts.state.state.value) },\n ];\n }\n } catch {\n // empty\n }\n return event;\n });\n\n function sentryWrapReducer(reducer: Reducer<S, A>): Reducer<S, A> {\n return (state, action): S => {\n const newState = reducer(state, action);\n\n const scope = getCurrentScope();\n\n /* Action breadcrumbs */\n const transformedAction = options.actionTransformer(action);\n if (typeof transformedAction !== 'undefined' && transformedAction !== null) {\n addBreadcrumb({\n category: ACTION_BREADCRUMB_CATEGORY,\n data: transformedAction,\n type: ACTION_BREADCRUMB_TYPE,\n });\n }\n\n /* Set latest state to scope */\n const transformedState = options.stateTransformer(newState);\n if (typeof transformedState !== 'undefined' && transformedState !== null) {\n const client = getClient();\n const options = client?.getOptions();\n const normalizationDepth = options?.normalizeDepth || 3; // default state normalization depth to 3\n\n // Set the normalization depth of the redux state to the configured `normalizeDepth` option or a sane number as a fallback\n const newStateContext = { state: { type: 'redux', value: transformedState } };\n addNonEnumerableProperty(\n newStateContext,\n '__sentry_override_normalization_depth__',\n 3 + // 3 layers for `state.value.transformedState`\n normalizationDepth, // rest for the actual state\n );\n\n scope.setContext('state', newStateContext);\n } else {\n scope.setContext('state', null);\n }\n\n /* Allow user to configure scope with latest state */\n const { configureScopeWithState } = options;\n if (typeof configureScopeWithState === 'function') {\n configureScopeWithState(scope, newState);\n }\n\n return newState;\n };\n }\n\n const store = next(sentryWrapReducer(reducer), initialState);\n\n // eslint-disable-next-line @typescript-eslint/unbound-method\n store.replaceReducer = new Proxy(store.replaceReducer, {\n apply: function (target, thisArg, args) {\n target.apply(thisArg, [sentryWrapReducer(args[0])]);\n },\n });\n\n return store;\n };\n}\n\nexport { createReduxEnhancer };\n"],"names":["getGlobalScope","getCurrentScope","addBreadcrumb","getClient","addNonEnumerableProperty"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tanstackrouter.js","sources":["../../src/tanstackrouter.ts"],"sourcesContent":["import {\n browserTracingIntegration as originalBrowserTracingIntegration,\n startBrowserTracingNavigationSpan,\n startBrowserTracingPageLoadSpan,\n WINDOW,\n} from '@sentry/browser';\nimport type { Integration } from '@sentry/core';\nimport {\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,\n} from '@sentry/core';\nimport type { VendoredTanstackRouter, VendoredTanstackRouterRouteMatch } from './vendor/tanstackrouter-types';\n\n/**\n * A custom browser tracing integration for TanStack Router.\n *\n * The minimum compatible version of `@tanstack/react-router` is `1.64.0`.\n *\n * @param router A TanStack Router `Router` instance that should be used for routing instrumentation.\n * @param options Sentry browser tracing configuration.\n */\nexport function tanstackRouterBrowserTracingIntegration(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n router: any, // This is `any` because we don't want any type mismatches if TanStack Router changes their types\n options: Parameters<typeof originalBrowserTracingIntegration>[0] = {},\n): Integration {\n const castRouterInstance: VendoredTanstackRouter = router;\n\n const browserTracingIntegrationInstance = originalBrowserTracingIntegration({\n ...options,\n instrumentNavigation: false,\n instrumentPageLoad: false,\n });\n\n const { instrumentPageLoad = true, instrumentNavigation = true } = options;\n\n return {\n ...browserTracingIntegrationInstance,\n afterAllSetup(client) {\n browserTracingIntegrationInstance.afterAllSetup(client);\n\n const initialWindowLocation = WINDOW.location;\n if (instrumentPageLoad && initialWindowLocation) {\n const matchedRoutes = castRouterInstance.matchRoutes(\n initialWindowLocation.pathname,\n castRouterInstance.options.parseSearch(initialWindowLocation.search),\n { preload: false, throwOnError: false },\n );\n\n const lastMatch = matchedRoutes[matchedRoutes.length - 1];\n\n startBrowserTracingPageLoadSpan(client, {\n name: lastMatch ? lastMatch.routeId : initialWindowLocation.pathname,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react.tanstack_router',\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: lastMatch ? 'route' : 'url',\n ...routeMatchToParamSpanAttributes(lastMatch),\n },\n });\n }\n\n if (instrumentNavigation) {\n // The onBeforeNavigate hook is called at the very beginning of a navigation and is only called once per navigation, even when the user is redirected\n castRouterInstance.subscribe('onBeforeNavigate', onBeforeNavigateArgs => {\n // onBeforeNavigate is called during pageloads. We can avoid creating navigation spans by comparing the states of the to and from arguments.\n if (onBeforeNavigateArgs.toLocation.state === onBeforeNavigateArgs.fromLocation?.state) {\n return;\n }\n\n const onResolvedMatchedRoutes = castRouterInstance.matchRoutes(\n onBeforeNavigateArgs.toLocation.pathname,\n onBeforeNavigateArgs.toLocation.search,\n { preload: false, throwOnError: false },\n );\n\n const onBeforeNavigateLastMatch = onResolvedMatchedRoutes[onResolvedMatchedRoutes.length - 1];\n\n const navigationLocation = WINDOW.location;\n const navigationSpan = startBrowserTracingNavigationSpan(client, {\n name: onBeforeNavigateLastMatch ? onBeforeNavigateLastMatch.routeId : navigationLocation.pathname,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.tanstack_router',\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: onBeforeNavigateLastMatch ? 'route' : 'url',\n },\n });\n\n // In case the user is redirected during navigation we want to update the span with the right value.\n const unsubscribeOnResolved = castRouterInstance.subscribe('onResolved', onResolvedArgs => {\n unsubscribeOnResolved();\n if (navigationSpan) {\n const onResolvedMatchedRoutes = castRouterInstance.matchRoutes(\n onResolvedArgs.toLocation.pathname,\n onResolvedArgs.toLocation.search,\n { preload: false, throwOnError: false },\n );\n\n const onResolvedLastMatch = onResolvedMatchedRoutes[onResolvedMatchedRoutes.length - 1];\n\n if (onResolvedLastMatch) {\n navigationSpan.updateName(onResolvedLastMatch.routeId);\n navigationSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');\n navigationSpan.setAttributes(routeMatchToParamSpanAttributes(onResolvedLastMatch));\n }\n }\n });\n });\n }\n },\n };\n}\n\nfunction routeMatchToParamSpanAttributes(match: VendoredTanstackRouterRouteMatch | undefined): Record<string, string> {\n if (!match) {\n return {};\n }\n\n const paramAttributes: Record<string, string> = {};\n Object.entries(match.params).forEach(([key, value]) => {\n paramAttributes[`url.path.params.${key}`] = value; // TODO(v11): remove attribute which does not adhere to Sentry's semantic convention\n paramAttributes[`url.path.parameter.${key}`] = value;\n paramAttributes[`params.${key}`] = value; // params.[key] is an alias\n });\n\n return paramAttributes;\n}\n"],"names":["originalBrowserTracingIntegration","WINDOW","startBrowserTracingPageLoadSpan","SEMANTIC_ATTRIBUTE_SENTRY_OP","SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN","SEMANTIC_ATTRIBUTE_SENTRY_SOURCE","startBrowserTracingNavigationSpan"],"mappings":";;;;;AAcA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,uCAAuC;AACvD;AACA,EAAE,MAAM;AACR,EAAE,OAAO,GAA4D,EAAE;AACvE,EAAe;AACf,EAAE,MAAM,kBAAkB,GAA2B,MAAM;;AAE3D,EAAE,MAAM,iCAAA,GAAoCA,iCAAiC,CAAC;AAC9E,IAAI,GAAG,OAAO;AACd,IAAI,oBAAoB,EAAE,KAAK;AAC/B,IAAI,kBAAkB,EAAE,KAAK;AAC7B,GAAG,CAAC;;AAEJ,EAAE,MAAM,EAAE,kBAAA,GAAqB,IAAI,EAAE,oBAAA,GAAuB,IAAA,EAAK,GAAI,OAAO;;AAE5E,EAAE,OAAO;AACT,IAAI,GAAG,iCAAiC;AACxC,IAAI,aAAa,CAAC,MAAM,EAAE;AAC1B,MAAM,iCAAiC,CAAC,aAAa,CAAC,MAAM,CAAC;;AAE7D,MAAM,MAAM,qBAAA,GAAwBC,cAAM,CAAC,QAAQ;AACnD,MAAM,IAAI,kBAAA,IAAsB,qBAAqB,EAAE;AACvD,QAAQ,MAAM,aAAA,GAAgB,kBAAkB,CAAC,WAAW;AAC5D,UAAU,qBAAqB,CAAC,QAAQ;AACxC,UAAU,kBAAkB,CAAC,OAAO,CAAC,WAAW,CAAC,qBAAqB,CAAC,MAAM,CAAC;AAC9E,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO;AACjD,SAAS;;AAET,QAAQ,MAAM,SAAA,GAAY,aAAa,CAAC,aAAa,CAAC,MAAA,GAAS,CAAC,CAAC;;AAEjE,QAAQC,uCAA+B,CAAC,MAAM,EAAE;AAChD,UAAU,IAAI,EAAE,SAAA,GAAY,SAAS,CAAC,OAAA,GAAU,qBAAqB,CAAC,QAAQ;AAC9E,UAAU,UAAU,EAAE;AACtB,YAAY,CAACC,iCAA4B,GAAG,UAAU;AACtD,YAAY,CAACC,qCAAgC,GAAG,qCAAqC;AACrF,YAAY,CAACC,qCAAgC,GAAG,YAAY,OAAA,GAAU,KAAK;AAC3E,YAAY,GAAG,+BAA+B,CAAC,SAAS,CAAC;AACzD,WAAW;AACX,SAAS,CAAC;AACV;;
|
|
1
|
+
{"version":3,"file":"tanstackrouter.js","sources":["../../src/tanstackrouter.ts"],"sourcesContent":["import {\n browserTracingIntegration as originalBrowserTracingIntegration,\n startBrowserTracingNavigationSpan,\n startBrowserTracingPageLoadSpan,\n WINDOW,\n} from '@sentry/browser';\nimport type { Integration } from '@sentry/core';\nimport {\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,\n} from '@sentry/core';\nimport type { VendoredTanstackRouter, VendoredTanstackRouterRouteMatch } from './vendor/tanstackrouter-types';\n\n/**\n * A custom browser tracing integration for TanStack Router.\n *\n * The minimum compatible version of `@tanstack/react-router` is `1.64.0`.\n *\n * @param router A TanStack Router `Router` instance that should be used for routing instrumentation.\n * @param options Sentry browser tracing configuration.\n */\nexport function tanstackRouterBrowserTracingIntegration(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n router: any, // This is `any` because we don't want any type mismatches if TanStack Router changes their types\n options: Parameters<typeof originalBrowserTracingIntegration>[0] = {},\n): Integration {\n const castRouterInstance: VendoredTanstackRouter = router;\n\n const browserTracingIntegrationInstance = originalBrowserTracingIntegration({\n ...options,\n instrumentNavigation: false,\n instrumentPageLoad: false,\n });\n\n const { instrumentPageLoad = true, instrumentNavigation = true } = options;\n\n return {\n ...browserTracingIntegrationInstance,\n afterAllSetup(client) {\n browserTracingIntegrationInstance.afterAllSetup(client);\n\n const initialWindowLocation = WINDOW.location;\n if (instrumentPageLoad && initialWindowLocation) {\n const matchedRoutes = castRouterInstance.matchRoutes(\n initialWindowLocation.pathname,\n castRouterInstance.options.parseSearch(initialWindowLocation.search),\n { preload: false, throwOnError: false },\n );\n\n const lastMatch = matchedRoutes[matchedRoutes.length - 1];\n\n startBrowserTracingPageLoadSpan(client, {\n name: lastMatch ? lastMatch.routeId : initialWindowLocation.pathname,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react.tanstack_router',\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: lastMatch ? 'route' : 'url',\n ...routeMatchToParamSpanAttributes(lastMatch),\n },\n });\n }\n\n if (instrumentNavigation) {\n // The onBeforeNavigate hook is called at the very beginning of a navigation and is only called once per navigation, even when the user is redirected\n castRouterInstance.subscribe('onBeforeNavigate', onBeforeNavigateArgs => {\n // onBeforeNavigate is called during pageloads. We can avoid creating navigation spans by comparing the states of the to and from arguments.\n if (onBeforeNavigateArgs.toLocation.state === onBeforeNavigateArgs.fromLocation?.state) {\n return;\n }\n\n const onResolvedMatchedRoutes = castRouterInstance.matchRoutes(\n onBeforeNavigateArgs.toLocation.pathname,\n onBeforeNavigateArgs.toLocation.search,\n { preload: false, throwOnError: false },\n );\n\n const onBeforeNavigateLastMatch = onResolvedMatchedRoutes[onResolvedMatchedRoutes.length - 1];\n\n const navigationLocation = WINDOW.location;\n const navigationSpan = startBrowserTracingNavigationSpan(client, {\n name: onBeforeNavigateLastMatch ? onBeforeNavigateLastMatch.routeId : navigationLocation.pathname,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.tanstack_router',\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: onBeforeNavigateLastMatch ? 'route' : 'url',\n },\n });\n\n // In case the user is redirected during navigation we want to update the span with the right value.\n const unsubscribeOnResolved = castRouterInstance.subscribe('onResolved', onResolvedArgs => {\n unsubscribeOnResolved();\n if (navigationSpan) {\n const onResolvedMatchedRoutes = castRouterInstance.matchRoutes(\n onResolvedArgs.toLocation.pathname,\n onResolvedArgs.toLocation.search,\n { preload: false, throwOnError: false },\n );\n\n const onResolvedLastMatch = onResolvedMatchedRoutes[onResolvedMatchedRoutes.length - 1];\n\n if (onResolvedLastMatch) {\n navigationSpan.updateName(onResolvedLastMatch.routeId);\n navigationSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');\n navigationSpan.setAttributes(routeMatchToParamSpanAttributes(onResolvedLastMatch));\n }\n }\n });\n });\n }\n },\n };\n}\n\nfunction routeMatchToParamSpanAttributes(match: VendoredTanstackRouterRouteMatch | undefined): Record<string, string> {\n if (!match) {\n return {};\n }\n\n const paramAttributes: Record<string, string> = {};\n Object.entries(match.params).forEach(([key, value]) => {\n paramAttributes[`url.path.params.${key}`] = value; // TODO(v11): remove attribute which does not adhere to Sentry's semantic convention\n paramAttributes[`url.path.parameter.${key}`] = value;\n paramAttributes[`params.${key}`] = value; // params.[key] is an alias\n });\n\n return paramAttributes;\n}\n"],"names":["originalBrowserTracingIntegration","WINDOW","startBrowserTracingPageLoadSpan","SEMANTIC_ATTRIBUTE_SENTRY_OP","SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN","SEMANTIC_ATTRIBUTE_SENTRY_SOURCE","startBrowserTracingNavigationSpan"],"mappings":"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"}
|
package/build/esm/error.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error.js","sources":["../../src/error.ts"],"sourcesContent":["import { captureException, withScope } from '@sentry/browser';\nimport { isError } from '@sentry/core';\nimport type { ErrorInfo } from 'react';\nimport { version } from 'react';\n\n/**\n * See if React major version is 17+ by parsing version string.\n */\nexport function isAtLeastReact17(reactVersion: string): boolean {\n const reactMajor = reactVersion.match(/^([^.]+)/);\n return reactMajor !== null && parseInt(reactMajor[0]) >= 17;\n}\n\n/**\n * Recurse through `error.cause` chain to set cause on an error.\n */\nexport function setCause(error: Error & { cause?: Error }, cause: Error): void {\n const seenErrors = new WeakSet();\n\n function recurse(error: Error & { cause?: Error }, cause: Error): void {\n // If we've already seen the error, there is a recursive loop somewhere in the error's\n // cause chain. Let's just bail out then to prevent a stack overflow.\n if (seenErrors.has(error)) {\n return;\n }\n if (error.cause) {\n seenErrors.add(error);\n return recurse(error.cause, cause);\n }\n error.cause = cause;\n }\n\n recurse(error, cause);\n}\n\n/**\n * Captures an error that was thrown by a React ErrorBoundary or React root.\n *\n * @param error The error to capture.\n * @param errorInfo The errorInfo provided by React.\n * @param hint Optional additional data to attach to the Sentry event.\n * @returns the id of the captured Sentry event.\n */\nexport function captureReactException(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n error: any,\n { componentStack }: ErrorInfo,\n hint?: Parameters<typeof captureException>[1],\n): string {\n // If on React version >= 17, create stack trace from componentStack param and links\n // to to the original error using `error.cause` otherwise relies on error param for stacktrace.\n // Linking errors requires the `LinkedErrors` integration be enabled.\n // See: https://reactjs.org/blog/2020/08/10/react-v17-rc.html#native-component-stacks\n //\n // Although `componentDidCatch` is typed to accept an `Error` object, it can also be invoked\n // with non-error objects. This is why we need to check if the error is an error-like object.\n // See: https://github.com/getsentry/sentry-javascript/issues/6167\n if (isAtLeastReact17(version) && isError(error) && componentStack) {\n const errorBoundaryError = new Error(error.message);\n errorBoundaryError.name = `React ErrorBoundary ${error.name}`;\n errorBoundaryError.stack = componentStack;\n\n // Using the `LinkedErrors` integration to link the errors together.\n setCause(error, errorBoundaryError);\n }\n\n return withScope(scope => {\n scope.setContext('react', { componentStack });\n return captureException(error, hint);\n });\n}\n\n/**\n * Creates an error handler that can be used with the `onCaughtError`, `onUncaughtError`,\n * and `onRecoverableError` options in `createRoot` and `hydrateRoot` React DOM methods.\n *\n * @param callback An optional callback that will be called after the error is captured.\n * Use this to add custom handling for errors.\n *\n * @example\n *\n * ```JavaScript\n * const root = createRoot(container, {\n * onCaughtError: Sentry.reactErrorHandler(),\n * onUncaughtError: Sentry.reactErrorHandler((error, errorInfo) => {\n * console.warn('Caught error', error, errorInfo.componentStack);\n * });\n * });\n * ```\n */\nexport function reactErrorHandler(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n callback?: (error: any, errorInfo: ErrorInfo, eventId: string) => void,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): (error: any, errorInfo: ErrorInfo) => void {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (error: any, errorInfo: ErrorInfo) => {\n const hasCallback = !!callback;\n const eventId = captureReactException(error, errorInfo, {\n mechanism: { handled: hasCallback, type: 'auto.function.react.error_handler' },\n });\n if (hasCallback) {\n callback(error, errorInfo, eventId);\n }\n };\n}\n"],"names":[],"mappings":";;;;AAKA;AACA;AACA;AACO,SAAS,gBAAgB,CAAC,YAAY,EAAmB;AAChE,EAAE,MAAM,aAAa,YAAY,CAAC,KAAK,CAAC,UAAU,CAAC;AACnD,EAAE,OAAO,UAAA,KAAe,IAAA,IAAQ,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA,IAAK,EAAE;AAC7D;;AAEA;AACA;AACA;AACO,SAAS,QAAQ,CAAC,KAAK,EAA6B,KAAK,EAAe;AAC/E,EAAE,MAAM,UAAA,GAAa,IAAI,OAAO,EAAE;;AAElC,EAAE,SAAS,OAAO,CAAC,KAAK,EAA6B,KAAK,EAAe;AACzE;AACA;AACA,IAAI,IAAI,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE;AAC/B,MAAM;AACN;
|
|
1
|
+
{"version":3,"file":"error.js","sources":["../../src/error.ts"],"sourcesContent":["import { captureException, withScope } from '@sentry/browser';\nimport { isError } from '@sentry/core';\nimport type { ErrorInfo } from 'react';\nimport { version } from 'react';\n\n/**\n * See if React major version is 17+ by parsing version string.\n */\nexport function isAtLeastReact17(reactVersion: string): boolean {\n const reactMajor = reactVersion.match(/^([^.]+)/);\n return reactMajor !== null && parseInt(reactMajor[0]) >= 17;\n}\n\n/**\n * Recurse through `error.cause` chain to set cause on an error.\n */\nexport function setCause(error: Error & { cause?: Error }, cause: Error): void {\n const seenErrors = new WeakSet();\n\n function recurse(error: Error & { cause?: Error }, cause: Error): void {\n // If we've already seen the error, there is a recursive loop somewhere in the error's\n // cause chain. Let's just bail out then to prevent a stack overflow.\n if (seenErrors.has(error)) {\n return;\n }\n if (error.cause) {\n seenErrors.add(error);\n return recurse(error.cause, cause);\n }\n error.cause = cause;\n }\n\n recurse(error, cause);\n}\n\n/**\n * Captures an error that was thrown by a React ErrorBoundary or React root.\n *\n * @param error The error to capture.\n * @param errorInfo The errorInfo provided by React.\n * @param hint Optional additional data to attach to the Sentry event.\n * @returns the id of the captured Sentry event.\n */\nexport function captureReactException(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n error: any,\n { componentStack }: ErrorInfo,\n hint?: Parameters<typeof captureException>[1],\n): string {\n // If on React version >= 17, create stack trace from componentStack param and links\n // to to the original error using `error.cause` otherwise relies on error param for stacktrace.\n // Linking errors requires the `LinkedErrors` integration be enabled.\n // See: https://reactjs.org/blog/2020/08/10/react-v17-rc.html#native-component-stacks\n //\n // Although `componentDidCatch` is typed to accept an `Error` object, it can also be invoked\n // with non-error objects. This is why we need to check if the error is an error-like object.\n // See: https://github.com/getsentry/sentry-javascript/issues/6167\n if (isAtLeastReact17(version) && isError(error) && componentStack) {\n const errorBoundaryError = new Error(error.message);\n errorBoundaryError.name = `React ErrorBoundary ${error.name}`;\n errorBoundaryError.stack = componentStack;\n\n // Using the `LinkedErrors` integration to link the errors together.\n setCause(error, errorBoundaryError);\n }\n\n return withScope(scope => {\n scope.setContext('react', { componentStack });\n return captureException(error, hint);\n });\n}\n\n/**\n * Creates an error handler that can be used with the `onCaughtError`, `onUncaughtError`,\n * and `onRecoverableError` options in `createRoot` and `hydrateRoot` React DOM methods.\n *\n * @param callback An optional callback that will be called after the error is captured.\n * Use this to add custom handling for errors.\n *\n * @example\n *\n * ```JavaScript\n * const root = createRoot(container, {\n * onCaughtError: Sentry.reactErrorHandler(),\n * onUncaughtError: Sentry.reactErrorHandler((error, errorInfo) => {\n * console.warn('Caught error', error, errorInfo.componentStack);\n * });\n * });\n * ```\n */\nexport function reactErrorHandler(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n callback?: (error: any, errorInfo: ErrorInfo, eventId: string) => void,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): (error: any, errorInfo: ErrorInfo) => void {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (error: any, errorInfo: ErrorInfo) => {\n const hasCallback = !!callback;\n const eventId = captureReactException(error, errorInfo, {\n mechanism: { handled: hasCallback, type: 'auto.function.react.error_handler' },\n });\n if (hasCallback) {\n callback(error, errorInfo, eventId);\n }\n };\n}\n"],"names":[],"mappings":";;;;AAKA;AACA;AACA;AACO,SAAS,gBAAgB,CAAC,YAAY,EAAmB;AAChE,EAAE,MAAM,aAAa,YAAY,CAAC,KAAK,CAAC,UAAU,CAAC;AACnD,EAAE,OAAO,UAAA,KAAe,IAAA,IAAQ,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA,IAAK,EAAE;AAC7D;;AAEA;AACA;AACA;AACO,SAAS,QAAQ,CAAC,KAAK,EAA6B,KAAK,EAAe;AAC/E,EAAE,MAAM,UAAA,GAAa,IAAI,OAAO,EAAE;;AAElC,EAAE,SAAS,OAAO,CAAC,KAAK,EAA6B,KAAK,EAAe;AACzE;AACA;AACA,IAAI,IAAI,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE;AAC/B,MAAM;AACN,IAAI;AACJ,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE;AACrB,MAAM,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC;AAC3B,MAAM,OAAO,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC;AACxC,IAAI;AACJ,IAAI,KAAK,CAAC,KAAA,GAAQ,KAAK;AACvB,EAAE;;AAEF,EAAE,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC;AACvB;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,qBAAqB;AACrC;AACA,EAAE,KAAK;AACP,EAAE,EAAE,gBAAgB;AACpB,EAAE,IAAI;AACN,EAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAE,IAAI,gBAAgB,CAAC,OAAO,CAAA,IAAK,OAAO,CAAC,KAAK,CAAA,IAAK,cAAc,EAAE;AACrE,IAAI,MAAM,qBAAqB,IAAI,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC;AACvD,IAAI,kBAAkB,CAAC,IAAA,GAAO,CAAC,oBAAoB,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;AACA,IAAA,kBAAA,CAAA,KAAA,GAAA,cAAA;;AAEA;AACA,IAAA,QAAA,CAAA,KAAA,EAAA,kBAAA,CAAA;AACA,EAAA;;AAEA,EAAA,OAAA,SAAA,CAAA,KAAA,IAAA;AACA,IAAA,KAAA,CAAA,UAAA,CAAA,OAAA,EAAA,EAAA,cAAA,EAAA,CAAA;AACA,IAAA,OAAA,gBAAA,CAAA,KAAA,EAAA,IAAA,CAAA;AACA,EAAA,CAAA,CAAA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAA,iBAAA;AACA;AACA,EAAA,QAAA;AACA;AACA,EAAA;AACA;AACA,EAAA,OAAA,CAAA,KAAA,EAAA,SAAA,KAAA;AACA,IAAA,MAAA,WAAA,GAAA,CAAA,CAAA,QAAA;AACA,IAAA,MAAA,OAAA,GAAA,qBAAA,CAAA,KAAA,EAAA,SAAA,EAAA;AACA,MAAA,SAAA,EAAA,EAAA,OAAA,EAAA,WAAA,EAAA,IAAA,EAAA,mCAAA,EAAA;AACA,KAAA,CAAA;AACA,IAAA,IAAA,WAAA,EAAA;AACA,MAAA,QAAA,CAAA,KAAA,EAAA,SAAA,EAAA,OAAA,CAAA;AACA,IAAA;AACA,EAAA,CAAA;AACA;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errorboundary.js","sources":["../../src/errorboundary.tsx"],"sourcesContent":["import type { ReportDialogOptions } from '@sentry/browser';\nimport { getClient, showReportDialog, withScope } from '@sentry/browser';\nimport type { Scope } from '@sentry/core';\nimport { debug } from '@sentry/core';\nimport * as React from 'react';\nimport { DEBUG_BUILD } from './debug-build';\nimport { captureReactException } from './error';\nimport { hoistNonReactStatics } from './hoist-non-react-statics';\n\nexport const UNKNOWN_COMPONENT = 'unknown';\n\nexport type FallbackRender = (errorData: {\n error: unknown;\n componentStack: string;\n eventId: string;\n resetError(): void;\n}) => React.ReactElement;\n\ntype OnUnmountType = {\n (error: null, componentStack: null, eventId: null): void;\n (error: unknown, componentStack: string, eventId: string): void;\n};\n\nexport type ErrorBoundaryProps = {\n children?: React.ReactNode | (() => React.ReactNode);\n /** If a Sentry report dialog should be rendered on error */\n showDialog?: boolean | undefined;\n /**\n * Options to be passed into the Sentry report dialog.\n * No-op if {@link showDialog} is false.\n */\n dialogOptions?: ReportDialogOptions | undefined;\n /**\n * A fallback component that gets rendered when the error boundary encounters an error.\n *\n * Can either provide a React Component, or a function that returns React Component as\n * a valid fallback prop. If a function is provided, the function will be called with\n * the error, the component stack, and an function that resets the error boundary on error.\n *\n */\n fallback?: React.ReactElement | FallbackRender | undefined;\n /**\n * If set to `true` or `false`, the error `handled` property will be set to the given value.\n * If unset, the default behaviour is to rely on the presence of the `fallback` prop to determine\n * if the error was handled or not.\n */\n handled?: boolean | undefined;\n /** Called when the error boundary encounters an error */\n onError?: ((error: unknown, componentStack: string, eventId: string) => void) | undefined;\n /** Called on componentDidMount() */\n onMount?: (() => void) | undefined;\n /**\n * Called when the error boundary resets due to a reset call from the\n * fallback render props function.\n */\n onReset?: ((error: unknown, componentStack: string, eventId: string) => void) | undefined;\n /**\n * Called on componentWillUnmount() with the error, componentStack, and eventId.\n *\n * If the error boundary never encountered an error, the error\n * componentStack, and eventId will be null.\n */\n onUnmount?: OnUnmountType | undefined;\n /** Called before the error is captured by Sentry, allows for you to add tags or context using the scope */\n beforeCapture?: ((scope: Scope, error: unknown, componentStack: string) => void) | undefined;\n};\n\ntype ErrorBoundaryState =\n | {\n componentStack: null;\n error: null;\n eventId: null;\n }\n | {\n componentStack: React.ErrorInfo['componentStack'];\n error: unknown;\n eventId: string;\n };\n\nconst INITIAL_STATE: ErrorBoundaryState = {\n componentStack: null,\n error: null,\n eventId: null,\n};\n\n/**\n * A ErrorBoundary component that logs errors to Sentry.\n * NOTE: If you are a Sentry user, and you are seeing this stack frame, it means the\n * Sentry React SDK ErrorBoundary caught an error invoking your application code. This\n * is expected behavior and NOT indicative of a bug with the Sentry React SDK.\n */\nclass ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {\n public state: ErrorBoundaryState;\n\n private readonly _openFallbackReportDialog: boolean;\n\n private _lastEventId?: string;\n private _cleanupHook?: () => void;\n\n public constructor(props: ErrorBoundaryProps) {\n super(props);\n\n this.state = INITIAL_STATE;\n this._openFallbackReportDialog = true;\n\n const client = getClient();\n if (client && props.showDialog) {\n this._openFallbackReportDialog = false;\n this._cleanupHook = client.on('afterSendEvent', event => {\n if (!event.type && this._lastEventId && event.event_id === this._lastEventId) {\n showReportDialog({ ...props.dialogOptions, eventId: this._lastEventId });\n }\n });\n }\n }\n\n public componentDidCatch(error: unknown, errorInfo: React.ErrorInfo): void {\n const { componentStack } = errorInfo;\n const { beforeCapture, onError, showDialog, dialogOptions } = this.props;\n withScope(scope => {\n if (beforeCapture) {\n beforeCapture(scope, error, componentStack);\n }\n\n const handled = this.props.handled != null ? this.props.handled : !!this.props.fallback;\n const eventId = captureReactException(error, errorInfo, {\n mechanism: { handled, type: 'auto.function.react.error_boundary' },\n });\n\n if (onError) {\n onError(error, componentStack, eventId);\n }\n if (showDialog) {\n this._lastEventId = eventId;\n if (this._openFallbackReportDialog) {\n showReportDialog({ ...dialogOptions, eventId });\n }\n }\n\n // componentDidCatch is used over getDerivedStateFromError\n // so that componentStack is accessible through state.\n this.setState({ error, componentStack, eventId });\n });\n }\n\n public componentDidMount(): void {\n const { onMount } = this.props;\n if (onMount) {\n onMount();\n }\n }\n\n public componentWillUnmount(): void {\n const { error, componentStack, eventId } = this.state;\n const { onUnmount } = this.props;\n if (onUnmount) {\n if (this.state === INITIAL_STATE) {\n // If the error boundary never encountered an error, call onUnmount with null values\n onUnmount(null, null, null);\n } else {\n // `componentStack` and `eventId` are guaranteed to be non-null here because `onUnmount` is only called\n // when the error boundary has already encountered an error.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n onUnmount(error, componentStack!, eventId!);\n }\n }\n\n if (this._cleanupHook) {\n this._cleanupHook();\n this._cleanupHook = undefined;\n }\n }\n\n public resetErrorBoundary(): void {\n const { onReset } = this.props;\n const { error, componentStack, eventId } = this.state;\n if (onReset) {\n // `componentStack` and `eventId` are guaranteed to be non-null here because `onReset` is only called\n // when the error boundary has already encountered an error.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n onReset(error, componentStack!, eventId!);\n }\n this.setState(INITIAL_STATE);\n }\n\n public render(): React.ReactNode {\n const { fallback, children } = this.props;\n const state = this.state;\n\n // `componentStack` is only null in the initial state, when no error has been captured.\n // If an error has been captured, `componentStack` will be a string.\n // We cannot check `state.error` because null can be thrown as an error.\n if (state.componentStack === null) {\n return typeof children === 'function' ? children() : children;\n }\n\n const element =\n typeof fallback === 'function'\n ? React.createElement(fallback, {\n error: state.error,\n componentStack: state.componentStack,\n resetError: () => this.resetErrorBoundary(),\n eventId: state.eventId,\n })\n : fallback;\n\n if (React.isValidElement(element)) {\n return element;\n }\n\n if (fallback) {\n DEBUG_BUILD && debug.warn('fallback did not produce a valid ReactElement');\n }\n\n // Fail gracefully if no fallback provided or is not valid\n return null;\n }\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction withErrorBoundary<P extends Record<string, any>>(\n WrappedComponent: React.ComponentType<P>,\n errorBoundaryOptions: ErrorBoundaryProps,\n): React.FC<P> {\n const componentDisplayName = WrappedComponent.displayName || WrappedComponent.name || UNKNOWN_COMPONENT;\n\n const Wrapped = React.memo((props: P) => (\n <ErrorBoundary {...errorBoundaryOptions}>\n <WrappedComponent {...props} />\n </ErrorBoundary>\n )) as unknown as React.FC<P>;\n\n Wrapped.displayName = `errorBoundary(${componentDisplayName})`;\n\n // Copy over static methods from Wrapped component to Profiler HOC\n // See: https://reactjs.org/docs/higher-order-components.html#static-methods-must-be-copied-over\n hoistNonReactStatics(Wrapped, WrappedComponent);\n return Wrapped;\n}\n\nexport { ErrorBoundary, withErrorBoundary };\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"errorboundary.js","sources":["../../src/errorboundary.tsx"],"sourcesContent":["import type { ReportDialogOptions } from '@sentry/browser';\nimport { getClient, showReportDialog, withScope } from '@sentry/browser';\nimport type { Scope } from '@sentry/core';\nimport { debug } from '@sentry/core';\nimport * as React from 'react';\nimport { DEBUG_BUILD } from './debug-build';\nimport { captureReactException } from './error';\nimport { hoistNonReactStatics } from './hoist-non-react-statics';\n\nexport const UNKNOWN_COMPONENT = 'unknown';\n\nexport type FallbackRender = (errorData: {\n error: unknown;\n componentStack: string;\n eventId: string;\n resetError(): void;\n}) => React.ReactElement;\n\ntype OnUnmountType = {\n (error: null, componentStack: null, eventId: null): void;\n (error: unknown, componentStack: string, eventId: string): void;\n};\n\nexport type ErrorBoundaryProps = {\n children?: React.ReactNode | (() => React.ReactNode);\n /** If a Sentry report dialog should be rendered on error */\n showDialog?: boolean | undefined;\n /**\n * Options to be passed into the Sentry report dialog.\n * No-op if {@link showDialog} is false.\n */\n dialogOptions?: ReportDialogOptions | undefined;\n /**\n * A fallback component that gets rendered when the error boundary encounters an error.\n *\n * Can either provide a React Component, or a function that returns React Component as\n * a valid fallback prop. If a function is provided, the function will be called with\n * the error, the component stack, and an function that resets the error boundary on error.\n *\n */\n fallback?: React.ReactElement | FallbackRender | undefined;\n /**\n * If set to `true` or `false`, the error `handled` property will be set to the given value.\n * If unset, the default behaviour is to rely on the presence of the `fallback` prop to determine\n * if the error was handled or not.\n */\n handled?: boolean | undefined;\n /** Called when the error boundary encounters an error */\n onError?: ((error: unknown, componentStack: string, eventId: string) => void) | undefined;\n /** Called on componentDidMount() */\n onMount?: (() => void) | undefined;\n /**\n * Called when the error boundary resets due to a reset call from the\n * fallback render props function.\n */\n onReset?: ((error: unknown, componentStack: string, eventId: string) => void) | undefined;\n /**\n * Called on componentWillUnmount() with the error, componentStack, and eventId.\n *\n * If the error boundary never encountered an error, the error\n * componentStack, and eventId will be null.\n */\n onUnmount?: OnUnmountType | undefined;\n /** Called before the error is captured by Sentry, allows for you to add tags or context using the scope */\n beforeCapture?: ((scope: Scope, error: unknown, componentStack: string) => void) | undefined;\n};\n\ntype ErrorBoundaryState =\n | {\n componentStack: null;\n error: null;\n eventId: null;\n }\n | {\n componentStack: React.ErrorInfo['componentStack'];\n error: unknown;\n eventId: string;\n };\n\nconst INITIAL_STATE: ErrorBoundaryState = {\n componentStack: null,\n error: null,\n eventId: null,\n};\n\n/**\n * A ErrorBoundary component that logs errors to Sentry.\n * NOTE: If you are a Sentry user, and you are seeing this stack frame, it means the\n * Sentry React SDK ErrorBoundary caught an error invoking your application code. This\n * is expected behavior and NOT indicative of a bug with the Sentry React SDK.\n */\nclass ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {\n public state: ErrorBoundaryState;\n\n private readonly _openFallbackReportDialog: boolean;\n\n private _lastEventId?: string;\n private _cleanupHook?: () => void;\n\n public constructor(props: ErrorBoundaryProps) {\n super(props);\n\n this.state = INITIAL_STATE;\n this._openFallbackReportDialog = true;\n\n const client = getClient();\n if (client && props.showDialog) {\n this._openFallbackReportDialog = false;\n this._cleanupHook = client.on('afterSendEvent', event => {\n if (!event.type && this._lastEventId && event.event_id === this._lastEventId) {\n showReportDialog({ ...props.dialogOptions, eventId: this._lastEventId });\n }\n });\n }\n }\n\n public componentDidCatch(error: unknown, errorInfo: React.ErrorInfo): void {\n const { componentStack } = errorInfo;\n const { beforeCapture, onError, showDialog, dialogOptions } = this.props;\n withScope(scope => {\n if (beforeCapture) {\n beforeCapture(scope, error, componentStack);\n }\n\n const handled = this.props.handled != null ? this.props.handled : !!this.props.fallback;\n const eventId = captureReactException(error, errorInfo, {\n mechanism: { handled, type: 'auto.function.react.error_boundary' },\n });\n\n if (onError) {\n onError(error, componentStack, eventId);\n }\n if (showDialog) {\n this._lastEventId = eventId;\n if (this._openFallbackReportDialog) {\n showReportDialog({ ...dialogOptions, eventId });\n }\n }\n\n // componentDidCatch is used over getDerivedStateFromError\n // so that componentStack is accessible through state.\n this.setState({ error, componentStack, eventId });\n });\n }\n\n public componentDidMount(): void {\n const { onMount } = this.props;\n if (onMount) {\n onMount();\n }\n }\n\n public componentWillUnmount(): void {\n const { error, componentStack, eventId } = this.state;\n const { onUnmount } = this.props;\n if (onUnmount) {\n if (this.state === INITIAL_STATE) {\n // If the error boundary never encountered an error, call onUnmount with null values\n onUnmount(null, null, null);\n } else {\n // `componentStack` and `eventId` are guaranteed to be non-null here because `onUnmount` is only called\n // when the error boundary has already encountered an error.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n onUnmount(error, componentStack!, eventId!);\n }\n }\n\n if (this._cleanupHook) {\n this._cleanupHook();\n this._cleanupHook = undefined;\n }\n }\n\n public resetErrorBoundary(): void {\n const { onReset } = this.props;\n const { error, componentStack, eventId } = this.state;\n if (onReset) {\n // `componentStack` and `eventId` are guaranteed to be non-null here because `onReset` is only called\n // when the error boundary has already encountered an error.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n onReset(error, componentStack!, eventId!);\n }\n this.setState(INITIAL_STATE);\n }\n\n public render(): React.ReactNode {\n const { fallback, children } = this.props;\n const state = this.state;\n\n // `componentStack` is only null in the initial state, when no error has been captured.\n // If an error has been captured, `componentStack` will be a string.\n // We cannot check `state.error` because null can be thrown as an error.\n if (state.componentStack === null) {\n return typeof children === 'function' ? children() : children;\n }\n\n const element =\n typeof fallback === 'function'\n ? React.createElement(fallback, {\n error: state.error,\n componentStack: state.componentStack,\n resetError: () => this.resetErrorBoundary(),\n eventId: state.eventId,\n })\n : fallback;\n\n if (React.isValidElement(element)) {\n return element;\n }\n\n if (fallback) {\n DEBUG_BUILD && debug.warn('fallback did not produce a valid ReactElement');\n }\n\n // Fail gracefully if no fallback provided or is not valid\n return null;\n }\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction withErrorBoundary<P extends Record<string, any>>(\n WrappedComponent: React.ComponentType<P>,\n errorBoundaryOptions: ErrorBoundaryProps,\n): React.FC<P> {\n const componentDisplayName = WrappedComponent.displayName || WrappedComponent.name || UNKNOWN_COMPONENT;\n\n const Wrapped = React.memo((props: P) => (\n <ErrorBoundary {...errorBoundaryOptions}>\n <WrappedComponent {...props} />\n </ErrorBoundary>\n )) as unknown as React.FC<P>;\n\n Wrapped.displayName = `errorBoundary(${componentDisplayName})`;\n\n // Copy over static methods from Wrapped component to Profiler HOC\n // See: https://reactjs.org/docs/higher-order-components.html#static-methods-must-be-copied-over\n hoistNonReactStatics(Wrapped, WrappedComponent);\n return Wrapped;\n}\n\nexport { ErrorBoundary, withErrorBoundary };\n"],"names":[],"mappings":"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"}
|
package/build/esm/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"type":"module","version":"10.
|
|
1
|
+
{"type":"module","version":"10.26.0","sideEffects":false}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"profiler.js","sources":["../../src/profiler.tsx"],"sourcesContent":["import { startInactiveSpan } from '@sentry/browser';\nimport type { Span } from '@sentry/core';\nimport { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, spanToJSON, timestampInSeconds, withActiveSpan } from '@sentry/core';\nimport * as React from 'react';\nimport { REACT_MOUNT_OP, REACT_RENDER_OP, REACT_UPDATE_OP } from './constants';\nimport { hoistNonReactStatics } from './hoist-non-react-statics';\n\nexport const UNKNOWN_COMPONENT = 'unknown';\n\nexport type ProfilerProps = {\n // The name of the component being profiled.\n name: string;\n // If the Profiler is disabled. False by default. This is useful if you want to disable profilers\n // in certain environments.\n disabled?: boolean;\n // If time component is on page should be displayed as spans. True by default.\n includeRender?: boolean;\n // If component updates should be displayed as spans. True by default.\n includeUpdates?: boolean;\n // Component that is being profiled.\n children?: React.ReactNode;\n // props given to component being profiled.\n updateProps: { [key: string]: unknown };\n};\n\n/**\n * The Profiler component leverages Sentry's Tracing integration to generate\n * spans based on component lifecycles.\n */\nclass Profiler extends React.Component<ProfilerProps> {\n /**\n * The span of the mount activity\n * Made protected for the React Native SDK to access\n */\n protected _mountSpan: Span | undefined;\n /**\n * The span that represents the duration of time between shouldComponentUpdate and componentDidUpdate\n */\n protected _updateSpan: Span | undefined;\n\n public constructor(props: ProfilerProps) {\n super(props);\n const { name, disabled = false } = this.props;\n\n if (disabled) {\n return;\n }\n\n this._mountSpan = startInactiveSpan({\n name: `<${name}>`,\n onlyIfParent: true,\n op: REACT_MOUNT_OP,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.react.profiler',\n 'ui.component_name': name,\n },\n });\n }\n\n // If a component mounted, we can finish the mount activity.\n public componentDidMount(): void {\n if (this._mountSpan) {\n this._mountSpan.end();\n }\n }\n\n public shouldComponentUpdate({ updateProps, includeUpdates = true }: ProfilerProps): boolean {\n // Only generate an update span if includeUpdates is true, if there is a valid mountSpan,\n // and if the updateProps have changed. It is ok to not do a deep equality check here as it is expensive.\n // We are just trying to give baseline clues for further investigation.\n if (includeUpdates && this._mountSpan && updateProps !== this.props.updateProps) {\n // See what props have changed between the previous props, and the current props. This is\n // set as data on the span. We just store the prop keys as the values could be potentially very large.\n const changedProps = Object.keys(updateProps).filter(k => updateProps[k] !== this.props.updateProps[k]);\n if (changedProps.length > 0) {\n const now = timestampInSeconds();\n this._updateSpan = withActiveSpan(this._mountSpan, () => {\n return startInactiveSpan({\n name: `<${this.props.name}>`,\n onlyIfParent: true,\n op: REACT_UPDATE_OP,\n startTime: now,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.react.profiler',\n 'ui.component_name': this.props.name,\n 'ui.react.changed_props': changedProps,\n },\n });\n });\n }\n }\n\n return true;\n }\n\n public componentDidUpdate(): void {\n if (this._updateSpan) {\n this._updateSpan.end();\n this._updateSpan = undefined;\n }\n }\n\n // If a component is unmounted, we can say it is no longer on the screen.\n // This means we can finish the span representing the component render.\n public componentWillUnmount(): void {\n const endTimestamp = timestampInSeconds();\n const { name, includeRender = true } = this.props;\n\n if (this._mountSpan && includeRender) {\n const startTime = spanToJSON(this._mountSpan).timestamp;\n withActiveSpan(this._mountSpan, () => {\n const renderSpan = startInactiveSpan({\n onlyIfParent: true,\n name: `<${name}>`,\n op: REACT_RENDER_OP,\n startTime,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.react.profiler',\n 'ui.component_name': name,\n },\n });\n if (renderSpan) {\n // Have to cast to Span because the type of _mountSpan is Span | undefined\n // and not getting narrowed properly\n renderSpan.end(endTimestamp);\n }\n });\n }\n }\n\n public render(): React.ReactNode {\n return this.props.children;\n }\n}\n\n// React.Component default props are defined as static property on the class\nObject.assign(Profiler, {\n defaultProps: {\n disabled: false,\n includeRender: true,\n includeUpdates: true,\n },\n});\n\n/**\n * withProfiler is a higher order component that wraps a\n * component in a {@link Profiler} component. It is recommended that\n * the higher order component be used over the regular {@link Profiler} component.\n *\n * @param WrappedComponent component that is wrapped by Profiler\n * @param options the {@link ProfilerProps} you can pass into the Profiler\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction withProfiler<P extends Record<string, any>>(\n WrappedComponent: React.ComponentType<P>,\n // We do not want to have `updateProps` given in options, it is instead filled through the HOC.\n options?: Pick<Partial<ProfilerProps>, Exclude<keyof ProfilerProps, 'updateProps' | 'children'>>,\n): React.FC<P> {\n const componentDisplayName =\n options?.name || WrappedComponent.displayName || WrappedComponent.name || UNKNOWN_COMPONENT;\n\n const Wrapped: React.FC<P> = (props: P) => (\n <Profiler {...options} name={componentDisplayName} updateProps={props}>\n <WrappedComponent {...props} />\n </Profiler>\n );\n\n Wrapped.displayName = `profiler(${componentDisplayName})`;\n\n // Copy over static methods from Wrapped component to Profiler HOC\n // See: https://reactjs.org/docs/higher-order-components.html#static-methods-must-be-copied-over\n hoistNonReactStatics(Wrapped, WrappedComponent);\n return Wrapped;\n}\n\n/**\n *\n * `useProfiler` is a React hook that profiles a React component.\n *\n * Requires React 16.8 or above.\n * @param name displayName of component being profiled\n */\nfunction useProfiler(\n name: string,\n options: { disabled?: boolean; hasRenderSpan?: boolean } = {\n disabled: false,\n hasRenderSpan: true,\n },\n): void {\n const [mountSpan] = React.useState(() => {\n if (options?.disabled) {\n return undefined;\n }\n\n return startInactiveSpan({\n name: `<${name}>`,\n onlyIfParent: true,\n op: REACT_MOUNT_OP,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.react.profiler',\n 'ui.component_name': name,\n },\n });\n });\n\n React.useEffect(() => {\n if (mountSpan) {\n mountSpan.end();\n }\n\n return (): void => {\n if (mountSpan && options.hasRenderSpan) {\n const startTime = spanToJSON(mountSpan).timestamp;\n const endTimestamp = timestampInSeconds();\n\n const renderSpan = startInactiveSpan({\n name: `<${name}>`,\n onlyIfParent: true,\n op: REACT_RENDER_OP,\n startTime,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.react.profiler',\n 'ui.component_name': name,\n },\n });\n if (renderSpan) {\n // Have to cast to Span because the type of _mountSpan is Span | undefined\n // and not getting narrowed properly\n renderSpan.end(endTimestamp);\n }\n }\n };\n // We only want this to run once.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n}\n\nexport { Profiler, useProfiler, withProfiler };\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"profiler.js","sources":["../../src/profiler.tsx"],"sourcesContent":["import { startInactiveSpan } from '@sentry/browser';\nimport type { Span } from '@sentry/core';\nimport { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, spanToJSON, timestampInSeconds, withActiveSpan } from '@sentry/core';\nimport * as React from 'react';\nimport { REACT_MOUNT_OP, REACT_RENDER_OP, REACT_UPDATE_OP } from './constants';\nimport { hoistNonReactStatics } from './hoist-non-react-statics';\n\nexport const UNKNOWN_COMPONENT = 'unknown';\n\nexport type ProfilerProps = {\n // The name of the component being profiled.\n name: string;\n // If the Profiler is disabled. False by default. This is useful if you want to disable profilers\n // in certain environments.\n disabled?: boolean;\n // If time component is on page should be displayed as spans. True by default.\n includeRender?: boolean;\n // If component updates should be displayed as spans. True by default.\n includeUpdates?: boolean;\n // Component that is being profiled.\n children?: React.ReactNode;\n // props given to component being profiled.\n updateProps: { [key: string]: unknown };\n};\n\n/**\n * The Profiler component leverages Sentry's Tracing integration to generate\n * spans based on component lifecycles.\n */\nclass Profiler extends React.Component<ProfilerProps> {\n /**\n * The span of the mount activity\n * Made protected for the React Native SDK to access\n */\n protected _mountSpan: Span | undefined;\n /**\n * The span that represents the duration of time between shouldComponentUpdate and componentDidUpdate\n */\n protected _updateSpan: Span | undefined;\n\n public constructor(props: ProfilerProps) {\n super(props);\n const { name, disabled = false } = this.props;\n\n if (disabled) {\n return;\n }\n\n this._mountSpan = startInactiveSpan({\n name: `<${name}>`,\n onlyIfParent: true,\n op: REACT_MOUNT_OP,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.react.profiler',\n 'ui.component_name': name,\n },\n });\n }\n\n // If a component mounted, we can finish the mount activity.\n public componentDidMount(): void {\n if (this._mountSpan) {\n this._mountSpan.end();\n }\n }\n\n public shouldComponentUpdate({ updateProps, includeUpdates = true }: ProfilerProps): boolean {\n // Only generate an update span if includeUpdates is true, if there is a valid mountSpan,\n // and if the updateProps have changed. It is ok to not do a deep equality check here as it is expensive.\n // We are just trying to give baseline clues for further investigation.\n if (includeUpdates && this._mountSpan && updateProps !== this.props.updateProps) {\n // See what props have changed between the previous props, and the current props. This is\n // set as data on the span. We just store the prop keys as the values could be potentially very large.\n const changedProps = Object.keys(updateProps).filter(k => updateProps[k] !== this.props.updateProps[k]);\n if (changedProps.length > 0) {\n const now = timestampInSeconds();\n this._updateSpan = withActiveSpan(this._mountSpan, () => {\n return startInactiveSpan({\n name: `<${this.props.name}>`,\n onlyIfParent: true,\n op: REACT_UPDATE_OP,\n startTime: now,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.react.profiler',\n 'ui.component_name': this.props.name,\n 'ui.react.changed_props': changedProps,\n },\n });\n });\n }\n }\n\n return true;\n }\n\n public componentDidUpdate(): void {\n if (this._updateSpan) {\n this._updateSpan.end();\n this._updateSpan = undefined;\n }\n }\n\n // If a component is unmounted, we can say it is no longer on the screen.\n // This means we can finish the span representing the component render.\n public componentWillUnmount(): void {\n const endTimestamp = timestampInSeconds();\n const { name, includeRender = true } = this.props;\n\n if (this._mountSpan && includeRender) {\n const startTime = spanToJSON(this._mountSpan).timestamp;\n withActiveSpan(this._mountSpan, () => {\n const renderSpan = startInactiveSpan({\n onlyIfParent: true,\n name: `<${name}>`,\n op: REACT_RENDER_OP,\n startTime,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.react.profiler',\n 'ui.component_name': name,\n },\n });\n if (renderSpan) {\n // Have to cast to Span because the type of _mountSpan is Span | undefined\n // and not getting narrowed properly\n renderSpan.end(endTimestamp);\n }\n });\n }\n }\n\n public render(): React.ReactNode {\n return this.props.children;\n }\n}\n\n// React.Component default props are defined as static property on the class\nObject.assign(Profiler, {\n defaultProps: {\n disabled: false,\n includeRender: true,\n includeUpdates: true,\n },\n});\n\n/**\n * withProfiler is a higher order component that wraps a\n * component in a {@link Profiler} component. It is recommended that\n * the higher order component be used over the regular {@link Profiler} component.\n *\n * @param WrappedComponent component that is wrapped by Profiler\n * @param options the {@link ProfilerProps} you can pass into the Profiler\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction withProfiler<P extends Record<string, any>>(\n WrappedComponent: React.ComponentType<P>,\n // We do not want to have `updateProps` given in options, it is instead filled through the HOC.\n options?: Pick<Partial<ProfilerProps>, Exclude<keyof ProfilerProps, 'updateProps' | 'children'>>,\n): React.FC<P> {\n const componentDisplayName =\n options?.name || WrappedComponent.displayName || WrappedComponent.name || UNKNOWN_COMPONENT;\n\n const Wrapped: React.FC<P> = (props: P) => (\n <Profiler {...options} name={componentDisplayName} updateProps={props}>\n <WrappedComponent {...props} />\n </Profiler>\n );\n\n Wrapped.displayName = `profiler(${componentDisplayName})`;\n\n // Copy over static methods from Wrapped component to Profiler HOC\n // See: https://reactjs.org/docs/higher-order-components.html#static-methods-must-be-copied-over\n hoistNonReactStatics(Wrapped, WrappedComponent);\n return Wrapped;\n}\n\n/**\n *\n * `useProfiler` is a React hook that profiles a React component.\n *\n * Requires React 16.8 or above.\n * @param name displayName of component being profiled\n */\nfunction useProfiler(\n name: string,\n options: { disabled?: boolean; hasRenderSpan?: boolean } = {\n disabled: false,\n hasRenderSpan: true,\n },\n): void {\n const [mountSpan] = React.useState(() => {\n if (options?.disabled) {\n return undefined;\n }\n\n return startInactiveSpan({\n name: `<${name}>`,\n onlyIfParent: true,\n op: REACT_MOUNT_OP,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.react.profiler',\n 'ui.component_name': name,\n },\n });\n });\n\n React.useEffect(() => {\n if (mountSpan) {\n mountSpan.end();\n }\n\n return (): void => {\n if (mountSpan && options.hasRenderSpan) {\n const startTime = spanToJSON(mountSpan).timestamp;\n const endTimestamp = timestampInSeconds();\n\n const renderSpan = startInactiveSpan({\n name: `<${name}>`,\n onlyIfParent: true,\n op: REACT_RENDER_OP,\n startTime,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.react.profiler',\n 'ui.component_name': name,\n },\n });\n if (renderSpan) {\n // Have to cast to Span because the type of _mountSpan is Span | undefined\n // and not getting narrowed properly\n renderSpan.end(endTimestamp);\n }\n }\n };\n // We only want this to run once.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n}\n\nexport { Profiler, useProfiler, withProfiler };\n"],"names":[],"mappings":"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"}
|
|
@@ -21,9 +21,11 @@ let _enableAsyncRouteHandlers = false;
|
|
|
21
21
|
const CLIENTS_WITH_INSTRUMENT_NAVIGATION = new WeakSet();
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
24
|
+
* Tracks last navigation per client to prevent duplicate spans in cross-usage scenarios.
|
|
25
|
+
* Entry persists until the navigation span ends, allowing cross-usage detection during delayed wrapper execution.
|
|
26
26
|
*/
|
|
27
|
+
const LAST_NAVIGATION_PER_CLIENT = new WeakMap();
|
|
28
|
+
|
|
27
29
|
function addResolvedRoutesToParent(resolvedRoutes, parentRoute) {
|
|
28
30
|
const existingChildren = parentRoute.children || [];
|
|
29
31
|
|
|
@@ -42,6 +44,26 @@ function addResolvedRoutesToParent(resolvedRoutes, parentRoute) {
|
|
|
42
44
|
}
|
|
43
45
|
}
|
|
44
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Determines if a navigation should be handled based on router state.
|
|
49
|
+
* Only handles:
|
|
50
|
+
* - PUSH navigations (always)
|
|
51
|
+
* - POP navigations (only after initial pageload is complete)
|
|
52
|
+
* - When router state is 'idle' (not 'loading' or 'submitting')
|
|
53
|
+
*
|
|
54
|
+
* During 'loading' or 'submitting', state.location may still have the old pathname,
|
|
55
|
+
* which would cause us to create a span for the wrong route.
|
|
56
|
+
*/
|
|
57
|
+
function shouldHandleNavigation(
|
|
58
|
+
state,
|
|
59
|
+
isInitialPageloadComplete,
|
|
60
|
+
) {
|
|
61
|
+
return (
|
|
62
|
+
(state.historyAction === 'PUSH' || (state.historyAction === 'POP' && isInitialPageloadComplete)) &&
|
|
63
|
+
state.navigation.state === 'idle'
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
45
67
|
// Keeping as a global variable for cross-usage in multiple functions
|
|
46
68
|
// only exported for testing purposes
|
|
47
69
|
const allRoutes = new Set();
|
|
@@ -218,27 +240,15 @@ function createV6CompatibleWrapCreateBrowserRouter
|
|
|
218
240
|
// If we haven't seen a pageload span yet, keep waiting (don't mark as complete)
|
|
219
241
|
}
|
|
220
242
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
version,
|
|
231
|
-
basename,
|
|
232
|
-
allRoutes: Array.from(allRoutes),
|
|
233
|
-
});
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
// Wait for the next render if loading an unsettled route
|
|
237
|
-
if (state.navigation.state !== 'idle') {
|
|
238
|
-
requestAnimationFrame(navigationHandler);
|
|
239
|
-
} else {
|
|
240
|
-
navigationHandler();
|
|
241
|
-
}
|
|
243
|
+
if (shouldHandleNavigation(state, isInitialPageloadComplete)) {
|
|
244
|
+
handleNavigation({
|
|
245
|
+
location: state.location,
|
|
246
|
+
routes,
|
|
247
|
+
navigationType: state.historyAction,
|
|
248
|
+
version,
|
|
249
|
+
basename,
|
|
250
|
+
allRoutes: Array.from(allRoutes),
|
|
251
|
+
});
|
|
242
252
|
}
|
|
243
253
|
});
|
|
244
254
|
|
|
@@ -344,29 +354,15 @@ function createV6CompatibleWrapCreateMemoryRouter
|
|
|
344
354
|
// If we haven't seen a pageload span yet, keep waiting (don't mark as complete)
|
|
345
355
|
}
|
|
346
356
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
routes,
|
|
357
|
-
navigationType: state.historyAction,
|
|
358
|
-
version,
|
|
359
|
-
basename,
|
|
360
|
-
allRoutes: Array.from(allRoutes),
|
|
361
|
-
});
|
|
362
|
-
};
|
|
363
|
-
|
|
364
|
-
// Wait for the next render if loading an unsettled route
|
|
365
|
-
if (state.navigation.state !== 'idle') {
|
|
366
|
-
requestAnimationFrame(navigationHandler);
|
|
367
|
-
} else {
|
|
368
|
-
navigationHandler();
|
|
369
|
-
}
|
|
357
|
+
if (shouldHandleNavigation(state, isInitialPageloadComplete)) {
|
|
358
|
+
handleNavigation({
|
|
359
|
+
location: state.location,
|
|
360
|
+
routes,
|
|
361
|
+
navigationType: state.historyAction,
|
|
362
|
+
version,
|
|
363
|
+
basename,
|
|
364
|
+
allRoutes: Array.from(allRoutes),
|
|
365
|
+
});
|
|
370
366
|
}
|
|
371
367
|
});
|
|
372
368
|
|
|
@@ -560,19 +556,74 @@ function wrapPatchRoutesOnNavigation(
|
|
|
560
556
|
};
|
|
561
557
|
}
|
|
562
558
|
|
|
559
|
+
function getNavigationKey(location) {
|
|
560
|
+
return `${location.pathname}${location.search}${location.hash}`;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function tryUpdateSpanName(
|
|
564
|
+
activeSpan,
|
|
565
|
+
currentSpanName,
|
|
566
|
+
newName,
|
|
567
|
+
newSource,
|
|
568
|
+
) {
|
|
569
|
+
// Check if the new name contains React Router parameter syntax (/:param/)
|
|
570
|
+
const isReactRouterParam = /\/:[a-zA-Z0-9_]+/.test(newName);
|
|
571
|
+
const isNewNameParameterized = newName !== currentSpanName && isReactRouterParam;
|
|
572
|
+
if (isNewNameParameterized) {
|
|
573
|
+
activeSpan.updateName(newName);
|
|
574
|
+
activeSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, newSource );
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function isDuplicateNavigation(client, navigationKey) {
|
|
579
|
+
const lastKey = LAST_NAVIGATION_PER_CLIENT.get(client);
|
|
580
|
+
return lastKey === navigationKey;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function createNavigationSpan(opts
|
|
584
|
+
|
|
585
|
+
) {
|
|
586
|
+
const { client, name, source, version, location, routes, basename, allRoutes, navigationKey } = opts;
|
|
587
|
+
|
|
588
|
+
const navigationSpan = startBrowserTracingNavigationSpan(client, {
|
|
589
|
+
name,
|
|
590
|
+
attributes: {
|
|
591
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source ,
|
|
592
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',
|
|
593
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: `auto.navigation.react.reactrouter_v${version}`,
|
|
594
|
+
},
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
if (navigationSpan) {
|
|
598
|
+
LAST_NAVIGATION_PER_CLIENT.set(client, navigationKey);
|
|
599
|
+
patchNavigationSpanEnd(navigationSpan, location, routes, basename, allRoutes);
|
|
600
|
+
|
|
601
|
+
const unsubscribe = client.on('spanEnd', endedSpan => {
|
|
602
|
+
if (endedSpan === navigationSpan) {
|
|
603
|
+
// Clear key only if it's still our key (handles overlapping navigations)
|
|
604
|
+
const lastKey = LAST_NAVIGATION_PER_CLIENT.get(client);
|
|
605
|
+
if (lastKey === navigationKey) {
|
|
606
|
+
LAST_NAVIGATION_PER_CLIENT.delete(client);
|
|
607
|
+
}
|
|
608
|
+
unsubscribe(); // Prevent memory leak
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
return navigationSpan;
|
|
614
|
+
}
|
|
615
|
+
|
|
563
616
|
function handleNavigation(opts
|
|
564
617
|
|
|
565
618
|
) {
|
|
566
619
|
const { location, routes, navigationType, version, matches, basename, allRoutes } = opts;
|
|
567
|
-
const branches = Array.isArray(matches) ? matches : _matchRoutes(routes, location, basename);
|
|
620
|
+
const branches = Array.isArray(matches) ? matches : _matchRoutes(allRoutes || routes, location, basename);
|
|
568
621
|
|
|
569
622
|
const client = getClient();
|
|
570
623
|
if (!client || !CLIENTS_WITH_INSTRUMENT_NAVIGATION.has(client)) {
|
|
571
624
|
return;
|
|
572
625
|
}
|
|
573
626
|
|
|
574
|
-
// Avoid starting a navigation span on initial load when a pageload root span is active.
|
|
575
|
-
// This commonly happens when lazy routes resolve during the first render and React Router emits a POP.
|
|
576
627
|
const activeRootSpan = getActiveRootSpan();
|
|
577
628
|
if (activeRootSpan && spanToJSON(activeRootSpan).op === 'pageload' && navigationType === 'POP') {
|
|
578
629
|
return;
|
|
@@ -581,31 +632,39 @@ function handleNavigation(opts
|
|
|
581
632
|
if ((navigationType === 'PUSH' || navigationType === 'POP') && branches) {
|
|
582
633
|
const [name, source] = resolveRouteNameAndSource(
|
|
583
634
|
location,
|
|
584
|
-
routes,
|
|
635
|
+
allRoutes || routes,
|
|
585
636
|
allRoutes || routes,
|
|
586
637
|
branches ,
|
|
587
638
|
basename,
|
|
588
639
|
);
|
|
589
640
|
|
|
590
|
-
const
|
|
591
|
-
const
|
|
592
|
-
const isAlreadyInNavigationSpan = spanJson?.op === 'navigation';
|
|
641
|
+
const currentNavigationKey = getNavigationKey(location);
|
|
642
|
+
const isNavDuplicate = isDuplicateNavigation(client, currentNavigationKey);
|
|
593
643
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
const
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source,
|
|
600
|
-
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',
|
|
601
|
-
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: `auto.navigation.react.reactrouter_v${version}`,
|
|
602
|
-
},
|
|
603
|
-
});
|
|
644
|
+
if (isNavDuplicate) {
|
|
645
|
+
// Cross-usage duplicate - update existing span name if better
|
|
646
|
+
const activeSpan = getActiveSpan();
|
|
647
|
+
const spanJson = activeSpan && spanToJSON(activeSpan);
|
|
648
|
+
const isAlreadyInNavigationSpan = spanJson?.op === 'navigation';
|
|
604
649
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
patchNavigationSpanEnd(navigationSpan, location, routes, basename, allRoutes);
|
|
650
|
+
if (isAlreadyInNavigationSpan && activeSpan) {
|
|
651
|
+
tryUpdateSpanName(activeSpan, spanJson?.description, name, source);
|
|
608
652
|
}
|
|
653
|
+
} else {
|
|
654
|
+
// Not a cross-usage duplicate - create new span
|
|
655
|
+
// This handles: different routes, same route with different params (/user/2 → /user/3)
|
|
656
|
+
// startBrowserTracingNavigationSpan will end any active navigation span
|
|
657
|
+
createNavigationSpan({
|
|
658
|
+
client,
|
|
659
|
+
name,
|
|
660
|
+
source,
|
|
661
|
+
version,
|
|
662
|
+
location,
|
|
663
|
+
routes,
|
|
664
|
+
basename,
|
|
665
|
+
allRoutes,
|
|
666
|
+
navigationKey: currentNavigationKey,
|
|
667
|
+
});
|
|
609
668
|
}
|
|
610
669
|
}
|
|
611
670
|
}
|
|
@@ -654,7 +713,13 @@ function updatePageloadTransaction({
|
|
|
654
713
|
: (_matchRoutes(allRoutes || routes, location, basename) );
|
|
655
714
|
|
|
656
715
|
if (branches) {
|
|
657
|
-
const [name, source] = resolveRouteNameAndSource(
|
|
716
|
+
const [name, source] = resolveRouteNameAndSource(
|
|
717
|
+
location,
|
|
718
|
+
allRoutes || routes,
|
|
719
|
+
allRoutes || routes,
|
|
720
|
+
branches,
|
|
721
|
+
basename,
|
|
722
|
+
);
|
|
658
723
|
|
|
659
724
|
getCurrentScope().setTransactionName(name || '/');
|
|
660
725
|
|
|
@@ -707,7 +772,7 @@ function patchSpanEnd(
|
|
|
707
772
|
if (branches) {
|
|
708
773
|
const [name, source] = resolveRouteNameAndSource(
|
|
709
774
|
location,
|
|
710
|
-
routes,
|
|
775
|
+
currentAllRoutes.length > 0 ? currentAllRoutes : routes,
|
|
711
776
|
currentAllRoutes.length > 0 ? currentAllRoutes : routes,
|
|
712
777
|
branches,
|
|
713
778
|
basename,
|