Skip to content

Commit 15a7e6d

Browse files
feat(init): Load options from sentry.options.json in JS (#4510)
1 parent 14fe05d commit 15a7e6d

File tree

4 files changed

+93
-20
lines changed

4 files changed

+93
-20
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
- Add experimental version of `init` with optional `OptionsConfiguration<SentryAndroidOptions>` for Android ([#4451](https://github.com/getsentry/sentry-react-native/pull/4451))
5454
- Add initialization using `sentry.options.json` for Apple platforms ([#4447](https://github.com/getsentry/sentry-react-native/pull/4447))
5555
- Add initialization using `sentry.options.json` for Android ([#4451](https://github.com/getsentry/sentry-react-native/pull/4451))
56+
- Merge options from file with `Sentry.init` options in JS ([#4510](https://github.com/getsentry/sentry-react-native/pull/4510))
5657
5758
### Internal
5859

packages/core/src/js/sdk.tsx

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { useEncodePolyfill } from './transports/encodePolyfill';
1919
import { DEFAULT_BUFFER_SIZE, makeNativeTransportFactory } from './transports/native';
2020
import { getDefaultEnvironment, isExpoGo, isRunningInMetroDevServer } from './utils/environment';
2121
import { safeFactory, safeTracesSampler } from './utils/safe';
22+
import { RN_GLOBAL_OBJ } from './utils/worldwide';
2223
import { NATIVE } from './wrapper';
2324

2425
const DEFAULT_OPTIONS: ReactNativeOptions = {
@@ -47,12 +48,17 @@ export function init(passedOptions: ReactNativeOptions): void {
4748
return;
4849
}
4950

50-
const maxQueueSize = passedOptions.maxQueueSize
51+
const userOptions = {
52+
...RN_GLOBAL_OBJ.__SENTRY_OPTIONS__,
53+
...passedOptions,
54+
};
55+
56+
const maxQueueSize = userOptions.maxQueueSize
5157
// eslint-disable-next-line deprecation/deprecation
52-
?? passedOptions.transportOptions?.bufferSize
58+
?? userOptions.transportOptions?.bufferSize
5359
?? DEFAULT_OPTIONS.maxQueueSize;
5460

55-
const enableNative = passedOptions.enableNative === undefined || passedOptions.enableNative
61+
const enableNative = userOptions.enableNative === undefined || userOptions.enableNative
5662
? NATIVE.isNativeAvailable()
5763
: false;
5864

@@ -75,11 +81,11 @@ export function init(passedOptions: ReactNativeOptions): void {
7581
return `${dsnComponents.protocol}://${dsnComponents.host}${port}`;
7682
};
7783

78-
const userBeforeBreadcrumb = safeFactory(passedOptions.beforeBreadcrumb, { loggerMessage: 'The beforeBreadcrumb threw an error' });
84+
const userBeforeBreadcrumb = safeFactory(userOptions.beforeBreadcrumb, { loggerMessage: 'The beforeBreadcrumb threw an error' });
7985

8086
// Exclude Dev Server and Sentry Dsn request from Breadcrumbs
8187
const devServerUrl = getDevServer()?.url;
82-
const dsn = getURLFromDSN(passedOptions.dsn);
88+
const dsn = getURLFromDSN(userOptions.dsn);
8389
const defaultBeforeBreadcrumb = (breadcrumb: Breadcrumb, _hint?: BreadcrumbHint): Breadcrumb | null => {
8490
const type = breadcrumb.type || '';
8591
const url = typeof breadcrumb.data?.url === 'string' ? breadcrumb.data.url : '';
@@ -103,26 +109,34 @@ export function init(passedOptions: ReactNativeOptions): void {
103109

104110
const options: ReactNativeClientOptions = {
105111
...DEFAULT_OPTIONS,
106-
...passedOptions,
112+
...userOptions,
107113
enableNative,
108-
enableNativeNagger: shouldEnableNativeNagger(passedOptions.enableNativeNagger),
114+
enableNativeNagger: shouldEnableNativeNagger(userOptions.enableNativeNagger),
109115
// If custom transport factory fails the SDK won't initialize
110-
transport: passedOptions.transport
116+
transport: userOptions.transport
111117
|| makeNativeTransportFactory({
112118
enableNative,
113119
})
114120
|| makeFetchTransport,
115121
transportOptions: {
116122
...DEFAULT_OPTIONS.transportOptions,
117-
...(passedOptions.transportOptions ?? {}),
123+
...(userOptions.transportOptions ?? {}),
118124
bufferSize: maxQueueSize,
119125
},
120126
maxQueueSize,
121127
integrations: [],
122-
stackParser: stackParserFromStackParserOptions(passedOptions.stackParser || defaultStackParser),
128+
stackParser: stackParserFromStackParserOptions(userOptions.stackParser || defaultStackParser),
123129
beforeBreadcrumb: chainedBeforeBreadcrumb,
124-
initialScope: safeFactory(passedOptions.initialScope, { loggerMessage: 'The initialScope threw an error' }),
130+
initialScope: safeFactory(userOptions.initialScope, { loggerMessage: 'The initialScope threw an error' }),
125131
};
132+
133+
if (!('autoInitializeNativeSdk' in userOptions) && RN_GLOBAL_OBJ.__SENTRY_OPTIONS__) {
134+
// We expect users to use the file options only in combination with manual native initialization
135+
// eslint-disable-next-line no-console
136+
console.info('Initializing Sentry JS with the options file. Expecting manual native initialization before JS. Native will not be initialized automatically.');
137+
options.autoInitializeNativeSdk = false;
138+
}
139+
126140
if ('tracesSampler' in options) {
127141
options.tracesSampler = safeTracesSampler(options.tracesSampler);
128142
}
@@ -131,12 +145,12 @@ export function init(passedOptions: ReactNativeOptions): void {
131145
options.environment = getDefaultEnvironment();
132146
}
133147

134-
const defaultIntegrations: false | Integration[] = passedOptions.defaultIntegrations === undefined
148+
const defaultIntegrations: false | Integration[] = userOptions.defaultIntegrations === undefined
135149
? getDefaultIntegrations(options)
136-
: passedOptions.defaultIntegrations;
150+
: userOptions.defaultIntegrations;
137151

138152
options.integrations = getIntegrationsToSetup({
139-
integrations: safeFactory(passedOptions.integrations, { loggerMessage: 'The integrations threw an error' }),
153+
integrations: safeFactory(userOptions.integrations, { loggerMessage: 'The integrations threw an error' }),
140154
defaultIntegrations,
141155
});
142156
initAndBind(ReactNativeClient, options);
@@ -145,6 +159,10 @@ export function init(passedOptions: ReactNativeOptions): void {
145159
logger.info('Offline caching, native errors features are not available in Expo Go.');
146160
logger.info('Use EAS Build / Native Release Build to test these features.');
147161
}
162+
163+
if (RN_GLOBAL_OBJ.__SENTRY_OPTIONS__) {
164+
logger.info('Sentry JS initialized with options from the options file.');
165+
}
148166
}
149167

150168
/**

packages/core/src/js/utils/worldwide.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { InternalGlobal } from '@sentry/core';
22
import { GLOBAL_OBJ } from '@sentry/core';
33
import type { ErrorUtils } from 'react-native/types';
44

5+
import type { ReactNativeOptions } from '../options';
56
import type { ExpoGlobalObject } from './expoglobalobject';
67

78
/** Internal Global object interface with common and Sentry specific properties */
@@ -25,6 +26,7 @@ export interface ReactNativeInternalGlobal extends InternalGlobal {
2526
__BUNDLE_START_TIME__?: number;
2627
nativePerformanceNow?: () => number;
2728
TextEncoder?: TextEncoder;
29+
__SENTRY_OPTIONS__?: ReactNativeOptions;
2830
}
2931

3032
type TextEncoder = {

packages/core/test/sdk.test.ts

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
import type { BaseTransportOptions, Breadcrumb, BreadcrumbHint, ClientOptions, Integration, Scope } from '@sentry/core';
1+
import type { Breadcrumb, BreadcrumbHint, Integration, Scope } from '@sentry/core';
22
import { initAndBind, logger } from '@sentry/core';
33
import { makeFetchTransport } from '@sentry/react';
44

55
import { getDevServer } from '../src/js/integrations/debugsymbolicatorutils';
6+
import type { ReactNativeClientOptions } from '../src/js/options';
67
import { init, withScope } from '../src/js/sdk';
78
import type { ReactNativeTracingIntegration } from '../src/js/tracing';
89
import { REACT_NATIVE_TRACING_INTEGRATION_NAME, reactNativeTracingIntegration } from '../src/js/tracing';
910
import { makeNativeTransport } from '../src/js/transports/native';
1011
import { getDefaultEnvironment, isExpoGo, notWeb } from '../src/js/utils/environment';
12+
import { RN_GLOBAL_OBJ } from '../src/js/utils/worldwide';
1113
import { NATIVE } from './mockWrapper';
1214
import { firstArg, secondArg } from './testutils';
1315

@@ -109,6 +111,60 @@ describe('Tests the SDK functionality', () => {
109111
});
110112
});
111113

114+
describe('initialization from sentry.options.json', () => {
115+
it('initializes without __SENTRY_OPTIONS__', () => {
116+
delete RN_GLOBAL_OBJ.__SENTRY_OPTIONS__;
117+
init({});
118+
expect(initAndBind).toHaveBeenCalledOnce();
119+
});
120+
121+
it('adds options from __SENTRY_OPTIONS__', () => {
122+
RN_GLOBAL_OBJ.__SENTRY_OPTIONS__ = {
123+
dsn: 'https://key@example.io/value',
124+
};
125+
init({});
126+
expect(usedOptions()?.dsn).toBe('https://key@example.io/value');
127+
});
128+
129+
it('options init override options from __SENTRY_OPTIONS__', () => {
130+
RN_GLOBAL_OBJ.__SENTRY_OPTIONS__ = {
131+
dsn: 'https://key@example.io/file',
132+
};
133+
init({
134+
dsn: 'https://key@example.io/code',
135+
});
136+
expect(usedOptions()?.dsn).toBe('https://key@example.io/code');
137+
});
138+
139+
it('initializing with __SENTRY_OPTIONS__ disabled native auto initialization', () => {
140+
RN_GLOBAL_OBJ.__SENTRY_OPTIONS__ = {};
141+
init({});
142+
expect(usedOptions()?.autoInitializeNativeSdk).toBe(false);
143+
});
144+
145+
it('initializing without __SENTRY_OPTIONS__ enables native auto initialization', () => {
146+
RN_GLOBAL_OBJ.__SENTRY_OPTIONS__ = undefined;
147+
init({});
148+
expect(usedOptions()?.autoInitializeNativeSdk).toBe(true);
149+
});
150+
151+
it('initializing with __SENTRY_OPTIONS__ keeps native auto initialization true if set', () => {
152+
RN_GLOBAL_OBJ.__SENTRY_OPTIONS__ = {};
153+
init({
154+
autoInitializeNativeSdk: true,
155+
});
156+
expect(usedOptions()?.autoInitializeNativeSdk).toBe(true);
157+
});
158+
159+
it('initializing with __SENTRY_OPTIONS__ keeps native auto initialization false if set', () => {
160+
RN_GLOBAL_OBJ.__SENTRY_OPTIONS__ = {};
161+
init({
162+
autoInitializeNativeSdk: false,
163+
});
164+
expect(usedOptions()?.autoInitializeNativeSdk).toBe(false);
165+
});
166+
});
167+
112168
describe('environment', () => {
113169
it('detect development environment', () => {
114170
(getDefaultEnvironment as jest.Mock).mockImplementation(() => 'development');
@@ -173,7 +229,6 @@ describe('Tests the SDK functionality', () => {
173229
(NATIVE.isNativeAvailable as jest.Mock).mockImplementation(() => false);
174230
init({});
175231
expect(NATIVE.isNativeAvailable).toBeCalled();
176-
// @ts-expect-error enableNative not publicly available here.
177232
expect(usedOptions()?.enableNative).toEqual(false);
178233
expect(usedOptions()?.transport).toEqual(makeFetchTransport);
179234
});
@@ -182,7 +237,6 @@ describe('Tests the SDK functionality', () => {
182237
(NATIVE.isNativeAvailable as jest.Mock).mockImplementation(() => false);
183238
init({ enableNative: true });
184239
expect(NATIVE.isNativeAvailable).toBeCalled();
185-
// @ts-expect-error enableNative not publicly available here.
186240
expect(usedOptions()?.enableNative).toEqual(false);
187241
expect(usedOptions()?.transport).toEqual(makeFetchTransport);
188242
});
@@ -191,7 +245,6 @@ describe('Tests the SDK functionality', () => {
191245
(NATIVE.isNativeAvailable as jest.Mock).mockImplementation(() => false);
192246
init({ enableNative: false });
193247
expect(NATIVE.isNativeAvailable).not.toBeCalled();
194-
// @ts-expect-error enableNative not publicly available here.
195248
expect(usedOptions()?.enableNative).toEqual(false);
196249
expect(usedOptions()?.transport).toEqual(makeFetchTransport);
197250
});
@@ -204,7 +257,6 @@ describe('Tests the SDK functionality', () => {
204257
});
205258
expect(usedOptions()?.transport).toEqual(mockTransport);
206259
expect(NATIVE.isNativeAvailable).toBeCalled();
207-
// @ts-expect-error enableNative not publicly available here.
208260
expect(usedOptions()?.enableNative).toEqual(false);
209261
});
210262
});
@@ -1058,7 +1110,7 @@ function createMockedIntegration({ name }: { name?: string } = {}): Integration
10581110
};
10591111
}
10601112

1061-
function usedOptions(): ClientOptions<BaseTransportOptions> | undefined {
1113+
function usedOptions(): ReactNativeClientOptions | undefined {
10621114
return (initAndBind as jest.MockedFunction<typeof initAndBind>).mock.calls[0]?.[secondArg];
10631115
}
10641116

0 commit comments

Comments
 (0)