Skip to content

Commit 51743e2

Browse files
antonisclaude
andcommitted
fix(core): Retry native module resolution to prevent silent event drops
The RNSentry TurboModule reference is resolved once at module load time. In production Hermes bytecode builds, JS execution can start before the TurboModule registry is fully populated, causing getRNSentryModule() to return undefined permanently. This silently disables native transport and drops all events. Re-resolve the native module in isNativeAvailable() if the initial resolution returned undefined. By the time Sentry.init() calls this method, TurboModules are registered and the module resolves correctly. Fixes #5508 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 51fbf68 commit 51743e2

2 files changed

Lines changed: 38 additions & 1 deletion

File tree

packages/core/src/js/wrapper.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export function getRNSentryModule(): Spec | undefined {
4545
: NativeModules.RNSentry;
4646
}
4747

48-
const RNSentry: Spec | undefined = getRNSentryModule();
48+
let RNSentry: Spec | undefined = getRNSentryModule();
4949

5050
export interface Screenshot {
5151
data: Uint8Array;
@@ -649,6 +649,9 @@ export const NATIVE: SentryNativeWrapper = {
649649
},
650650

651651
isNativeAvailable(): boolean {
652+
if (!RNSentry) {
653+
RNSentry = getRNSentryModule();
654+
}
652655
return this._isModuleLoaded(RNSentry);
653656
},
654657

packages/core/test/wrapper.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,4 +1208,38 @@ describe('Tests Native Wrapper', () => {
12081208
});
12091209
});
12101210
});
1211+
1212+
describe('isNativeAvailable', () => {
1213+
test('retries module resolution if initially undefined', () => {
1214+
// Simulate the race condition: RNSentry was undefined at module load time
1215+
// but becomes available when isNativeAvailable() is called during init()
1216+
let mockModule: Spec | undefined = undefined;
1217+
1218+
jest.resetModules();
1219+
jest.doMock('react-native', () => ({
1220+
NativeModules: {
1221+
get RNSentry() {
1222+
return mockModule;
1223+
},
1224+
},
1225+
Platform: { OS: 'ios' },
1226+
}));
1227+
// Ensure TurboModules path is not used so NativeModules.RNSentry is checked
1228+
jest.doMock('../src/js/utils/environment', () => ({
1229+
isTurboModuleEnabled: () => false,
1230+
}));
1231+
1232+
// eslint-disable-next-line @typescript-eslint/no-var-requires
1233+
const { NATIVE: isolatedNATIVE } = require('../src/js/wrapper');
1234+
1235+
// Initially unavailable (simulates race condition)
1236+
expect(isolatedNATIVE.isNativeAvailable()).toBe(false);
1237+
1238+
// Native module becomes available (TurboModule registered)
1239+
mockModule = RNSentry;
1240+
1241+
// isNativeAvailable retries and finds it
1242+
expect(isolatedNATIVE.isNativeAvailable()).toBe(true);
1243+
});
1244+
});
12111245
});

0 commit comments

Comments
 (0)