Skip to content

Commit 1236b6e

Browse files
alwxclaude
andauthored
fix(core): Strip callback options before the native SDK starts (#6847)
* fix(core): Strip callback options before the native SDK starts A JS function crosses the bridge as a native callback that only accepts an array. sentry-cocoa reads `beforeSendLog` from the options dictionary and calls it with a `SentryLog`, which crashes the app. Remove every function value from the options before `initNativeSdk`, instead of a fixed list of names. This also covers `beforeSendSpan` and any callback the JavaScript SDK adds later. Fixes #6842 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore: Link the CHANGELOG entry to the PR Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent 0668149 commit 1236b6e

3 files changed

Lines changed: 79 additions & 13 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
- Populate the Hermes runtime version on JS profiles instead of sending an empty value ([#6817](https://github.com/getsentry/sentry-react-native/pull/6817))
1717
- Mark `@sentry/react-native` as side-effect free so bundlers can tree-shake unused exports ([#6829](https://github.com/getsentry/sentry-react-native/pull/6829))
1818
- Resolve `@sentry/react-native` from the Android project directory in the Expo plugin `build.gradle` line, instead of the directory where Gradle started ([#6840](https://github.com/getsentry/sentry-react-native/pull/6840))
19+
- If `beforeSendLog` or another callback option is set, the app no longer crashes on iOS when the native SDK emits a log ([#6847](https://github.com/getsentry/sentry-react-native/pull/6847))
20+
21+
Callback options are for the JavaScript layer only. The SDK now removes all of them before it starts the native SDK.
1922

2023
### Dependencies
2124

‎packages/core/src/js/wrapper.ts‎

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,22 @@ interface SentryNativeWrapper {
170170

171171
const EOL = encodeUTF8('\n');
172172

173+
/**
174+
* A JS function crosses the bridge as a native callback that only accepts an array.
175+
* The native SDK calls hooks such as `beforeSend` or `beforeSendLog` with a native
176+
* object instead, which crashes the app.
177+
*/
178+
function withoutFunctionValues<T extends object>(options: T): T {
179+
const source = options as Record<string, unknown>;
180+
const result: Record<string, unknown> = {};
181+
for (const key of Object.keys(source)) {
182+
if (typeof source[key] !== 'function') {
183+
result[key] = source[key];
184+
}
185+
}
186+
return result as T;
187+
}
188+
173189
/**
174190
* Our internal interface for calling native functions
175191
*/
@@ -314,21 +330,12 @@ export const NATIVE: SentryNativeWrapper = {
314330

315331
// filter out all the options that would crash native.
316332
/* oxlint-disable typescript-eslint(no-unused-vars) */
317-
const {
318-
beforeSend,
319-
beforeBreadcrumb,
320-
beforeSendTransaction,
321-
beforeSendMetric,
322-
integrations,
323-
ignoreErrors,
324-
logsOrigin,
325-
profilingOptions,
326-
androidProfilingOptions,
327-
onNativeLog,
328-
...filteredOptions
329-
} = options;
333+
const { integrations, ignoreErrors, logsOrigin, profilingOptions, androidProfilingOptions, ...remainingOptions } =
334+
options;
330335
/* oxlint-enable typescript-eslint(no-unused-vars) */
331336

337+
const filteredOptions = withoutFunctionValues(remainingOptions);
338+
332339
// Move profilingOptions into _experiments
333340
// Support deprecated androidProfilingOptions for backwards compatibility
334341
const resolvedProfilingOptions = profilingOptions ?? androidProfilingOptions;

‎packages/core/test/wrapper.test.ts‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,62 @@ describe('Tests Native Wrapper', () => {
398398
expect(NATIVE.enableNative).toBe(true);
399399
});
400400

401+
test('filter beforeSendLog when initializing Native SDK', async () => {
402+
await NATIVE.initNativeSdk({
403+
dsn: VALID_DSN,
404+
enableNative: true,
405+
autoInitializeNativeSdk: true,
406+
beforeSendLog: jest.fn(),
407+
devServerUrl: undefined,
408+
defaultSidecarUrl: undefined,
409+
mobileReplayOptions: undefined,
410+
});
411+
412+
expect(RNSentry.initNativeSdk).toHaveBeenCalled();
413+
// @ts-expect-error mock value
414+
const initParameter = RNSentry.initNativeSdk.mock.calls[0][0];
415+
expect(initParameter).not.toHaveProperty('beforeSendLog');
416+
expect(NATIVE.enableNative).toBe(true);
417+
});
418+
419+
test('filter beforeSendSpan when initializing Native SDK', async () => {
420+
await NATIVE.initNativeSdk({
421+
dsn: VALID_DSN,
422+
enableNative: true,
423+
autoInitializeNativeSdk: true,
424+
beforeSendSpan: jest.fn(),
425+
devServerUrl: undefined,
426+
defaultSidecarUrl: undefined,
427+
mobileReplayOptions: undefined,
428+
});
429+
430+
expect(RNSentry.initNativeSdk).toHaveBeenCalled();
431+
// @ts-expect-error mock value
432+
const initParameter = RNSentry.initNativeSdk.mock.calls[0][0];
433+
expect(initParameter).not.toHaveProperty('beforeSendSpan');
434+
expect(NATIVE.enableNative).toBe(true);
435+
});
436+
437+
test('filter every function option when initializing Native SDK', async () => {
438+
await NATIVE.initNativeSdk({
439+
dsn: VALID_DSN,
440+
enableNative: true,
441+
autoInitializeNativeSdk: true,
442+
onReady: jest.fn(),
443+
onNativeLog: jest.fn(),
444+
tracesSampler: jest.fn(),
445+
devServerUrl: undefined,
446+
defaultSidecarUrl: undefined,
447+
mobileReplayOptions: undefined,
448+
});
449+
450+
expect(RNSentry.initNativeSdk).toHaveBeenCalled();
451+
// @ts-expect-error mock value
452+
const initParameter = RNSentry.initNativeSdk.mock.calls[0][0];
453+
expect(Object.values(initParameter).every(value => typeof value !== 'function')).toBe(true);
454+
expect(NATIVE.enableNative).toBe(true);
455+
});
456+
401457
test('passes sdkVersion to native SDK', async () => {
402458
await NATIVE.initNativeSdk({
403459
dsn: VALID_DSN,

0 commit comments

Comments
 (0)