Repository navigation
feat(core): Support applicationKey and moduleMetadata in Metro config #6863
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,9 +16,14 @@ import { | |
| setSentryDefaultBabelTransformerPathEnv, | ||
| } from './sentryBabelTransformerUtils'; | ||
| import { checkSentryExpoNativeProject } from './sentryExpoNativeCheck'; | ||
| import { createSentryMetroSerializer, unstableBeforeAssetSerializationDebugIdPlugin } from './sentryMetroSerializer'; | ||
| import { | ||
| createSentryMetroSerializer, | ||
| unstableBeforeAssetSerializationDebugIdPlugin, | ||
| unstableBeforeAssetSerializationModuleMetadataPlugin, | ||
| } from './sentryMetroSerializer'; | ||
| import { withSentryOptionsFromFile } from './sentryOptionsSerializer'; | ||
| import { unstableReleaseConstantsPlugin } from './sentryReleaseInjector'; | ||
| import { buildModuleMetadata } from './utils'; | ||
|
|
||
| export * from './sentryMetroSerializer'; | ||
|
|
||
|
|
@@ -88,6 +93,21 @@ export interface SentryMetroConfigOptions { | |
| * @default false | ||
| */ | ||
| autoWrapExpoRouterErrorBoundary?: boolean; | ||
| /** | ||
| * A key that identifies the code of your application. | ||
| * Use the same key in the `filterKeys` option of `thirdPartyErrorFilterIntegration` | ||
| * to filter errors that come from third-party code, for example browser extensions. | ||
| * | ||
| * This is the same option as `applicationKey` of the Sentry bundler plugins. | ||
| */ | ||
| applicationKey?: string; | ||
| /** | ||
| * Metadata to attach to the stack frames of your application code. | ||
| * The SDK adds the metadata to the `module_metadata` field of each stack frame from your bundle. | ||
| * | ||
| * This is the same option as the object form of `moduleMetadata` of the Sentry bundler plugins. | ||
| */ | ||
| moduleMetadata?: Record<string, unknown>; | ||
| } | ||
|
|
||
| export interface SentryExpoConfigOptions { | ||
|
|
@@ -119,13 +139,15 @@ export function withSentryConfig( | |
| enableSourceContextInDevelopment = true, | ||
| optionsFile = true, | ||
| autoWrapExpoRouterErrorBoundary = false, | ||
| applicationKey, | ||
| moduleMetadata, | ||
| }: SentryMetroConfigOptions = {}, | ||
| ): MetroConfig { | ||
| setSentryMetroDevServerEnvFlag(); | ||
|
|
||
| let newConfig = config; | ||
|
|
||
| newConfig = withSentryDebugId(newConfig); | ||
| newConfig = withSentryDebugId(newConfig, buildModuleMetadata({ applicationKey, moduleMetadata })); | ||
| newConfig = withSentryFramesCollapsed(newConfig); | ||
| if (annotateReactComponents || autoWrapExpoRouterErrorBoundary) { | ||
| newConfig = withSentryBabelTransformer(newConfig, annotateReactComponents, autoWrapExpoRouterErrorBoundary); | ||
|
|
@@ -159,11 +181,13 @@ export function getSentryExpoConfig( | |
| checkSentryExpoNativeProject(projectRoot); | ||
|
|
||
| const getDefaultConfig = options.getDefaultConfig || loadExpoMetroConfigModule().getDefaultConfig; | ||
| const moduleMetadata = buildModuleMetadata(options); | ||
| const config = getDefaultConfig(projectRoot, { | ||
| ...options, | ||
| unstable_beforeAssetSerializationPlugins: [ | ||
| ...(options.unstable_beforeAssetSerializationPlugins || []), | ||
| ...((options.injectReleaseForWeb ?? true) ? [unstableReleaseConstantsPlugin(projectRoot)] : []), | ||
| ...(moduleMetadata ? [unstableBeforeAssetSerializationModuleMetadataPlugin(moduleMetadata)] : []), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Expo skips development metadata injectionMedium Severity
Additional Locations (2)Triggered by project rule: PR Review Guidelines for Cursor Bot Reviewed by Cursor Bugbot for commit 4e05634. Configure here. |
||
| unstableBeforeAssetSerializationDebugIdPlugin, | ||
| ], | ||
| }); | ||
|
|
@@ -258,10 +282,10 @@ export function withSentryBabelTransformer( | |
| }; | ||
| } | ||
|
|
||
| function withSentryDebugId(config: MetroConfig): MetroConfig { | ||
| const customSerializer = createSentryMetroSerializer( | ||
| config.serializer?.customSerializer || undefined, | ||
| ) as MetroCustomSerializer; | ||
| function withSentryDebugId(config: MetroConfig, moduleMetadata?: Record<string, unknown>): MetroConfig { | ||
| const customSerializer = createSentryMetroSerializer(config.serializer?.customSerializer || undefined, { | ||
| moduleMetadata, | ||
| }) as MetroCustomSerializer; | ||
| // MetroConfig types customSerializers as async only, but sync returns are also supported | ||
| // The default serializer is sync | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| import type { Event } from '@sentry/core'; | ||
|
|
||
| import { defaultStackParser } from '@sentry/browser'; | ||
| import { GLOBAL_OBJ, Scope } from '@sentry/core'; | ||
|
|
||
| import { ReactNativeClient } from '../../src/js/client'; | ||
| import { thirdPartyErrorFilterIntegration } from '../../src/js/index'; | ||
|
|
||
| jest.mock('../../src/js/wrapper', () => jest.requireActual('../mockWrapper')); | ||
|
|
||
| describe('thirdPartyErrorFilterIntegration', () => { | ||
| beforeEach(() => { | ||
| (GLOBAL_OBJ as { _sentryModuleMetadata?: Record<string, unknown> })._sentryModuleMetadata = { | ||
| [new Error().stack as string]: { '_sentryBundlerPluginAppKey:my-app': true }, | ||
| }; | ||
| }); | ||
|
|
||
| afterEach(() => { | ||
| delete (GLOBAL_OBJ as { _sentryModuleMetadata?: unknown })._sentryModuleMetadata; | ||
| }); | ||
|
|
||
| it('keeps errors from application code with the injected module metadata', async () => { | ||
| const beforeSend = await captureError(['my-app']); | ||
|
|
||
| expect(beforeSend).toHaveBeenCalledTimes(1); | ||
| }); | ||
|
|
||
| it('drops errors without frames from application code', async () => { | ||
| const beforeSend = await captureError(['other-app']); | ||
|
|
||
| expect(beforeSend).not.toHaveBeenCalled(); | ||
| }); | ||
| }); | ||
|
|
||
| async function captureError(filterKeys: string[]): Promise<jest.Mock> { | ||
| const beforeSend = jest.fn((event: Event) => event); | ||
| const client = new ReactNativeClient({ | ||
| dsn: 'https://public@sentry.example.com/1', | ||
| enableNative: false, | ||
| stackParser: defaultStackParser, | ||
| integrations: [ | ||
| thirdPartyErrorFilterIntegration({ | ||
| filterKeys, | ||
| behaviour: 'drop-error-if-exclusively-contains-third-party-frames', | ||
| }), | ||
| ], | ||
|
Check failure on line 46 in packages/core/test/integrations/thirdPartyErrorFilter.test.ts
|
||
| transport: () => ({ send: jest.fn().mockResolvedValue({}), flush: jest.fn().mockResolvedValue(true) }), | ||
| beforeSend, | ||
| }); | ||
| client.init(); | ||
|
|
||
| client.captureException(new Error('test'), undefined, new Scope()); | ||
| await client.flush(); | ||
|
|
||
| return beforeSend; | ||
| } | ||


Uh oh!
There was an error while loading. Please reload this page.