Skip to content
Merged
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
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@
### Changed

- (plugin-network-instrumentation) Manually parse URLs to improve React Native compatibility [#2674](https://github.com/bugsnag/bugsnag-js/pull/2674)
- Update bugsnag-android to [v6.22.0](https//github.com/bugsnag/bugsnag-android/releases/tag/v6.22.0) [#2656](https://github.com/bugsnag/bugsnag-js/pull/2656)
- Update bugsnag-cocoa to [v6.35.0](https//github.com/bugsnag/bugsnag-cocoa/releases/tag/v6.35.0) [#2663](https://github.com/bugsnag/bugsnag-js/pull/2663)

### Fixed

- (plugin-network-instrumentation) Report HTTP Errors as handled [#2662](https://github.com/bugsnag/bugsnag-js/pull/2662)
- (plugin-network-instrumentation) Omit stacktraces from HTTP Error events [#2684](https://github.com/bugsnag/bugsnag-js/pull/2684)
- (react-native) Report error cause with native stacktrace for Turbo Module exceptions [#2686] (https://github.com/bugsnag/bugsnag-js/pull/2686)

### Dependencies

- Update bugsnag-cocoa to [v6.35.0](https//github.com/bugsnag/bugsnag-cocoa/releases/tag/v6.35.0) [#2663](https://github.com/bugsnag/bugsnag-js/pull/2663)
- Update bugsnag-android to [v6.22.0](https//github.com/bugsnag/bugsnag-android/releases/tag/v6.22.0) [#2656](https://github.com/bugsnag/bugsnag-js/pull/2656)
- Update bugsnag-android to [v6.23.0](https//github.com/bugsnag/bugsnag-android/releases/tag/v6.23.0) [#2673](https://github.com/bugsnag/bugsnag-js/pull/2673)

## [8.8.1] - 2026-01-26
Expand All @@ -26,6 +27,7 @@
## [8.8.0] - 2026-01-20

This release adds support for notitfying failed network requests using the new `plugin-network-instrumentation` package

### Added

- (plugin-network-instrumentation) Add new plugin to notify failed network requests [#2647](https://github.com/bugsnag/bugsnag-js/pull/2647)
Expand Down
26 changes: 24 additions & 2 deletions packages/delivery-react-native/delivery.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,35 @@ const derecursify = require('@bugsnag/core/lib/derecursify')
module.exports = (client, NativeClient) => ({
sendEvent: (payload, cb = () => {}) => {
const event = payload.events[0]
let nativeStack

if (event.originalError) {
// extract native stacktrace from originalError if available
let nativeErrorMessage, nativeStack
if (event.originalError.nativeStackIOS) {
// old arch ios
nativeErrorMessage = event.originalError.message
nativeStack = event.originalError.nativeStackIOS
} else if (event.originalError.nativeStackAndroid) {
// old arch android
nativeErrorMessage = event.originalError.message
nativeStack = event.originalError.nativeStackAndroid
} else if (event.originalError.cause && event.originalError.cause.stackSymbols) {
// new arch ios
nativeErrorMessage = event.originalError.cause.message
nativeStack = event.originalError.cause.stackSymbols
} else if (event.originalError.cause && event.originalError.cause.stackElements) {
// new arch android
nativeErrorMessage = event.originalError.cause.message
nativeStack = event.originalError.cause.stackElements
}

if (nativeErrorMessage && nativeStack) {
// add the native stack to the corresponding error in the event payload
const nativeError = event.errors.find(err => err.errorMessage === nativeErrorMessage)

if (nativeError) {
nativeError.nativeStack = nativeStack
}
}
}

Expand All @@ -30,7 +53,6 @@ module.exports = (client, NativeClient) => ({
groupingDiscriminator: event._groupingDiscriminator,
apiKey: event.apiKey,
featureFlags: event.toJSON().featureFlags,
nativeStack: nativeStack,
correlation: event._correlation
}

Expand Down
102 changes: 100 additions & 2 deletions packages/delivery-react-native/test/delivery.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,17 @@ type NativeClientEvent = Pick<EventWithInternals,
nativeStack: NativeStackIOS | NativeStackAndroid
}

interface ReactNativeErrorCause {
name: string
message: string
stackSymbols?: NativeStackIOS
stackElements?: NativeStackAndroid
}

class ReactNativeError extends Error {
nativeStackAndroid?: NativeStackAndroid
nativeStackIOS?: NativeStackIOS
cause?: ReactNativeErrorCause
}

describe('delivery: react native', () => {
Expand Down Expand Up @@ -111,7 +119,8 @@ describe('delivery: react native', () => {
]
c.notify(error, (e) => {}, (err, event) => {
expect(err).not.toBeTruthy()
expect(sent[0].nativeStack).toEqual(error.nativeStackIOS)
// @ts-expect-error nativeStack is added by the delivery module
expect(sent[0].errors[0].nativeStack).toEqual(error.nativeStackIOS)
done()
})
})
Expand All @@ -137,7 +146,96 @@ describe('delivery: react native', () => {
]
c.notify(error, (e) => {}, (err, event) => {
expect(err).not.toBeTruthy()
expect(sent[0].nativeStack).toBe(error.nativeStackAndroid)
// @ts-expect-error nativeStack is added by the delivery module
expect(sent[0].errors[0].nativeStack).toBe(error.nativeStackAndroid)
done()
})
})

it('extracts iOS native error cause', done => {
const sent: NativeClientEvent[] = []
const NativeClient = {
dispatchAsync: (event: NativeClientEvent) => {
sent.push(event)
return new Promise((resolve) => resolve(true))
}
}
const c = new Client({ apiKey: 'api_key' })
c._setDelivery(client => delivery(client, NativeClient))
const error = new ReactNativeError('oh no')
const stackSymbols = [
'0 ReactNativeTest 0x000000010fda7f1b RCTJSErrorFromCodeMessageAndNSError + 79',
'1 ReactNativeTest 0x000000010fd76897 __41-[RCTModuleMethod processMethodSignature]_block_invoke_2.103 + 97',
'2 ReactNativeTest 0x000000010fccd9c3 -[BenCrash asyncReject:rejecter:] + 106',
'3 CoreFoundation 0x00007fff23e44dec __invoking___ + 140',
'4 CoreFoundation 0x00007fff23e41fd1 -[NSInvocation invoke] + 321',
'5 CoreFoundation 0x00007fff23e422a4 -[NSInvocation invokeWithTarget:] + 68',
'6 ReactNativeTest 0x000000010fd76eae -[RCTModuleMethod invokeWithBridge:module:arguments:] + 578',
'7 ReactNativeTest 0x000000010fd79138 _ZN8facebook5reactL11invokeInnerEP9RCTBridgeP13RCTModuleDatajRKN5folly7dynamicE + 246'
]

error.cause = {
name: 'NativeiOSException',
message: 'Native iOS error occurred',
stackSymbols
}

c.notify(error, (e) => {}, (err, event) => {
expect(err).not.toBeTruthy()
expect(sent[0].errors[0].errorMessage).toEqual(error.message)
// @ts-expect-error nativeStack is added by the delivery module
expect(sent[0].errors[0].nativeStack).toBeUndefined()

expect(sent[0].errors[1].errorMessage).toBeDefined()
expect(sent[0].errors[1].errorMessage).toEqual(error.cause?.message)
expect(sent[0].errors[1].errorClass).toBeDefined()
expect(sent[0].errors[1].errorClass).toEqual(error.cause?.name)

// @ts-expect-error nativeStack is added by the delivery module
expect(sent[0].errors[1].nativeStack).toEqual(error.cause.stackSymbols)
done()
})
})

it('extracts Android native error cause', done => {
const sent: NativeClientEvent[] = []
const NativeClient = {
dispatchAsync: (event: NativeClientEvent) => {
sent.push(event)
return new Promise((resolve) => resolve(true))
}
}
const c = new Client({ apiKey: 'api_key' })
c._setDelivery(client => delivery(client, NativeClient))
const error = new ReactNativeError('oh no')
const stackElements = [
{
class: 'com.testing.Blah',
lineNumber: 101,
file: 'app/com.testing.Blah.java',
methodName: 'crash()'
}
]

error.cause = {
name: 'NativeAndroidException',
message: 'Native Android error occurred',
stackElements
}

c.notify(error, (e) => {}, (err, event) => {
expect(err).not.toBeTruthy()
expect(sent[0].errors[0].errorMessage).toEqual(error.message)
// @ts-expect-error nativeStack is added by the delivery module
expect(sent[0].errors[0].nativeStack).toBeUndefined()

expect(sent[0].errors[1].errorMessage).toBeDefined()
expect(sent[0].errors[1].errorMessage).toEqual(error.cause?.message)
expect(sent[0].errors[1].errorClass).toBeDefined()
expect(sent[0].errors[1].errorClass).toEqual(error.cause?.name)

// @ts-expect-error nativeStack is added by the delivery module
expect(sent[0].errors[1].nativeStack).toEqual(error.cause.stackElements)
done()
})
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#import "BugsnagEventDeserializer.h"

#import "BugsnagInternals.h"
#import <Bugsnag/BugsnagStacktrace.h>

@implementation BugsnagEventDeserializer

Expand All @@ -25,7 +26,7 @@ - (BugsnagEvent *)deserializeEvent:(NSDictionary *)payload {
user:[[BugsnagUser alloc] initWithDictionary:user]
metadata:metadata
breadcrumbs:[self deserializeBreadcrumbs:payload[@"breadcrumbs"]]
errors:@[[BugsnagError new]]
errors:[self deserializeErrors:payload[@"errors"]]
threads:[self deserializeThreads:payload[@"threads"]]
session:nil /* set by -[BugsnagClient notifyInternal:block:] */];
event.context = payload[@"context"];
Expand All @@ -49,13 +50,17 @@ - (BugsnagEvent *)deserializeEvent:(NSDictionary *)payload {
}
}

NSDictionary *error = payload[@"errors"][0];
return event;
}

if (error != nil) {
event.errors[0].errorClass = error[@"errorClass"];
event.errors[0].errorMessage = error[@"errorMessage"];
- (NSArray<BugsnagError *> *)deserializeErrors:(NSArray *)errors {
NSMutableArray *array = [NSMutableArray new];
for (NSDictionary *error in errors) {
BugsnagError *errorObj = [BugsnagError new];
errorObj.errorClass = error[@"errorClass"];
errorObj.errorMessage = error[@"errorMessage"];
NSArray<NSDictionary *> *stacktrace = error[@"stacktrace"];
NSArray<NSString *> *nativeStack = payload[@"nativeStack"];
NSArray<NSString *> *nativeStack = error[@"nativeStack"];
if (nativeStack) {
NSMutableArray<NSDictionary *> *mixedStack = [NSMutableArray array];
for (BugsnagStackframe *frame in [BugsnagStackframe stackframesWithCallStackSymbols:nativeStack]) {
Expand All @@ -66,10 +71,13 @@ - (BugsnagEvent *)deserializeEvent:(NSDictionary *)payload {
[mixedStack addObjectsFromArray:stacktrace];
stacktrace = mixedStack;
}
[event attachCustomStacktrace:stacktrace withType:@"reactnativejs"];

errorObj.stacktrace = [BugsnagStacktrace stacktraceFromJson:stacktrace].trace;
errorObj.type = BSGErrorTypeReactNativeJs;
[array addObject:errorObj];
}

return event;
return array;
}

- (NSArray *)deserializeBreadcrumbs:(NSArray *)crumbs {
Expand Down
Loading