From f056db1dfac48edcdd700a8efbfdb64b6e8e39c9 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Mon, 2 Sep 2024 18:13:01 +0200 Subject: [PATCH 1/4] refactor: Remove workaround for Spotlight image handling (#2253) --- dart/lib/src/transport/spotlight_http_transport.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/dart/lib/src/transport/spotlight_http_transport.dart b/dart/lib/src/transport/spotlight_http_transport.dart index 7567039d82..1889bb7339 100644 --- a/dart/lib/src/transport/spotlight_http_transport.dart +++ b/dart/lib/src/transport/spotlight_http_transport.dart @@ -38,10 +38,6 @@ class SpotlightHttpTransport extends Transport { Future _sendToSpotlight(SentryEnvelope envelope) async { envelope.header.sentAt = _options.clock(); - // Screenshots do not work currently https://github.com/getsentry/spotlight/issues/274 - envelope.items - .removeWhere((element) => element.header.contentType == 'image/png'); - final spotlightRequest = await _requestHandler.createRequest(envelope); final response = await _options.httpClient From 0210372fac176a538e9f73a48b48083e1a2df9ae Mon Sep 17 00:00:00 2001 From: Ivan Dlugos <6349682+vaind@users.noreply.github.com> Date: Mon, 2 Sep 2024 23:47:22 +0200 Subject: [PATCH 2/4] fix: capture replay call on iOS (#2264) --- flutter/ios/Classes/SentryFlutterPluginApple.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/ios/Classes/SentryFlutterPluginApple.swift b/flutter/ios/Classes/SentryFlutterPluginApple.swift index 3e436c88bb..24e50cc166 100644 --- a/flutter/ios/Classes/SentryFlutterPluginApple.swift +++ b/flutter/ios/Classes/SentryFlutterPluginApple.swift @@ -182,7 +182,7 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin { case "nativeCrash": crash() - case "sendReplayForEvent": + case "captureReplay": #if canImport(UIKit) && !SENTRY_NO_UIKIT && (os(iOS) || os(tvOS)) PrivateSentrySDKOnly.captureReplay() result(PrivateSentrySDKOnly.getReplayId()) From 7b2e0ad54ee7a20e1ca24a40ee897000e52b5ae3 Mon Sep 17 00:00:00 2001 From: Martin Haintz Date: Tue, 3 Sep 2024 11:35:19 +0200 Subject: [PATCH 3/4] Support allowUrls, denyUrls (#2227) * moved regex matcher into regex utils * add allowUrls, denyUrls for web * add changelog entry for allowUrls and denyUrls * add conditional import for non web platforms * fix multiplatform build * fix wording in sentry options * Update dart/lib/src/utils/regex_utils.dart Co-authored-by: Giancarlo Buenaflor * Update dart/lib/src/sentry_options.dart Co-authored-by: Giancarlo Buenaflor * Update dart/lib/src/sentry_options.dart Co-authored-by: Giancarlo Buenaflor * add tests for isMatchingRegexPattern * simplified allowUrls and denyUrls handling * moved allowUrls and denyUrls from dart to flutter * add event processor for html * rephrased documentation and split up tests for web and mobile platform. * add expected error * Update scripts/publish_validation/bin/publish_validation.dart Co-authored-by: Giancarlo Buenaflor * Update flutter/lib/src/event_processor/url_filter/html_url_filter_event_processor.dart Co-authored-by: Giancarlo Buenaflor * Update flutter/lib/src/event_processor/url_filter/web_url_filter_event_processor.dart Co-authored-by: Giancarlo Buenaflor --------- Co-authored-by: Giancarlo Buenaflor --- CHANGELOG.md | 14 ++ dart/lib/src/sentry_client.dart | 12 +- dart/lib/src/sentry_options.dart | 2 + dart/lib/src/utils/regex_utils.dart | 9 + dart/test/utils/regex_utils_test.dart | 24 +++ .../html_url_filter_event_processor.dart | 54 ++++++ .../io_url_filter_event_processor.dart | 10 + .../url_filter_event_processor.dart | 9 + .../web_url_filter_event_processor.dart | 56 ++++++ flutter/lib/src/sentry_flutter.dart | 2 + flutter/lib/src/sentry_flutter_options.dart | 15 ++ .../io_filter_event_processor_test.dart | 39 ++++ .../web_url_filter_event_processor_test.dart | 179 ++++++++++++++++++ .../bin/publish_validation.dart | 3 +- 14 files changed, 418 insertions(+), 10 deletions(-) create mode 100644 dart/lib/src/utils/regex_utils.dart create mode 100644 dart/test/utils/regex_utils_test.dart create mode 100644 flutter/lib/src/event_processor/url_filter/html_url_filter_event_processor.dart create mode 100644 flutter/lib/src/event_processor/url_filter/io_url_filter_event_processor.dart create mode 100644 flutter/lib/src/event_processor/url_filter/url_filter_event_processor.dart create mode 100644 flutter/lib/src/event_processor/url_filter/web_url_filter_event_processor.dart create mode 100644 flutter/test/event_processor/url_filter/io_filter_event_processor_test.dart create mode 100644 flutter/test/event_processor/url_filter/web_url_filter_event_processor_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index a486e6eb1a..666ed2bbd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,19 @@ ### Features +- Support allowUrls and denyUrls for Flutter Web ([#2227](https://github.com/getsentry/sentry-dart/pull/2227)) + + ```dart + await SentryFlutter.init( + (options) { + ... + options.allowUrls = ["^https://sentry.com.*\$", "my-custom-domain"]; + options.denyUrls = ["^.*ends-with-this\$", "denied-url"]; + }, + appRunner: () => runApp(MyApp()), + ); + ``` + - Session replay Alpha for Android and iOS ([#2208](https://github.com/getsentry/sentry-dart/pull/2208)). To try out replay, you can set following options (access is limited to early access orgs on Sentry. If you're interested, [sign up for the waitlist](https://sentry.io/lp/mobile-replay-beta/)): @@ -31,6 +44,7 @@ - Add `SentryFlutter.nativeCrash()` using MethodChannels for Android and iOS ([#2239](https://github.com/getsentry/sentry-dart/pull/2239)) - This can be used to test if native crash reporting works + - Add `ignoreRoutes` parameter to `SentryNavigatorObserver`. ([#2218](https://github.com/getsentry/sentry-dart/pull/2218)) - This will ignore the Routes and prevent the Route from being pushed to the Sentry server. - Ignored routes will also create no TTID and TTFD spans. diff --git a/dart/lib/src/sentry_client.dart b/dart/lib/src/sentry_client.dart index ba557aad2e..b66c0d25b5 100644 --- a/dart/lib/src/sentry_client.dart +++ b/dart/lib/src/sentry_client.dart @@ -26,6 +26,7 @@ import 'transport/rate_limiter.dart'; import 'transport/spotlight_http_transport.dart'; import 'transport/task_queue.dart'; import 'utils/isolate_utils.dart'; +import 'utils/regex_utils.dart'; import 'utils/stacktrace_utils.dart'; import 'version.dart'; @@ -196,7 +197,7 @@ class SentryClient { } var message = event.message!.formatted; - return _isMatchingRegexPattern(message, _options.ignoreErrors); + return isMatchingRegexPattern(message, _options.ignoreErrors); } SentryEvent _prepareEvent(SentryEvent event, {dynamic stackTrace}) { @@ -415,7 +416,7 @@ class SentryClient { } var name = transaction.tracer.name; - return _isMatchingRegexPattern(name, _options.ignoreTransactions); + return isMatchingRegexPattern(name, _options.ignoreTransactions); } /// Reports the [envelope] to Sentry.io. @@ -593,11 +594,4 @@ class SentryClient { SentryId.empty(), ); } - - bool _isMatchingRegexPattern(String value, List regexPattern, - {bool caseSensitive = false}) { - final combinedRegexPattern = regexPattern.join('|'); - final regExp = RegExp(combinedRegexPattern, caseSensitive: caseSensitive); - return regExp.hasMatch(value); - } } diff --git a/dart/lib/src/sentry_options.dart b/dart/lib/src/sentry_options.dart index 2b1771a2b5..5cfbd0fdc7 100644 --- a/dart/lib/src/sentry_options.dart +++ b/dart/lib/src/sentry_options.dart @@ -186,10 +186,12 @@ class SentryOptions { /// The ignoreErrors tells the SDK which errors should be not sent to the sentry server. /// If an null or an empty list is used, the SDK will send all transactions. + /// To use regex add the `^` and the `$` to the string. List ignoreErrors = []; /// The ignoreTransactions tells the SDK which transactions should be not sent to the sentry server. /// If null or an empty list is used, the SDK will send all transactions. + /// To use regex add the `^` and the `$` to the string. List ignoreTransactions = []; final List _inAppExcludes = []; diff --git a/dart/lib/src/utils/regex_utils.dart b/dart/lib/src/utils/regex_utils.dart new file mode 100644 index 0000000000..ba64f7504e --- /dev/null +++ b/dart/lib/src/utils/regex_utils.dart @@ -0,0 +1,9 @@ +import 'package:meta/meta.dart'; + +@internal +bool isMatchingRegexPattern(String value, List regexPattern, + {bool caseSensitive = false}) { + final combinedRegexPattern = regexPattern.join('|'); + final regExp = RegExp(combinedRegexPattern, caseSensitive: caseSensitive); + return regExp.hasMatch(value); +} diff --git a/dart/test/utils/regex_utils_test.dart b/dart/test/utils/regex_utils_test.dart new file mode 100644 index 0000000000..ff098ab964 --- /dev/null +++ b/dart/test/utils/regex_utils_test.dart @@ -0,0 +1,24 @@ +import 'package:sentry/src/utils/regex_utils.dart'; +import 'package:test/test.dart'; + +void main() { + group('regex_utils', () { + final testString = "this is a test"; + + test('testString contains string pattern', () { + expect(isMatchingRegexPattern(testString, ["is"]), isTrue); + }); + + test('testString does not contain string pattern', () { + expect(isMatchingRegexPattern(testString, ["not"]), isFalse); + }); + + test('testString contains regex pattern', () { + expect(isMatchingRegexPattern(testString, ["^this.*\$"]), isTrue); + }); + + test('testString does not contain regex pattern', () { + expect(isMatchingRegexPattern(testString, ["^is.*\$"]), isFalse); + }); + }); +} diff --git a/flutter/lib/src/event_processor/url_filter/html_url_filter_event_processor.dart b/flutter/lib/src/event_processor/url_filter/html_url_filter_event_processor.dart new file mode 100644 index 0000000000..7792f6a333 --- /dev/null +++ b/flutter/lib/src/event_processor/url_filter/html_url_filter_event_processor.dart @@ -0,0 +1,54 @@ +import 'dart:html' as html show window, Window; + +import '../../../sentry_flutter.dart'; +import 'url_filter_event_processor.dart'; +// ignore: implementation_imports +import 'package:sentry/src/utils/regex_utils.dart'; + +// ignore_for_file: invalid_use_of_internal_member + +UrlFilterEventProcessor urlFilterEventProcessor(SentryFlutterOptions options) => + WebUrlFilterEventProcessor(options); + +class WebUrlFilterEventProcessor implements UrlFilterEventProcessor { + WebUrlFilterEventProcessor( + this._options, + ); + + final SentryFlutterOptions _options; + + @override + SentryEvent? apply(SentryEvent event, Hint hint) { + final frames = _getStacktraceFrames(event); + final lastPath = frames?.first?.absPath; + + if (lastPath == null) { + return event; + } + + if (_options.allowUrls.isNotEmpty && + !isMatchingRegexPattern(lastPath, _options.allowUrls)) { + return null; + } + + if (_options.denyUrls.isNotEmpty && + isMatchingRegexPattern(lastPath, _options.denyUrls)) { + return null; + } + + return event; + } + + Iterable? _getStacktraceFrames(SentryEvent event) { + if (event.exceptions?.isNotEmpty == true) { + return event.exceptions?.first.stackTrace?.frames; + } + if (event.threads?.isNotEmpty == true) { + final stacktraces = event.threads?.map((e) => e.stacktrace); + return stacktraces + ?.where((element) => element != null) + .expand((element) => element!.frames); + } + return null; + } +} diff --git a/flutter/lib/src/event_processor/url_filter/io_url_filter_event_processor.dart b/flutter/lib/src/event_processor/url_filter/io_url_filter_event_processor.dart new file mode 100644 index 0000000000..b49573bbc5 --- /dev/null +++ b/flutter/lib/src/event_processor/url_filter/io_url_filter_event_processor.dart @@ -0,0 +1,10 @@ +import '../../../sentry_flutter.dart'; +import 'url_filter_event_processor.dart'; + +UrlFilterEventProcessor urlFilterEventProcessor(SentryFlutterOptions _) => + IoUrlFilterEventProcessor(); + +class IoUrlFilterEventProcessor implements UrlFilterEventProcessor { + @override + SentryEvent apply(SentryEvent event, Hint hint) => event; +} diff --git a/flutter/lib/src/event_processor/url_filter/url_filter_event_processor.dart b/flutter/lib/src/event_processor/url_filter/url_filter_event_processor.dart new file mode 100644 index 0000000000..5a1e5ed537 --- /dev/null +++ b/flutter/lib/src/event_processor/url_filter/url_filter_event_processor.dart @@ -0,0 +1,9 @@ +import '../../../sentry_flutter.dart'; +import 'io_url_filter_event_processor.dart' + if (dart.library.html) 'html_url_filter_event_processor.dart' + if (dart.library.js_interop) 'web_url_filter_event_processor.dart'; + +abstract class UrlFilterEventProcessor implements EventProcessor { + factory UrlFilterEventProcessor(SentryFlutterOptions options) => + urlFilterEventProcessor(options); +} diff --git a/flutter/lib/src/event_processor/url_filter/web_url_filter_event_processor.dart b/flutter/lib/src/event_processor/url_filter/web_url_filter_event_processor.dart new file mode 100644 index 0000000000..10cfee3478 --- /dev/null +++ b/flutter/lib/src/event_processor/url_filter/web_url_filter_event_processor.dart @@ -0,0 +1,56 @@ +// We would lose compatibility with old dart versions by adding web to pubspec. +// ignore: depend_on_referenced_packages +import 'package:web/web.dart' as web show window, Window; + +import '../../../sentry_flutter.dart'; +import 'url_filter_event_processor.dart'; +// ignore: implementation_imports +import 'package:sentry/src/utils/regex_utils.dart'; + +// ignore_for_file: invalid_use_of_internal_member + +UrlFilterEventProcessor urlFilterEventProcessor(SentryFlutterOptions options) => + WebUrlFilterEventProcessor(options); + +class WebUrlFilterEventProcessor implements UrlFilterEventProcessor { + WebUrlFilterEventProcessor( + this._options, + ); + + final SentryFlutterOptions _options; + + @override + SentryEvent? apply(SentryEvent event, Hint hint) { + final frames = _getStacktraceFrames(event); + final lastPath = frames?.first?.absPath; + + if (lastPath == null) { + return event; + } + + if (_options.allowUrls.isNotEmpty && + !isMatchingRegexPattern(lastPath, _options.allowUrls)) { + return null; + } + + if (_options.denyUrls.isNotEmpty && + isMatchingRegexPattern(lastPath, _options.denyUrls)) { + return null; + } + + return event; + } + + Iterable? _getStacktraceFrames(SentryEvent event) { + if (event.exceptions?.isNotEmpty == true) { + return event.exceptions?.first.stackTrace?.frames; + } + if (event.threads?.isNotEmpty == true) { + final stacktraces = event.threads?.map((e) => e.stacktrace); + return stacktraces + ?.where((element) => element != null) + .expand((element) => element!.frames); + } + return null; + } +} diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index 3ff835284c..29f533d082 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -9,6 +9,7 @@ import 'event_processor/android_platform_exception_event_processor.dart'; import 'event_processor/flutter_enricher_event_processor.dart'; import 'event_processor/flutter_exception_event_processor.dart'; import 'event_processor/platform_exception_event_processor.dart'; +import 'event_processor/url_filter/url_filter_event_processor.dart'; import 'event_processor/widget_event_processor.dart'; import 'file_system_transport.dart'; import 'flutter_exception_type_identifier.dart'; @@ -131,6 +132,7 @@ mixin SentryFlutter { options.addEventProcessor(FlutterEnricherEventProcessor(options)); options.addEventProcessor(WidgetEventProcessor()); + options.addEventProcessor(UrlFilterEventProcessor(options)); if (options.platformChecker.platform.isAndroid) { options.addEventProcessor( diff --git a/flutter/lib/src/sentry_flutter_options.dart b/flutter/lib/src/sentry_flutter_options.dart index 8b6f7ed491..308ed805b0 100644 --- a/flutter/lib/src/sentry_flutter_options.dart +++ b/flutter/lib/src/sentry_flutter_options.dart @@ -146,6 +146,21 @@ class SentryFlutterOptions extends SentryOptions { /// See https://api.flutter.dev/flutter/foundation/FlutterErrorDetails/silent.html bool reportSilentFlutterErrors = false; + /// (Web only) Events only occurring on these Urls will be handled and sent to sentry. + /// If an empty list is used, the SDK will send all errors. + /// `allowUrls` uses regex for the matching. + /// + /// If used on a platform other than Web, this setting will be ignored. + List allowUrls = []; + + /// (Web only) Events occurring on these Urls will be ignored and are not sent to sentry. + /// If an empty list is used, the SDK will send all errors. + /// `denyUrls` uses regex for the matching. + /// In combination with `allowUrls` you can block subdomains of the domains listed in `allowUrls`. + /// + /// If used on a platform other than Web, this setting will be ignored. + List denyUrls = []; + /// Enables Out of Memory Tracking for iOS and macCatalyst. /// See the following link for more information and possible restrictions: /// https://docs.sentry.io/platforms/apple/guides/ios/configuration/out-of-memory/ diff --git a/flutter/test/event_processor/url_filter/io_filter_event_processor_test.dart b/flutter/test/event_processor/url_filter/io_filter_event_processor_test.dart new file mode 100644 index 0000000000..22708b95bb --- /dev/null +++ b/flutter/test/event_processor/url_filter/io_filter_event_processor_test.dart @@ -0,0 +1,39 @@ +@TestOn('vm') +library flutter_test; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:sentry_flutter/src/event_processor/url_filter/url_filter_event_processor.dart'; + +void main() { + group("ignore allowUrls and denyUrls for non Web", () { + late Fixture fixture; + + setUp(() async { + fixture = Fixture(); + }); + + test('returns the event and ignore allowUrls and denyUrls for non Web', + () async { + SentryEvent? event = SentryEvent( + request: SentryRequest( + url: 'another.url/for/a/special/test/testing/this-feature', + ), + ); + fixture.options.allowUrls = ["^this.is/.*\$"]; + fixture.options.denyUrls = ["special"]; + + var eventProcessor = fixture.getSut(); + event = await eventProcessor.apply(event, Hint()); + + expect(event, isNotNull); + }); + }); +} + +class Fixture { + SentryFlutterOptions options = SentryFlutterOptions(); + UrlFilterEventProcessor getSut() { + return UrlFilterEventProcessor(options); + } +} diff --git a/flutter/test/event_processor/url_filter/web_url_filter_event_processor_test.dart b/flutter/test/event_processor/url_filter/web_url_filter_event_processor_test.dart new file mode 100644 index 0000000000..f16f5c5003 --- /dev/null +++ b/flutter/test/event_processor/url_filter/web_url_filter_event_processor_test.dart @@ -0,0 +1,179 @@ +@TestOn('browser') +library flutter_test; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:sentry_flutter/src/event_processor/url_filter/url_filter_event_processor.dart'; + +// can be tested on command line with +// `flutter test --platform=chrome test/event_processor/url_filter/web_url_filter_event_processor_test.dart` +void main() { + group(UrlFilterEventProcessor, () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('returns event if no allowUrl and no denyUrl is set', () async { + SentryEvent? event = SentryEvent( + request: SentryRequest( + url: 'foo.bar', + ), + ); + + var eventProcessor = fixture.getSut(); + event = await eventProcessor.apply(event, Hint()); + + expect(event, isNotNull); + }); + + test('returns null if allowUrl is set and does not match with url', + () async { + final event = _createEventWithException("foo.bar"); + fixture.options.allowUrls = ["another.url"]; + + var eventProcessor = fixture.getSut(); + final processedEvent = await eventProcessor.apply(event, Hint()); + + expect(processedEvent, isNull); + }); + + test('returns event if allowUrl is set and does partially match with url', + () async { + final event = _createEventWithException("foo.bar"); + fixture.options.allowUrls = ["bar"]; + + var eventProcessor = fixture.getSut(); + final processedEvent = await eventProcessor.apply(event, Hint()); + + expect(processedEvent, isNotNull); + }); + + test('returns event if denyUrl is set and does not match with url', + () async { + final event = _createEventWithException("foo.bar"); + fixture.options.denyUrls = ["another.url"]; + + var eventProcessor = fixture.getSut(); + final processedEvent = await eventProcessor.apply(event, Hint()); + + expect(processedEvent, isNotNull); + }); + + test('returns null if denyUrl is set and partially matches with url', + () async { + final event = _createEventWithException("foo.bar"); + fixture.options.denyUrls = ["bar"]; + + var eventProcessor = fixture.getSut(); + final processedEvent = await eventProcessor.apply(event, Hint()); + + expect(processedEvent, isNull); + }); + + test( + 'returns null if it is part of the allowed domain, but blocked for subdomain', + () async { + final event = _createEventWithException( + "this.is/a/special/url/for-testing/this-feature"); + + fixture.options.allowUrls = ["^this.is/.*\$"]; + fixture.options.denyUrls = ["special"]; + + var eventProcessor = fixture.getSut(); + final processedEvent = await eventProcessor.apply(event, Hint()); + + expect(processedEvent, isNull); + }); + + test( + 'returns event if it is part of the allowed domain, and not of the blocked for subdomain', + () async { + final event = _createEventWithException( + "this.is/a/test/url/for-testing/this-feature"); + fixture.options.allowUrls = ["^this.is/.*\$"]; + fixture.options.denyUrls = ["special"]; + + var eventProcessor = fixture.getSut(); + final processedEvent = await eventProcessor.apply(event, Hint()); + + expect(processedEvent, isNotNull); + }); + + test( + 'returns null if it is not part of the allowed domain, and not of the blocked for subdomain', + () async { + final event = _createEventWithException( + "another.url/for/a/test/testing/this-feature"); + fixture.options.allowUrls = ["^this.is/.*\$"]; + fixture.options.denyUrls = ["special"]; + + var eventProcessor = fixture.getSut(); + final processedEvent = await eventProcessor.apply(event, Hint()); + + expect(processedEvent, isNull); + }); + + test( + 'returns event if denyUrl is set and not matching with url of first exception', + () async { + final frame1 = SentryStackFrame(absPath: "test.url"); + final st1 = SentryStackTrace(frames: [frame1]); + final exception1 = SentryException( + type: "test-type", value: "test-value", stackTrace: st1); + + final frame2 = SentryStackFrame(absPath: "foo.bar"); + final st2 = SentryStackTrace(frames: [frame2]); + final exception2 = SentryException( + type: "test-type", value: "test-value", stackTrace: st2); + + SentryEvent event = SentryEvent(exceptions: [exception1, exception2]); + + fixture.options.denyUrls = ["bar"]; + + var eventProcessor = fixture.getSut(); + final processedEvent = await eventProcessor.apply(event, Hint()); + + expect(processedEvent, isNotNull); + }); + + test( + 'returns event if denyUrl is set and not matching with url of first stacktraceframe', + () async { + final frame1 = SentryStackFrame(absPath: "test.url"); + final st1 = SentryStackTrace(frames: [frame1]); + final thread1 = SentryThread(stacktrace: st1); + + final frame2 = SentryStackFrame(absPath: "foo.bar"); + final st2 = SentryStackTrace(frames: [frame2]); + final thread2 = SentryThread(stacktrace: st2); + + SentryEvent event = SentryEvent(threads: [thread1, thread2]); + + fixture.options.denyUrls = ["bar"]; + + var eventProcessor = fixture.getSut(); + final processedEvent = await eventProcessor.apply(event, Hint()); + + expect(processedEvent, isNotNull); + }); + }); +} + +class Fixture { + SentryFlutterOptions options = SentryFlutterOptions(); + UrlFilterEventProcessor getSut() { + return UrlFilterEventProcessor(options); + } +} + +SentryEvent _createEventWithException(String url) { + final frame = SentryStackFrame(absPath: url); + final st = SentryStackTrace(frames: [frame]); + final exception = + SentryException(type: "test-type", value: "test-value", stackTrace: st); + SentryEvent event = SentryEvent(exceptions: [exception]); + + return event; +} diff --git a/scripts/publish_validation/bin/publish_validation.dart b/scripts/publish_validation/bin/publish_validation.dart index ab871e910e..0585d7dd00 100644 --- a/scripts/publish_validation/bin/publish_validation.dart +++ b/scripts/publish_validation/bin/publish_validation.dart @@ -34,7 +34,8 @@ void main(List arguments) async { 'lib/src/integrations/connectivity/web_connectivity_provider.dart: This package does not have web in the `dependencies` section of `pubspec.yaml`', 'lib/src/event_processor/enricher/web_enricher_event_processor.dart: This package does not have web in the `dependencies` section of `pubspec.yaml`', 'lib/src/origin_web.dart: This package does not have web in the `dependencies` section of `pubspec.yaml`', - 'lib/src/platform/_web_platform.dart: This package does not have web in the `dependencies` section of `pubspec.yaml`' + 'lib/src/platform/_web_platform.dart: This package does not have web in the `dependencies` section of `pubspec.yaml`', + 'lib/src/event_processor/url_filter/web_url_filter_event_processor.dart: This package does not have web in the `dependencies` section of `pubspec.yaml`', ]; // So far the expected errors all start with `* line` From 3a1617909872133e3cbb31acbb811bc3239c9806 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Andra=C5=A1ec?= Date: Tue, 3 Sep 2024 12:40:21 +0200 Subject: [PATCH 4/4] Only access renderObject if `hasSize` is true (#2263) --- CHANGELOG.md | 4 ++++ flutter/lib/src/view_hierarchy/sentry_tree_walker.dart | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 666ed2bbd6..b915e3dcf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,10 @@ - [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#8360) - [diff](https://github.com/getsentry/sentry-cocoa/compare/8.35.1...8.36.0) +### Fixes + +- Only access renderObject if `hasSize` is true ([#2263](https://github.com/getsentry/sentry-dart/pull/2263)) + ## 8.8.0 ### Features diff --git a/flutter/lib/src/view_hierarchy/sentry_tree_walker.dart b/flutter/lib/src/view_hierarchy/sentry_tree_walker.dart index b2e759b2c9..578792d18a 100644 --- a/flutter/lib/src/view_hierarchy/sentry_tree_walker.dart +++ b/flutter/lib/src/view_hierarchy/sentry_tree_walker.dart @@ -255,7 +255,7 @@ class _TreeWalker { double? alpha; final renderObject = element.renderObject; - if (renderObject is RenderBox) { + if (renderObject is RenderBox && renderObject.hasSize) { final offset = renderObject.localToGlobal(Offset.zero); if (offset.dx > 0) { x = offset.dx;