Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@

## Unreleased

### Features

- Add `strictTraceContinuation` and `orgId` options for trace continuation validation ([#1166](https://github.com/getsentry/sentry-capacitor/pull/1166))

### Dependencies

- Bump Android SDK from v8.35.0 to v8.41.0 ([#1247](https://github.com/getsentry/sentry-capacitor/pull/1247))
Expand Down
12 changes: 12 additions & 0 deletions android/src/main/java/io/sentry/capacitor/SentryCapacitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,18 @@ public void initNativeSdk(final PluginCall call) {
}
}

if (capOptions.has("strictTraceContinuation")) {
options.setStrictTraceContinuation(capOptions.getBool("strictTraceContinuation"));
}
if (capOptions.has("orgId")) {
Object orgIdValue = capOptions.opt("orgId");
if (orgIdValue instanceof String) {
options.setOrgId((String) orgIdValue);
} else if (orgIdValue instanceof Number) {
options.setOrgId(String.valueOf(((Number) orgIdValue).longValue()));
}
}

options.getLogs().setEnabled(Boolean.TRUE.equals(capOptions.getBoolean("enableLogs", false)));

logger.log(SentryLevel.INFO, String.format("Native Integrations '%s'", options.getIntegrations()));
Expand Down
11 changes: 11 additions & 0 deletions ios/Sources/SentryCapacitorPlugin/SentryCapacitorPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,17 @@ public class SentryCapacitorPlugin: CAPPlugin, CAPBridgedPlugin {
options.enableAutoPerformanceTracing = enableAutoPerformanceTracing
}

if let strictTraceContinuation = dict["strictTraceContinuation"] as? Bool {
options.strictTraceContinuation = strictTraceContinuation
}
if let orgId = dict["orgId"] {
if let orgIdString = orgId as? String {
options.orgId = orgIdString
} else if let orgIdNumber = orgId as? NSNumber {
options.orgId = orgIdNumber.stringValue
}
}

return options
}

Expand Down
2 changes: 2 additions & 0 deletions src/nativeOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,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(),
Expand Down
21 changes: 21 additions & 0 deletions src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,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
Expand Down
10 changes: 10 additions & 0 deletions test/nativeOptions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
{
Expand Down
51 changes: 51 additions & 0 deletions test/sdk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>(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<void>(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();
Expand Down
52 changes: 52 additions & 0 deletions test/wrapper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,58 @@ describe('Tests Native Wrapper', () => {
);
});

test('passes strictTraceContinuation to native SDK', async () => {
const initNativeSdk = jest.spyOn(SentryCapacitor, 'initNativeSdk');

await NATIVE.initNativeSdk({
dsn: 'test',
enableNative: true,
strictTraceContinuation: true,
});

const nativeOption = initNativeSdk.mock.calls[0]?.[0]?.options;
expect(nativeOption?.strictTraceContinuation).toBe(true);
});

test('passes orgId as string to native SDK', async () => {
const initNativeSdk = jest.spyOn(SentryCapacitor, 'initNativeSdk');

await NATIVE.initNativeSdk({
dsn: 'test',
enableNative: true,
orgId: '12345',
});

const nativeOption = initNativeSdk.mock.calls[0]?.[0]?.options;
expect(nativeOption?.orgId).toBe('12345');
});

test('passes numeric orgId to native SDK', async () => {
const initNativeSdk = jest.spyOn(SentryCapacitor, 'initNativeSdk');

await NATIVE.initNativeSdk({
dsn: 'test',
enableNative: true,
orgId: 12345,
});

const nativeOption = initNativeSdk.mock.calls[0]?.[0]?.options;
expect(nativeOption?.orgId).toBe(12345);
});

test('does not include strictTraceContinuation when not set', async () => {
const initNativeSdk = jest.spyOn(SentryCapacitor, 'initNativeSdk');

await NATIVE.initNativeSdk({
dsn: 'test',
enableNative: true,
});

const nativeOption = initNativeSdk.mock.calls[0]?.[0]?.options;
expect(nativeOption?.strictTraceContinuation).toBeUndefined();
expect(nativeOption?.orgId).toBeUndefined();
});

test('sets enableNative: false when dsn is undefined', async () => {
await NATIVE.initNativeSdk({
dsn: undefined,
Expand Down
Loading