Skip to content

Commit

Permalink
Merge branch 'main' into fix/native-contexts-propagation
Browse files Browse the repository at this point in the history
  • Loading branch information
vaind authored Nov 25, 2024
2 parents ca374dd + 7f97e6c commit 068659d
Show file tree
Hide file tree
Showing 33 changed files with 3,969 additions and 3,969 deletions.
3 changes: 2 additions & 1 deletion .github/file-filters.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ high_risk_code: &high_risk_code
- "flutter/lib/src/integrations/native_app_start_integration.dart"
- "flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt"
- "flutter/ios/Classes/SentryFlutterPluginApple.swift"

- "flutter/lib/src/screenshot/recorder.dart"
- "flutter/lib/src/screenshot/widget_filter.dart"
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,46 @@

### Features

- Support for screenshot PII content masking ([#2361](https://github.com/getsentry/sentry-dart/pull/2361))
By default, masking is enabled for SessionReplay. To also enable it for screenshots captured with events, you can specify `options.experimental.privacy`:
```dart
await SentryFlutter.init(
(options) {
...
// the defaults are:
options.experimental.privacy.maskAllText = true;
options.experimental.privacy.maskAllImages = true;
options.experimental.privacy.maskAssetImages = false;
// you cal also set up custom masking, for example:
options.experimental.privacy.mask<WebView>();
},
appRunner: () => runApp(MyApp()),
);
```
Actually, just accessing this field will cause it to be initialized with the default settings to mask all text and images:
```dart
await SentryFlutter.init(
(options) {
...
// this has a side-effect of creating the default privacy configuration, thus enabling Screenshot masking:
options.experimental.privacy;
},
appRunner: () => runApp(MyApp()),
);
```
- Linux native error & obfuscation support ([#2431](https://github.com/getsentry/sentry-dart/pull/2431))
- Improve Device context on plain Dart and Flutter desktop apps ([#2441](https://github.com/getsentry/sentry-dart/pull/2441))

### Fixes

- OS & device contexts missing on Windows ([#2439](https://github.com/getsentry/sentry-dart/pull/2439))

### Dependencies

- Bump Cocoa SDK from v8.40.1 to v8.41.0 ([#2442](https://github.com/getsentry/sentry-dart/pull/2442))
- [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#8410)
- [diff](https://github.com/getsentry/sentry-cocoa/compare/8.40.1...8.41.0)

## 8.11.0-beta.1

### Features
Expand Down
8 changes: 6 additions & 2 deletions flutter/example/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,11 @@ android {

externalNativeBuild {
cmake {
arguments.add(0, "-DANDROID_STL=c++_static")
arguments.addAll([
"-DANDROID_STL=c++_static",
// https://developer.android.com/guide/practices/page-sizes#compile-r27
"-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
])
}
}

Expand All @@ -66,7 +70,7 @@ android {
}
}

ndkVersion "25.1.8937393"
ndkVersion "27.2.12479018"

externalNativeBuild {
cmake {
Expand Down
5 changes: 4 additions & 1 deletion flutter/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ Future<void> setupSentry(
options.sendDefaultPii = true;
options.reportSilentFlutterErrors = true;
options.attachScreenshot = true;
options.screenshotQuality = SentryScreenshotQuality.low;
options.attachViewHierarchy = true;
// We can enable Sentry debug logging during development. This is likely
// going to log too much for your app, but can be useful when figuring out
Expand All @@ -92,6 +91,10 @@ Future<void> setupSentry(
options.experimental.replay.sessionSampleRate = 1.0;
options.experimental.replay.onErrorSampleRate = 1.0;

// This has a side-effect of creating the default privacy configuration,
// thus enabling Screenshot masking. No need to actually change it.
options.experimental.privacy;

_isIntegrationTest = isIntegrationTest;
if (_isIntegrationTest) {
options.dist = '1';
Expand Down
2 changes: 1 addition & 1 deletion flutter/ios/sentry_flutter.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Sentry SDK for Flutter with support to native through sentry-cocoa.
:tag => s.version.to_s }
s.source_files = 'Classes/**/*'
s.public_header_files = 'Classes/**/*.h'
s.dependency 'Sentry/HybridSDK', '8.40.1'
s.dependency 'Sentry/HybridSDK', '8.41.0'
s.ios.dependency 'Flutter'
s.osx.dependency 'FlutterMacOS'
s.ios.deployment_target = '12.0'
Expand Down
3 changes: 2 additions & 1 deletion flutter/lib/sentry_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ export 'src/navigation/sentry_navigator_observer.dart';
export 'src/sentry_flutter.dart';
export 'src/sentry_flutter_options.dart';
export 'src/sentry_replay_options.dart';
export 'src/sentry_privacy_options.dart';
export 'src/flutter_sentry_attachment.dart';
export 'src/sentry_asset_bundle.dart' show SentryAssetBundle;
export 'src/integrations/on_error_integration.dart';
export 'src/replay/masking_config.dart' show SentryMaskingDecision;
export 'src/screenshot/masking_config.dart' show SentryMaskingDecision;
export 'src/screenshot/sentry_mask_widget.dart';
export 'src/screenshot/sentry_unmask_widget.dart';
export 'src/screenshot/sentry_screenshot_widget.dart';
Expand Down
111 changes: 37 additions & 74 deletions flutter/lib/src/event_processor/screenshot_event_processor.dart
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
import 'dart:async';
import 'dart:math';
import 'dart:typed_data';
import 'dart:ui';

import 'package:flutter/rendering.dart';
import 'package:meta/meta.dart';
import 'package:sentry/sentry.dart';
import '../screenshot/sentry_screenshot_widget.dart';
import '../sentry_flutter_options.dart';
import '../../sentry_flutter.dart';
import '../renderer/renderer.dart';
import '../screenshot/recorder.dart';
import '../screenshot/recorder_config.dart';
import 'package:flutter/widgets.dart' as widget;

class ScreenshotEventProcessor implements EventProcessor {
final SentryFlutterOptions _options;

ScreenshotEventProcessor(this._options);
late final ScreenshotRecorder _recorder;

ScreenshotEventProcessor(this._options) {
final targetResolution = _options.screenshotQuality.targetResolution();

_recorder = ScreenshotRecorder(
ScreenshotRecorderConfig(
width: targetResolution,
height: targetResolution,
),
_options,
isReplayRecorder: false,
);
}

@override
Future<SentryEvent?> apply(SentryEvent event, Hint hint) async {
Expand Down Expand Up @@ -77,84 +88,36 @@ class ScreenshotEventProcessor implements EventProcessor {
return event;
}

final bytes = await createScreenshot();
if (bytes != null) {
hint.screenshot = SentryAttachment.fromScreenshotData(bytes);
Uint8List? screenshotData = await createScreenshot();

if (screenshotData != null) {
hint.screenshot = SentryAttachment.fromScreenshotData(screenshotData);
}

return event;
}

@internal
Future<Uint8List?> createScreenshot() async {
try {
final renderObject =
sentryScreenshotWidgetGlobalKey.currentContext?.findRenderObject();
if (renderObject is RenderRepaintBoundary) {
// ignore: deprecated_member_use
final pixelRatio = window.devicePixelRatio;
var imageResult = _getImage(renderObject, pixelRatio);
Image image;
if (imageResult is Future<Image>) {
image = await imageResult;
} else {
image = imageResult;
}
// At the time of writing there's no other image format available which
// Sentry understands.
Uint8List? screenshotData;

if (image.width == 0 || image.height == 0) {
_options.logger(SentryLevel.debug,
'View\'s width and height is zeroed, not taking screenshot.');
return null;
}

final targetResolution = _options.screenshotQuality.targetResolution();
if (targetResolution != null) {
var ratioWidth = targetResolution / image.width;
var ratioHeight = targetResolution / image.height;
var ratio = min(ratioWidth, ratioHeight);
if (ratio > 0.0 && ratio < 1.0) {
imageResult = _getImage(renderObject, ratio * pixelRatio);
if (imageResult is Future<Image>) {
image = await imageResult;
} else {
image = imageResult;
}
}
}
final byteData = await image.toByteData(format: ImageByteFormat.png);
await _recorder.capture((Image image) async {
screenshotData = await _convertImageToUint8List(image);
});

final bytes = byteData?.buffer.asUint8List();
if (bytes?.isNotEmpty == true) {
return bytes;
} else {
_options.logger(SentryLevel.debug,
'Screenshot is 0 bytes, not attaching the image.');
return null;
}
}
} catch (exception, stackTrace) {
_options.logger(
SentryLevel.error,
'Taking screenshot failed.',
exception: exception,
stackTrace: stackTrace,
);
if (_options.automatedTestMode) {
rethrow;
}
}
return null;
return screenshotData;
}

FutureOr<Image> _getImage(
RenderRepaintBoundary repaintBoundary, double pixelRatio) {
// This one is a hack to use https://api.flutter.dev/flutter/rendering/RenderRepaintBoundary/toImage.html on versions older than 3.7 and https://api.flutter.dev/flutter/rendering/RenderRepaintBoundary/toImageSync.html on versions equal or newer than 3.7
try {
return (repaintBoundary as dynamic).toImageSync(pixelRatio: pixelRatio)
as Image;
} on NoSuchMethodError catch (_) {
return repaintBoundary.toImage(pixelRatio: pixelRatio);
Future<Uint8List?> _convertImageToUint8List(Image image) async {
final byteData = await image.toByteData(format: ImageByteFormat.png);

final bytes = byteData?.buffer.asUint8List();
if (bytes?.isNotEmpty == true) {
return bytes;
} else {
_options.logger(
SentryLevel.debug, 'Screenshot is 0 bytes, not attaching the image.');
return null;
}
}
}
2 changes: 1 addition & 1 deletion flutter/lib/src/integrations/screenshot_integration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import '../event_processor/screenshot_event_processor.dart';
import '../sentry_flutter_options.dart';

/// Adds [ScreenshotEventProcessor] to options event processors if
/// [SentryFlutterOptions.attachScreenshot] is true
/// [SentryFlutterOptions.screenshot.attach] is true
class ScreenshotIntegration implements Integration<SentryFlutterOptions> {
SentryFlutterOptions? _options;
ScreenshotEventProcessor? _screenshotEventProcessor;
Expand Down
19 changes: 18 additions & 1 deletion flutter/lib/src/native/c/sentry_native.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:ffi';
import 'dart:io';
import 'dart:typed_data';

import 'package:collection/collection.dart';
import 'package:ffi/ffi.dart';
import 'package:meta/meta.dart';

Expand Down Expand Up @@ -31,7 +32,7 @@ class SentryNative with SentryNativeSafeInvoker implements SentryNativeBinding {
static String dynamicLibraryDirectory = '';

@visibleForTesting
static String? crashpadPath;
static String? crashpadPath = _getDefaultCrashpadPath();

SentryNative(this.options);

Expand Down Expand Up @@ -396,3 +397,19 @@ extension on List<dynamic> {
return cObject;
}
}

String? _getDefaultCrashpadPath() {
if (Platform.isLinux) {
final lastSeparator =
Platform.resolvedExecutable.lastIndexOf(Platform.pathSeparator);
if (lastSeparator >= 0) {
final appDir = Platform.resolvedExecutable.substring(0, lastSeparator);
final candidates = [
'$appDir${Platform.pathSeparator}crashpad_handler',
'$appDir${Platform.pathSeparator}bin/crashpad_handler'
];
return candidates.firstWhereOrNull((path) => File(path).existsSync());
}
}
return null;
}
Loading

0 comments on commit 068659d

Please sign in to comment.