Skip to content
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
- Do not leak extensions of external classes ([#1576](https://github.com/getsentry/sentry-dart/pull/1576))
- Make `hint` non-nullable in `BeforeSendCallback`, `BeforeBreadcrumbCall` and `EventProcessor` ([#1574](https://github.com/getsentry/sentry-dart/pull/1574))
- This will affect your callbacks, making this a breaking change.

- Load Device Contexts from Sentry Java ([#1616](https://github.com/getsentry/sentry-dart/pull/1616))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • 🚫 The changelog entry seems to be part of an already released section ## 8.0.0.
    Consider moving the entry to the ## Unreleased section, please.

- Now the device context from Android is available in `BeforeSendCallback`

## Unreleased

### Fixes
Expand Down
38 changes: 0 additions & 38 deletions dart/lib/src/sentry_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,6 @@ class SentryClient {
return _sentryId;
}

preparedEvent = _eventWithoutBreadcrumbsIfNeeded(preparedEvent);

var attachments = List<SentryAttachment>.from(scope?.attachments ?? []);
attachments.addAll(hint.attachments);
var screenshot = hint.screenshot;
Expand Down Expand Up @@ -322,8 +320,6 @@ class SentryClient {
return _sentryId;
}

preparedTransaction = _eventWithoutBreadcrumbsIfNeeded(preparedTransaction);

final attachments = scope?.attachments
.where((element) => element.addToTransactions)
.toList();
Expand Down Expand Up @@ -457,40 +453,6 @@ class SentryClient {
_options.recorder.recordLostEvent(reason, category);
}

T _eventWithoutBreadcrumbsIfNeeded<T extends SentryEvent>(T event) {
if (_shouldRemoveBreadcrumbs(event)) {
return event.copyWith(breadcrumbs: []) as T;
} else {
return event;
}
}

/// We do this to avoid duplicate breadcrumbs on Android as sentry-android applies the breadcrumbs
/// from the native scope onto every envelope sent through it. This scope will contain the breadcrumbs
/// sent through the scope sync feature. This causes duplicate breadcrumbs.
/// We then remove the breadcrumbs in all cases but if it is handled == false,
/// this is a signal that the app would crash and android would lose the breadcrumbs by the time the app is restarted to read
/// the envelope.
bool _shouldRemoveBreadcrumbs(SentryEvent event) {
if (_options.platformChecker.isWeb) {
return false;
}

final isAndroid = _options.platformChecker.platform.isAndroid;
final enableScopeSync = _options.enableScopeSync;

if (!isAndroid || !enableScopeSync) {
return false;
}

final mechanisms =
(event.exceptions ?? []).map((e) => e.mechanism).whereType<Mechanism>();
final hasNoMechanism = mechanisms.isEmpty;
final hasOnlyHandledMechanism =
mechanisms.every((e) => (e.handled ?? true));
return hasNoMechanism || hasOnlyHandledMechanism;
}

Future<SentryId?> _attachClientReportsAndSend(SentryEnvelope envelope) {
final clientReport = _options.recorder.flush();
envelope.addClientReport(clientReport);
Expand Down
195 changes: 0 additions & 195 deletions dart/test/sentry_client_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1153,201 +1153,6 @@ void main() {
});
});

group('Breadcrumbs', () {
late Fixture fixture;

setUp(() {
fixture = Fixture();
});

test('Clears breadcrumbs on Android for transaction', () async {
fixture.options.enableScopeSync = true;
fixture.options.platformChecker =
MockPlatformChecker(platform: MockPlatform.android());

final client = fixture.getSut();
final transaction = SentryTransaction(
fixture.tracer,
breadcrumbs: [
Breadcrumb(),
],
);
await client.captureTransaction(transaction);

final capturedEnvelope = (fixture.transport).envelopes.first;
final capturedTransaction =
await transactionFromEnvelope(capturedEnvelope);

expect((capturedTransaction['breadcrumbs'] ?? []).isEmpty, true);
});

test('Clears breadcrumbs on Android if mechanism.handled is true for event',
() async {
fixture.options.enableScopeSync = true;
fixture.options.platformChecker =
MockPlatformChecker(platform: MockPlatform.android());

final client = fixture.getSut();
final event = SentryEvent(exceptions: [
SentryException(
type: "type",
value: "value",
mechanism: Mechanism(
type: 'type',
handled: true,
),
)
], breadcrumbs: [
Breadcrumb()
]);
await client.captureEvent(event);

final capturedEnvelope = (fixture.transport).envelopes.first;
final capturedEvent = await eventFromEnvelope(capturedEnvelope);

expect((capturedEvent.breadcrumbs ?? []).isEmpty, true);
});

test('Clears breadcrumbs on Android if mechanism.handled is null for event',
() async {
fixture.options.enableScopeSync = true;
fixture.options.platformChecker =
MockPlatformChecker(platform: MockPlatform.android());

final client = fixture.getSut();
final event = SentryEvent(exceptions: [
SentryException(
type: "type",
value: "value",
mechanism: Mechanism(type: 'type'),
)
], breadcrumbs: [
Breadcrumb()
]);
await client.captureEvent(event);

final capturedEnvelope = (fixture.transport).envelopes.first;
final capturedEvent = await eventFromEnvelope(capturedEnvelope);

expect((capturedEvent.breadcrumbs ?? []).isEmpty, true);
});

test('Clears breadcrumbs on Android if theres no mechanism for event',
() async {
fixture.options.enableScopeSync = true;
fixture.options.platformChecker =
MockPlatformChecker(platform: MockPlatform.android());

final client = fixture.getSut();
final event = SentryEvent(exceptions: [
SentryException(
type: "type",
value: "value",
)
], breadcrumbs: [
Breadcrumb()
]);
await client.captureEvent(event);

final capturedEnvelope = (fixture.transport).envelopes.first;
final capturedEvent = await eventFromEnvelope(capturedEnvelope);

expect((capturedEvent.breadcrumbs ?? []).isEmpty, true);
});

test(
'Does not clear breadcrumbs on Android if mechanism.handled is false for event',
() async {
fixture.options.enableScopeSync = true;
fixture.options.platformChecker =
MockPlatformChecker(platform: MockPlatform.android());

final client = fixture.getSut();
final event = SentryEvent(exceptions: [
SentryException(
type: "type",
value: "value",
mechanism: Mechanism(
type: 'type',
handled: false,
),
)
], breadcrumbs: [
Breadcrumb()
]);
await client.captureEvent(event);

final capturedEnvelope = (fixture.transport).envelopes.first;
final capturedEvent = await eventFromEnvelope(capturedEnvelope);

expect((capturedEvent.breadcrumbs ?? []).isNotEmpty, true);
});

test(
'Does not clear breadcrumbs on Android if any mechanism.handled is false for event',
() async {
fixture.options.enableScopeSync = true;
fixture.options.platformChecker =
MockPlatformChecker(platform: MockPlatform.android());

final client = fixture.getSut();
final event = SentryEvent(exceptions: [
SentryException(
type: "type",
value: "value",
mechanism: Mechanism(
type: 'type',
handled: true,
),
),
SentryException(
type: "type",
value: "value",
mechanism: Mechanism(
type: 'type',
handled: false,
),
)
], breadcrumbs: [
Breadcrumb()
]);
await client.captureEvent(event);

final capturedEnvelope = (fixture.transport).envelopes.first;
final capturedEvent = await eventFromEnvelope(capturedEnvelope);

expect((capturedEvent.breadcrumbs ?? []).isNotEmpty, true);
});

test('web breadcrumbs exist on web Android devices', () async {
fixture.options.enableScopeSync = true;
fixture.options.platformChecker = MockPlatformChecker(
platform: MockPlatform.android(),
isWebValue: true,
);

final client = fixture.getSut();
final event = SentryEvent(exceptions: [
SentryException(
type: "type",
value: "value",
mechanism: Mechanism(
type: 'type',
handled: true,
),
),
], breadcrumbs: [
Breadcrumb(),
]);
await client.captureEvent(event);

final capturedEnvelope = (fixture.transport).envelopes.first;
final capturedEvent = await eventFromEnvelope(capturedEnvelope);

expect((capturedEvent.breadcrumbs ?? []).isNotEmpty, true);
});
});

group('ClientReportRecorder', () {
late Fixture fixture;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import io.sentry.SentryOptions
import io.sentry.android.core.ActivityFramesTracker
import io.sentry.android.core.AppStartState
import io.sentry.android.core.BuildConfig.VERSION_NAME
import io.sentry.android.core.InternalSentrySdk
import io.sentry.android.core.LoadClass
import io.sentry.android.core.SentryAndroid
import io.sentry.android.core.SentryAndroidOptions
Expand Down Expand Up @@ -65,6 +66,7 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
"removeExtra" -> removeExtra(call.argument("key"), result)
"setTag" -> setTag(call.argument("key"), call.argument("value"), result)
"removeTag" -> removeTag(call.argument("key"), result)
"loadContexts" -> loadContexts(result)
else -> result.notImplemented()
}
}
Expand Down Expand Up @@ -94,18 +96,6 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
// Stub
}

private fun writeEnvelope(envelope: ByteArray): Boolean {
val options = HubAdapter.getInstance().options
if (options.outboxPath.isNullOrEmpty()) {
return false
}

val file = File(options.outboxPath, UUID.randomUUID().toString())
file.writeBytes(envelope)

return true
}

private fun initNativeSdk(call: MethodCall, result: Result) {
if (!this::context.isInitialized) {
result.error("1", "Context is null", null)
Expand Down Expand Up @@ -356,20 +346,19 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
result.error("1", "The Sentry Android SDK is disabled", null)
return
}

val args = call.arguments() as List<Any>? ?: listOf<Any>()
val args = call.arguments() as List<Any>? ?: listOf()
if (args.isNotEmpty()) {
val event = args.first() as ByteArray?

if (event != null && event.isNotEmpty()) {
if (!writeEnvelope(event)) {
result.error("2", "SentryOptions or outboxPath are null or empty", null)
val id = InternalSentrySdk.captureEnvelope(event)
if (id != null) {
result.success("")
} else {
result.error("2", "Failed to capture envelope", null)
}
result.success("")
return
}
}

result.error("3", "Envelope is null or empty", null)
}

Expand Down Expand Up @@ -454,6 +443,21 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
}
}
}

private fun loadContexts(result: Result) {
val options = HubAdapter.getInstance().options
if (options !is SentryAndroidOptions) {
result.success(null)
return
}
val currentScope = InternalSentrySdk.getCurrentScope()
val serializedScope = InternalSentrySdk.serializeScope(
context,
options,
currentScope
)
result.success(serializedScope)
}
}

// Call the `completion` closure if cast to map value with `key` and type `T` is successful.
Expand Down
17 changes: 6 additions & 11 deletions flutter/lib/src/integrations/load_contexts_integration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,16 @@ import 'package:flutter/services.dart';
import 'package:sentry/sentry.dart';
import '../sentry_flutter_options.dart';

/// Load Device's Contexts from the iOS SDK.
/// Load Device's Contexts from the iOS & Android SDKs.
///
/// This integration calls the iOS SDK via Message channel to load the
/// Device's contexts before sending the event back to the iOS SDK via
/// This integration calls the iOS & Android SDKs via Message channel to load
/// the Device's contexts before sending the event back to the SDK via
/// Message channel (already enriched with all the information).
///
/// The Device's contexts are:
/// App, Device and OS.
///
/// ps. This integration won't be run on Android because the Device's Contexts
/// is set on Android when the event is sent to the Android SDK via
/// the Message channel.
/// We intend to unify this behaviour in the future.
///
/// This integration is only executed on iOS & MacOS Apps.
/// This integration is only executed on iOS, macOS & Android Apps.
class LoadContextsIntegration extends Integration<SentryFlutterOptions> {
final MethodChannel _channel;

Expand Down Expand Up @@ -194,8 +189,8 @@ class _LoadContextsIntegrationEventProcessor implements EventProcessor {
event = event.copyWith(sdk: sdk);
}

// on iOS, captureEnvelope does not call the beforeSend callback,
// hence we need to add these tags here.
// captureEnvelope does not call the beforeSend callback, hence we need to
// add these tags here.
if (event.sdk?.name == 'sentry.dart.flutter') {
final tags = event.tags ?? {};
tags['event.origin'] = 'flutter';
Expand Down
Loading