diff --git a/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java b/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java index 5a24e7d54c..23750666dc 100644 --- a/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java +++ b/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java @@ -694,11 +694,16 @@ public void captureEnvelope(String rawBytes, ReadableMap options, Promise promis try { InternalSentrySdk.captureEnvelope( bytes, !options.hasKey("hardCrashed") || !options.getBoolean("hardCrashed")); + + // TODO(alwx): Capture transport response from sentry-android when available + WritableMap response = new WritableNativeMap(); + promise.resolve(response); } catch (Throwable e) { // NOPMD - We don't want to crash in any case - logger.log(SentryLevel.ERROR, "Error while capturing envelope"); - promise.resolve(false); + logger.log(SentryLevel.ERROR, "Error while capturing envelope", e); + WritableMap errorResponse = new WritableNativeMap(); + errorResponse.putString("message", "Error while capturing envelope: " + e.getMessage()); + promise.resolve(errorResponse); } - promise.resolve(true); } public void captureScreenshot(Promise promise) { diff --git a/packages/core/ios/RNSentry.mm b/packages/core/ios/RNSentry.mm index f5fefc0cd6..b2b596c315 100644 --- a/packages/core/ios/RNSentry.mm +++ b/packages/core/ios/RNSentry.mm @@ -555,7 +555,9 @@ - (void)stopObserving [PrivateSentrySDKOnly captureEnvelope:envelope]; } #endif - resolve(@YES); + + // TODO(alwx): Capture transport response from sentry-cocoa when available + resolve(@ {}); } RCT_EXPORT_METHOD( diff --git a/packages/core/src/js/NativeRNSentry.ts b/packages/core/src/js/NativeRNSentry.ts index 3e75967c27..eece3c4643 100644 --- a/packages/core/src/js/NativeRNSentry.ts +++ b/packages/core/src/js/NativeRNSentry.ts @@ -15,7 +15,7 @@ export interface Spec extends TurboModule { options: { hardCrashed: boolean; }, - ): Promise; + ): Promise<{ status?: string; message?: string }>; captureScreenshot(): Promise; clearBreadcrumbs(): void; crash(): void; diff --git a/packages/core/src/js/wrapper.ts b/packages/core/src/js/wrapper.ts index 4089bc5c75..df7d7e77e2 100644 --- a/packages/core/src/js/wrapper.ts +++ b/packages/core/src/js/wrapper.ts @@ -218,7 +218,26 @@ export const NATIVE: SentryNativeWrapper = { envelopeBytes = newBytes; } - await RNSentry.captureEnvelope(base64StringFromByteArray(envelopeBytes), { hardCrashed }); + const response = await RNSentry.captureEnvelope(base64StringFromByteArray(envelopeBytes), { hardCrashed }); + + if (response?.status) { + const statusCode = parseInt(response.status, 10); + + // Handle HTTP 413 - Content Too Large + if (statusCode === 413) { + debug.error( + 'Failed to send event to Sentry: HTTP 413 - Envelope exceeds size limit.\n' + + 'The event was rejected because the envelope size exceeds the maximum allowed size.\n' + + 'Consider reducing the size of breadcrumbs, context data, or attachments.', + ); + } + // Log other error status codes + else if (statusCode >= 400) { + debug.error( + `Failed to send event to Sentry: HTTP ${statusCode}${response.message ? ` - ${response.message}` : ''}`, + ); + } + } }, /** diff --git a/packages/core/test/wrapper.test.ts b/packages/core/test/wrapper.test.ts index cc7b79c0d9..012b64a11d 100644 --- a/packages/core/test/wrapper.test.ts +++ b/packages/core/test/wrapper.test.ts @@ -696,6 +696,61 @@ describe('Tests Native Wrapper', () => { { hardCrashed: false }, ); }); + + test('handles HTTP 413 response from native', async () => { + const errorSpy = jest.spyOn(debug, 'error'); + + // Mock native module to return HTTP 413 + (RNSentry.captureEnvelope as jest.Mock).mockResolvedValueOnce({ + status: '413', + message: 'Payload Too Large', + }); + + const event = { + event_id: 'event0', + message: 'test', + }; + + const env = createEnvelope({ event_id: event.event_id, sent_at: '123' }, [ + [{ type: 'event' }, event] as EventItem, + ]); + + await NATIVE.sendEnvelope(env); + + expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('HTTP 413 - Envelope exceeds size limit')); + expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('Consider reducing the size of breadcrumbs')); + + errorSpy.mockRestore(); + // Reset mock to default behavior + (RNSentry.captureEnvelope as jest.Mock).mockResolvedValue({}); + }); + + test('handles other HTTP error responses from native', async () => { + const errorSpy = jest.spyOn(debug, 'error'); + + // Mock native module to return HTTP 400 + (RNSentry.captureEnvelope as jest.Mock).mockResolvedValueOnce({ + status: '400', + message: 'Bad Request', + }); + + const event = { + event_id: 'event0', + message: 'test', + }; + + const env = createEnvelope({ event_id: event.event_id, sent_at: '123' }, [ + [{ type: 'event' }, event] as EventItem, + ]); + + await NATIVE.sendEnvelope(env); + + expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('HTTP 400 - Bad Request')); + + errorSpy.mockRestore(); + // Reset mock to default behavior + (RNSentry.captureEnvelope as jest.Mock).mockResolvedValue({}); + }); }); describe('fetchRelease', () => {