Skip to content

Commit 7d14655

Browse files
authored
Merge branch 'main' into fix/flutter-multiview-support
2 parents 2886956 + 3a16179 commit 7d14655

17 files changed

+424
-16
lines changed

CHANGELOG.md

+18
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,19 @@
44

55
### Features
66

7+
- Support allowUrls and denyUrls for Flutter Web ([#2227](https://github.com/getsentry/sentry-dart/pull/2227))
8+
9+
```dart
10+
await SentryFlutter.init(
11+
(options) {
12+
...
13+
options.allowUrls = ["^https://sentry.com.*\$", "my-custom-domain"];
14+
options.denyUrls = ["^.*ends-with-this\$", "denied-url"];
15+
},
16+
appRunner: () => runApp(MyApp()),
17+
);
18+
```
19+
720
- Session replay Alpha for Android and iOS ([#2208](https://github.com/getsentry/sentry-dart/pull/2208)).
821

922
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/)):
@@ -25,12 +38,17 @@
2538
- [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#8360)
2639
- [diff](https://github.com/getsentry/sentry-cocoa/compare/8.35.1...8.36.0)
2740

41+
### Fixes
42+
43+
- Only access renderObject if `hasSize` is true ([#2263](https://github.com/getsentry/sentry-dart/pull/2263))
44+
2845
## 8.8.0
2946

3047
### Features
3148

3249
- Add `SentryFlutter.nativeCrash()` using MethodChannels for Android and iOS ([#2239](https://github.com/getsentry/sentry-dart/pull/2239))
3350
- This can be used to test if native crash reporting works
51+
3452
- Add `ignoreRoutes` parameter to `SentryNavigatorObserver`. ([#2218](https://github.com/getsentry/sentry-dart/pull/2218))
3553
- This will ignore the Routes and prevent the Route from being pushed to the Sentry server.
3654
- Ignored routes will also create no TTID and TTFD spans.

dart/lib/src/sentry_client.dart

+3-9
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import 'transport/rate_limiter.dart';
2626
import 'transport/spotlight_http_transport.dart';
2727
import 'transport/task_queue.dart';
2828
import 'utils/isolate_utils.dart';
29+
import 'utils/regex_utils.dart';
2930
import 'utils/stacktrace_utils.dart';
3031
import 'version.dart';
3132

@@ -196,7 +197,7 @@ class SentryClient {
196197
}
197198

198199
var message = event.message!.formatted;
199-
return _isMatchingRegexPattern(message, _options.ignoreErrors);
200+
return isMatchingRegexPattern(message, _options.ignoreErrors);
200201
}
201202

202203
SentryEvent _prepareEvent(SentryEvent event, {dynamic stackTrace}) {
@@ -415,7 +416,7 @@ class SentryClient {
415416
}
416417

417418
var name = transaction.tracer.name;
418-
return _isMatchingRegexPattern(name, _options.ignoreTransactions);
419+
return isMatchingRegexPattern(name, _options.ignoreTransactions);
419420
}
420421

421422
/// Reports the [envelope] to Sentry.io.
@@ -593,11 +594,4 @@ class SentryClient {
593594
SentryId.empty(),
594595
);
595596
}
596-
597-
bool _isMatchingRegexPattern(String value, List<String> regexPattern,
598-
{bool caseSensitive = false}) {
599-
final combinedRegexPattern = regexPattern.join('|');
600-
final regExp = RegExp(combinedRegexPattern, caseSensitive: caseSensitive);
601-
return regExp.hasMatch(value);
602-
}
603597
}

dart/lib/src/sentry_options.dart

+2
Original file line numberDiff line numberDiff line change
@@ -186,10 +186,12 @@ class SentryOptions {
186186

187187
/// The ignoreErrors tells the SDK which errors should be not sent to the sentry server.
188188
/// If an null or an empty list is used, the SDK will send all transactions.
189+
/// To use regex add the `^` and the `$` to the string.
189190
List<String> ignoreErrors = [];
190191

191192
/// The ignoreTransactions tells the SDK which transactions should be not sent to the sentry server.
192193
/// If null or an empty list is used, the SDK will send all transactions.
194+
/// To use regex add the `^` and the `$` to the string.
193195
List<String> ignoreTransactions = [];
194196

195197
final List<String> _inAppExcludes = [];

dart/lib/src/transport/spotlight_http_transport.dart

-4
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,6 @@ class SpotlightHttpTransport extends Transport {
3838
Future<void> _sendToSpotlight(SentryEnvelope envelope) async {
3939
envelope.header.sentAt = _options.clock();
4040

41-
// Screenshots do not work currently https://github.com/getsentry/spotlight/issues/274
42-
envelope.items
43-
.removeWhere((element) => element.header.contentType == 'image/png');
44-
4541
final spotlightRequest = await _requestHandler.createRequest(envelope);
4642

4743
final response = await _options.httpClient

dart/lib/src/utils/regex_utils.dart

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import 'package:meta/meta.dart';
2+
3+
@internal
4+
bool isMatchingRegexPattern(String value, List<String> regexPattern,
5+
{bool caseSensitive = false}) {
6+
final combinedRegexPattern = regexPattern.join('|');
7+
final regExp = RegExp(combinedRegexPattern, caseSensitive: caseSensitive);
8+
return regExp.hasMatch(value);
9+
}

dart/test/utils/regex_utils_test.dart

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import 'package:sentry/src/utils/regex_utils.dart';
2+
import 'package:test/test.dart';
3+
4+
void main() {
5+
group('regex_utils', () {
6+
final testString = "this is a test";
7+
8+
test('testString contains string pattern', () {
9+
expect(isMatchingRegexPattern(testString, ["is"]), isTrue);
10+
});
11+
12+
test('testString does not contain string pattern', () {
13+
expect(isMatchingRegexPattern(testString, ["not"]), isFalse);
14+
});
15+
16+
test('testString contains regex pattern', () {
17+
expect(isMatchingRegexPattern(testString, ["^this.*\$"]), isTrue);
18+
});
19+
20+
test('testString does not contain regex pattern', () {
21+
expect(isMatchingRegexPattern(testString, ["^is.*\$"]), isFalse);
22+
});
23+
});
24+
}

flutter/ios/Classes/SentryFlutterPluginApple.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin {
182182
case "nativeCrash":
183183
crash()
184184

185-
case "sendReplayForEvent":
185+
case "captureReplay":
186186
#if canImport(UIKit) && !SENTRY_NO_UIKIT && (os(iOS) || os(tvOS))
187187
PrivateSentrySDKOnly.captureReplay()
188188
result(PrivateSentrySDKOnly.getReplayId())
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import 'dart:html' as html show window, Window;
2+
3+
import '../../../sentry_flutter.dart';
4+
import 'url_filter_event_processor.dart';
5+
// ignore: implementation_imports
6+
import 'package:sentry/src/utils/regex_utils.dart';
7+
8+
// ignore_for_file: invalid_use_of_internal_member
9+
10+
UrlFilterEventProcessor urlFilterEventProcessor(SentryFlutterOptions options) =>
11+
WebUrlFilterEventProcessor(options);
12+
13+
class WebUrlFilterEventProcessor implements UrlFilterEventProcessor {
14+
WebUrlFilterEventProcessor(
15+
this._options,
16+
);
17+
18+
final SentryFlutterOptions _options;
19+
20+
@override
21+
SentryEvent? apply(SentryEvent event, Hint hint) {
22+
final frames = _getStacktraceFrames(event);
23+
final lastPath = frames?.first?.absPath;
24+
25+
if (lastPath == null) {
26+
return event;
27+
}
28+
29+
if (_options.allowUrls.isNotEmpty &&
30+
!isMatchingRegexPattern(lastPath, _options.allowUrls)) {
31+
return null;
32+
}
33+
34+
if (_options.denyUrls.isNotEmpty &&
35+
isMatchingRegexPattern(lastPath, _options.denyUrls)) {
36+
return null;
37+
}
38+
39+
return event;
40+
}
41+
42+
Iterable<SentryStackFrame?>? _getStacktraceFrames(SentryEvent event) {
43+
if (event.exceptions?.isNotEmpty == true) {
44+
return event.exceptions?.first.stackTrace?.frames;
45+
}
46+
if (event.threads?.isNotEmpty == true) {
47+
final stacktraces = event.threads?.map((e) => e.stacktrace);
48+
return stacktraces
49+
?.where((element) => element != null)
50+
.expand((element) => element!.frames);
51+
}
52+
return null;
53+
}
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import '../../../sentry_flutter.dart';
2+
import 'url_filter_event_processor.dart';
3+
4+
UrlFilterEventProcessor urlFilterEventProcessor(SentryFlutterOptions _) =>
5+
IoUrlFilterEventProcessor();
6+
7+
class IoUrlFilterEventProcessor implements UrlFilterEventProcessor {
8+
@override
9+
SentryEvent apply(SentryEvent event, Hint hint) => event;
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import '../../../sentry_flutter.dart';
2+
import 'io_url_filter_event_processor.dart'
3+
if (dart.library.html) 'html_url_filter_event_processor.dart'
4+
if (dart.library.js_interop) 'web_url_filter_event_processor.dart';
5+
6+
abstract class UrlFilterEventProcessor implements EventProcessor {
7+
factory UrlFilterEventProcessor(SentryFlutterOptions options) =>
8+
urlFilterEventProcessor(options);
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// We would lose compatibility with old dart versions by adding web to pubspec.
2+
// ignore: depend_on_referenced_packages
3+
import 'package:web/web.dart' as web show window, Window;
4+
5+
import '../../../sentry_flutter.dart';
6+
import 'url_filter_event_processor.dart';
7+
// ignore: implementation_imports
8+
import 'package:sentry/src/utils/regex_utils.dart';
9+
10+
// ignore_for_file: invalid_use_of_internal_member
11+
12+
UrlFilterEventProcessor urlFilterEventProcessor(SentryFlutterOptions options) =>
13+
WebUrlFilterEventProcessor(options);
14+
15+
class WebUrlFilterEventProcessor implements UrlFilterEventProcessor {
16+
WebUrlFilterEventProcessor(
17+
this._options,
18+
);
19+
20+
final SentryFlutterOptions _options;
21+
22+
@override
23+
SentryEvent? apply(SentryEvent event, Hint hint) {
24+
final frames = _getStacktraceFrames(event);
25+
final lastPath = frames?.first?.absPath;
26+
27+
if (lastPath == null) {
28+
return event;
29+
}
30+
31+
if (_options.allowUrls.isNotEmpty &&
32+
!isMatchingRegexPattern(lastPath, _options.allowUrls)) {
33+
return null;
34+
}
35+
36+
if (_options.denyUrls.isNotEmpty &&
37+
isMatchingRegexPattern(lastPath, _options.denyUrls)) {
38+
return null;
39+
}
40+
41+
return event;
42+
}
43+
44+
Iterable<SentryStackFrame?>? _getStacktraceFrames(SentryEvent event) {
45+
if (event.exceptions?.isNotEmpty == true) {
46+
return event.exceptions?.first.stackTrace?.frames;
47+
}
48+
if (event.threads?.isNotEmpty == true) {
49+
final stacktraces = event.threads?.map((e) => e.stacktrace);
50+
return stacktraces
51+
?.where((element) => element != null)
52+
.expand((element) => element!.frames);
53+
}
54+
return null;
55+
}
56+
}

flutter/lib/src/sentry_flutter.dart

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'event_processor/android_platform_exception_event_processor.dart';
99
import 'event_processor/flutter_enricher_event_processor.dart';
1010
import 'event_processor/flutter_exception_event_processor.dart';
1111
import 'event_processor/platform_exception_event_processor.dart';
12+
import 'event_processor/url_filter/url_filter_event_processor.dart';
1213
import 'event_processor/widget_event_processor.dart';
1314
import 'file_system_transport.dart';
1415
import 'flutter_exception_type_identifier.dart';
@@ -131,6 +132,7 @@ mixin SentryFlutter {
131132

132133
options.addEventProcessor(FlutterEnricherEventProcessor(options));
133134
options.addEventProcessor(WidgetEventProcessor());
135+
options.addEventProcessor(UrlFilterEventProcessor(options));
134136

135137
if (options.platformChecker.platform.isAndroid) {
136138
options.addEventProcessor(

flutter/lib/src/sentry_flutter_options.dart

+15
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,21 @@ class SentryFlutterOptions extends SentryOptions {
146146
/// See https://api.flutter.dev/flutter/foundation/FlutterErrorDetails/silent.html
147147
bool reportSilentFlutterErrors = false;
148148

149+
/// (Web only) Events only occurring on these Urls will be handled and sent to sentry.
150+
/// If an empty list is used, the SDK will send all errors.
151+
/// `allowUrls` uses regex for the matching.
152+
///
153+
/// If used on a platform other than Web, this setting will be ignored.
154+
List<String> allowUrls = [];
155+
156+
/// (Web only) Events occurring on these Urls will be ignored and are not sent to sentry.
157+
/// If an empty list is used, the SDK will send all errors.
158+
/// `denyUrls` uses regex for the matching.
159+
/// In combination with `allowUrls` you can block subdomains of the domains listed in `allowUrls`.
160+
///
161+
/// If used on a platform other than Web, this setting will be ignored.
162+
List<String> denyUrls = [];
163+
149164
/// Enables Out of Memory Tracking for iOS and macCatalyst.
150165
/// See the following link for more information and possible restrictions:
151166
/// https://docs.sentry.io/platforms/apple/guides/ios/configuration/out-of-memory/

flutter/lib/src/view_hierarchy/sentry_tree_walker.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ class _TreeWalker {
255255
double? alpha;
256256

257257
final renderObject = element.renderObject;
258-
if (renderObject is RenderBox) {
258+
if (renderObject is RenderBox && renderObject.hasSize) {
259259
final offset = renderObject.localToGlobal(Offset.zero);
260260
if (offset.dx > 0) {
261261
x = offset.dx;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
@TestOn('vm')
2+
library flutter_test;
3+
4+
import 'package:flutter_test/flutter_test.dart';
5+
import 'package:sentry_flutter/sentry_flutter.dart';
6+
import 'package:sentry_flutter/src/event_processor/url_filter/url_filter_event_processor.dart';
7+
8+
void main() {
9+
group("ignore allowUrls and denyUrls for non Web", () {
10+
late Fixture fixture;
11+
12+
setUp(() async {
13+
fixture = Fixture();
14+
});
15+
16+
test('returns the event and ignore allowUrls and denyUrls for non Web',
17+
() async {
18+
SentryEvent? event = SentryEvent(
19+
request: SentryRequest(
20+
url: 'another.url/for/a/special/test/testing/this-feature',
21+
),
22+
);
23+
fixture.options.allowUrls = ["^this.is/.*\$"];
24+
fixture.options.denyUrls = ["special"];
25+
26+
var eventProcessor = fixture.getSut();
27+
event = await eventProcessor.apply(event, Hint());
28+
29+
expect(event, isNotNull);
30+
});
31+
});
32+
}
33+
34+
class Fixture {
35+
SentryFlutterOptions options = SentryFlutterOptions();
36+
UrlFilterEventProcessor getSut() {
37+
return UrlFilterEventProcessor(options);
38+
}
39+
}

0 commit comments

Comments
 (0)