diff --git a/CHANGELOG.md b/CHANGELOG.md index 291678df..894de1c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ > [migration guide](https://docs.sentry.io/platforms/javascript/guides/capacitor/migration/) first. +## Unreleased + +### Features + +- Add `strictTraceContinuation` and `orgId` options for trace continuation validation ([#1166](https://github.com/getsentry/sentry-capacitor/pull/1166)) + ## 3.1.0 ### Dependencies diff --git a/src/nativeOptions.ts b/src/nativeOptions.ts index 019adacb..8f433d6c 100644 --- a/src/nativeOptions.ts +++ b/src/nativeOptions.ts @@ -44,6 +44,8 @@ export function FilterNativeOptions( tracesSampleRate: options.tracesSampleRate, // tunnel: options.tunnel: Only handled on the JavaScript Layer. enableCaptureFailedRequests: options.enableCaptureFailedRequests, + ...(options.strictTraceContinuation !== undefined && { strictTraceContinuation: options.strictTraceContinuation }), + ...(options.orgId !== undefined && { orgId: options.orgId }), ...iOSParameters(options), ...LogParameters(options), ...SpotlightParameters(), diff --git a/src/options.ts b/src/options.ts index 2f074c4c..214019af 100644 --- a/src/options.ts +++ b/src/options.ts @@ -74,6 +74,27 @@ export interface BaseCapacitorOptions { appHangTimeoutInterval?: number; + /** + * If set to `true`, the SDK will only continue a trace if the `organization ID` of the incoming trace found in the + * `baggage` header matches the `organization ID` of the current Sentry client. + * + * The client's organization ID is extracted from the DSN or can be set with the `orgId` option. + * + * If the organization IDs do not match, the SDK will start a new trace instead of continuing the incoming one. + * This is useful to prevent traces of unknown third-party services from being continued in your application. + * + * @default false + */ + strictTraceContinuation?: boolean; + + /** + * The organization ID for your Sentry project. + * + * The SDK will try to extract the organization ID from the DSN. If it cannot be found, or if you need to override it, + * you can provide the ID with this option. The organization ID is used for trace propagation and for features like `strictTraceContinuation`. + */ + orgId?: `${number}` | number; + /** * Only for Vue or Nuxt Client. * Allows the setup of sibling specific SDK. You are still allowed to set the same parameters diff --git a/test/nativeOptions.test.ts b/test/nativeOptions.test.ts index 65fe7ae0..b0ff16ca 100644 --- a/test/nativeOptions.test.ts +++ b/test/nativeOptions.test.ts @@ -35,6 +35,16 @@ describe('nativeOptions', () => { expect(nativeOptions.enableWatchdogTerminationTracking).toBeTruthy(); }); + test('strictTraceContinuation and orgId are set when defined', () => { + const nativeOptions = FilterNativeOptions( + { + strictTraceContinuation: true, + orgId: '12345', + }); + expect(nativeOptions.strictTraceContinuation).toBe(true); + expect(nativeOptions.orgId).toBe('12345'); + }); + test('enableCaptureFailedRequests is set when defined', () => { const nativeOptions = FilterNativeOptions( { diff --git a/test/sdk.test.ts b/test/sdk.test.ts index a05f09cc..5f98a1b7 100644 --- a/test/sdk.test.ts +++ b/test/sdk.test.ts @@ -206,6 +206,57 @@ describe('SDK Init', () => { }); }); + test('passes strictTraceContinuation and orgId to browser options', () => { + NATIVE.platform = 'web'; + const mockOriginalInit = jest.fn(); + + init({ + dsn: 'test-dsn', + enabled: true, + strictTraceContinuation: true, + orgId: '12345', + }, mockOriginalInit); + + // Wait for async operations + return new Promise(resolve => { + setTimeout(() => { + expect(mockOriginalInit).toHaveBeenCalled(); + const browserOptions = mockOriginalInit.mock.calls[0][0]; + + expect(browserOptions.strictTraceContinuation).toBe(true); + expect(browserOptions.orgId).toBe('12345'); + + resolve(); + }, 0); + }); + }); + + // Native option filtering for strictTraceContinuation and orgId + // is tested in nativeOptions.test.ts via FilterNativeOptions. + + test('strictTraceContinuation defaults to undefined when not set', () => { + NATIVE.platform = 'web'; + const mockOriginalInit = jest.fn(); + + init({ + dsn: 'test-dsn', + enabled: true, + }, mockOriginalInit); + + // Wait for async operations + return new Promise(resolve => { + setTimeout(() => { + expect(mockOriginalInit).toHaveBeenCalled(); + const browserOptions = mockOriginalInit.mock.calls[0][0]; + + expect(browserOptions.strictTraceContinuation).toBeUndefined(); + expect(browserOptions.orgId).toBeUndefined(); + + resolve(); + }, 0); + }); + }); + test('RewriteFrames to be added by default', async () => { NATIVE.platform = 'web'; const mockOriginalInit = jest.fn();