diff --git a/CHANGELOG.md b/CHANGELOG.md index d00aecb6a1..2a6294383b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,9 @@ - Bump Cocoa SDK from v8.29.0 to v8.30.0 ([#2132](https://github.com/getsentry/sentry-dart/pull/2132)) - [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#8300) - [diff](https://github.com/getsentry/sentry-cocoa/compare/8.29.0...8.30.0) +- Bump Android SDK from v7.10.0 to v7.11.0 ([#2144](https://github.com/getsentry/sentry-dart/pull/2144)) + - [changelog](https://github.com/getsentry/sentry-java/blob/main/CHANGELOG.md#7110) + - [diff](https://github.com/getsentry/sentry-java/compare/7.10.0...7.11.0) ## 8.3.0 diff --git a/dart/lib/src/sentry_envelope.dart b/dart/lib/src/sentry_envelope.dart index 7835b7859e..fb7cd1543a 100644 --- a/dart/lib/src/sentry_envelope.dart +++ b/dart/lib/src/sentry_envelope.dart @@ -13,7 +13,8 @@ import 'sentry_user_feedback.dart'; /// Class representation of `Envelope` file. class SentryEnvelope { - SentryEnvelope(this.header, this.items); + SentryEnvelope(this.header, this.items, + {this.containsUnhandledException = false}); /// Header describing envelope content. final SentryEnvelopeHeader header; @@ -21,6 +22,10 @@ class SentryEnvelope { /// All items contained in the envelope. final List items; + /// Whether the envelope contains an unhandled exception. + /// This is used to determine if the native SDK should start a new session. + final bool containsUnhandledException; + /// Create a [SentryEnvelope] containing one [SentryEnvelopeItem] which holds the [SentryEvent] data. factory SentryEnvelope.fromEvent( SentryEvent event, @@ -29,6 +34,15 @@ class SentryEnvelope { SentryTraceContextHeader? traceContext, List? attachments, }) { + bool containsUnhandledException = false; + + if (event.exceptions != null && event.exceptions!.isNotEmpty) { + // Check all exceptions for any unhandled ones + containsUnhandledException = event.exceptions!.any((exception) { + return exception.mechanism?.handled == false; + }); + } + return SentryEnvelope( SentryEnvelopeHeader( event.eventId, @@ -41,6 +55,7 @@ class SentryEnvelope { if (attachments != null) ...attachments.map((e) => SentryEnvelopeItem.fromAttachment(e)) ], + containsUnhandledException: containsUnhandledException, ); } diff --git a/dart/test/mocks/mock_envelope.dart b/dart/test/mocks/mock_envelope.dart index 9b43a41b8a..1009f2e396 100644 --- a/dart/test/mocks/mock_envelope.dart +++ b/dart/test/mocks/mock_envelope.dart @@ -23,4 +23,7 @@ class MockEnvelope implements SentryEnvelope { @override List items = []; + + @override + bool get containsUnhandledException => false; } diff --git a/flutter/android/build.gradle b/flutter/android/build.gradle index 89146e73ff..3242dddd64 100644 --- a/flutter/android/build.gradle +++ b/flutter/android/build.gradle @@ -60,7 +60,7 @@ android { } dependencies { - api 'io.sentry:sentry-android:7.10.0' + api 'io.sentry:sentry-android:7.11.0' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" // Required -- JUnit 4 framework diff --git a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index 26b84a6f5d..4f154a2465 100644 --- a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -355,8 +355,9 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { val args = call.arguments() as List? ?: listOf() if (args.isNotEmpty()) { val event = args.first() as ByteArray? - if (event != null && event.isNotEmpty()) { - val id = InternalSentrySdk.captureEnvelope(event) + val containsUnhandledException = args[1] as Boolean + if (event != null && event.isNotEmpty() && containsUnhandledException != null) { + val id = InternalSentrySdk.captureEnvelope(event, containsUnhandledException) if (id != null) { result.success("") } else { diff --git a/flutter/lib/src/file_system_transport.dart b/flutter/lib/src/file_system_transport.dart index 9b7ace7dee..85cc0947a7 100644 --- a/flutter/lib/src/file_system_transport.dart +++ b/flutter/lib/src/file_system_transport.dart @@ -19,7 +19,8 @@ class FileSystemTransport implements Transport { await envelope.envelopeStream(_options).forEach(envelopeData.addAll); try { // TODO avoid copy - await _native.captureEnvelope(Uint8List.fromList(envelopeData)); + await _native.captureEnvelope(Uint8List.fromList(envelopeData), + envelope.containsUnhandledException); } catch (exception, stackTrace) { _options.logger( SentryLevel.error, diff --git a/flutter/lib/src/native/sentry_native_binding.dart b/flutter/lib/src/native/sentry_native_binding.dart index 07ea5969fe..002790fc32 100644 --- a/flutter/lib/src/native/sentry_native_binding.dart +++ b/flutter/lib/src/native/sentry_native_binding.dart @@ -16,7 +16,8 @@ abstract class SentryNativeBinding { Future fetchNativeAppStart(); - Future captureEnvelope(Uint8List envelopeData); + Future captureEnvelope( + Uint8List envelopeData, bool containsUnhandledException); Future beginNativeFrames(); diff --git a/flutter/lib/src/native/sentry_native_channel.dart b/flutter/lib/src/native/sentry_native_channel.dart index fe00683b0e..0a3b97820d 100644 --- a/flutter/lib/src/native/sentry_native_channel.dart +++ b/flutter/lib/src/native/sentry_native_channel.dart @@ -80,8 +80,11 @@ class SentryNativeChannel } @override - Future captureEnvelope(Uint8List envelopeData) => - _channel.invokeMethod('captureEnvelope', [envelopeData]); + Future captureEnvelope( + Uint8List envelopeData, bool containsUnhandledException) { + return _channel.invokeMethod( + 'captureEnvelope', [envelopeData, containsUnhandledException]); + } @override Future?> loadContexts() => diff --git a/flutter/test/file_system_transport_test.dart b/flutter/test/file_system_transport_test.dart index a658ab9f17..5aa0713183 100644 --- a/flutter/test/file_system_transport_test.dart +++ b/flutter/test/file_system_transport_test.dart @@ -2,6 +2,7 @@ library flutter_test; import 'dart:convert'; + // backcompatibility for Flutter < 3.3 // ignore: unnecessary_import import 'dart:typed_data'; @@ -39,7 +40,7 @@ void main() { }); test('$FileSystemTransport returns emptyId if channel throws', () async { - when(fixture.binding.captureEnvelope(any)).thenThrow(Exception()); + when(fixture.binding.captureEnvelope(any, false)).thenThrow(Exception()); final transport = fixture.getSut(); final event = SentryEvent(); @@ -56,6 +57,58 @@ void main() { expect(SentryId.empty(), sentryId); }); + test( + 'sets unhandled exception flag in captureEnvelope to true for unhandled exception', + () async { + final transport = fixture.getSut(); + + final unhandledException = SentryException( + mechanism: Mechanism(type: 'UnhandledException', handled: false), + threadId: 99, + type: 'Exception', + value: 'Unhandled exception', + ); + final event = SentryEvent(exceptions: [unhandledException]); + final sdkVersion = + SdkVersion(name: 'fixture-sdkName', version: 'fixture-sdkVersion'); + final envelope = SentryEnvelope.fromEvent( + event, + sdkVersion, + dsn: fixture.options.dsn, + ); + + await transport.send(envelope); + + verify(fixture.binding.captureEnvelope(captureAny, true)).captured.single + as Uint8List; + }); + + test( + 'sets unhandled exception flag in captureEnvelope to false for handled exception', + () async { + final transport = fixture.getSut(); + + final unhandledException = SentryException( + mechanism: Mechanism(type: 'UnhandledException', handled: true), + threadId: 99, + type: 'Exception', + value: 'Unhandled exception', + ); + final event = SentryEvent(exceptions: [unhandledException]); + final sdkVersion = + SdkVersion(name: 'fixture-sdkName', version: 'fixture-sdkVersion'); + final envelope = SentryEnvelope.fromEvent( + event, + sdkVersion, + dsn: fixture.options.dsn, + ); + + await transport.send(envelope); + + verify(fixture.binding.captureEnvelope(captureAny, false)).captured.single + as Uint8List; + }); + test('$FileSystemTransport asserts the event', () async { final transport = fixture.getSut(); @@ -70,9 +123,10 @@ void main() { ); await transport.send(envelope); - final envelopeData = verify(fixture.binding.captureEnvelope(captureAny)) - .captured - .single as Uint8List; + final envelopeData = + verify(fixture.binding.captureEnvelope(captureAny, false)) + .captured + .single as Uint8List; final envelopeString = utf8.decode(envelopeData); final lines = envelopeString.split('\n'); final envelopeHeader = lines.first; diff --git a/flutter/test/mocks.mocks.dart b/flutter/test/mocks.mocks.dart index 214b15b588..01d2127efe 100644 --- a/flutter/test/mocks.mocks.dart +++ b/flutter/test/mocks.mocks.dart @@ -1116,11 +1116,17 @@ class MockSentryNativeBinding extends _i1.Mock ) as _i8.Future<_i15.NativeAppStart?>); @override - _i8.Future captureEnvelope(_i16.Uint8List? envelopeData) => + _i8.Future captureEnvelope( + _i16.Uint8List? envelopeData, + bool? containsUnhandledException, + ) => (super.noSuchMethod( Invocation.method( #captureEnvelope, - [envelopeData], + [ + envelopeData, + containsUnhandledException, + ], ), returnValue: _i8.Future.value(), returnValueForMissingStub: _i8.Future.value(), diff --git a/flutter/test/sentry_native_channel_test.dart b/flutter/test/sentry_native_channel_test.dart index cc2e67dfd5..5ad5eb2b3f 100644 --- a/flutter/test/sentry_native_channel_test.dart +++ b/flutter/test/sentry_native_channel_test.dart @@ -244,7 +244,7 @@ void main() { (invocation) async => {captured = invocation.positionalArguments[1][0] as Uint8List}); - await sut.captureEnvelope(data); + await sut.captureEnvelope(data, false); expect(captured, data); });