Skip to content

Commit 78c3c07

Browse files
authored
internal(web): add sdk name for native js errors (#3525)
* Add beforeSend to change sdk name in JS * Add beforeSend to js * Review * Cleanup * Update factory for creating JsSdkInfo
1 parent d052aef commit 78c3c07

File tree

3 files changed

+132
-1
lines changed

3 files changed

+132
-1
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@
2121
- [changelog](https://github.com/getsentry/sentry-java/blob/main/CHANGELOG.md#8330)
2222
- [diff](https://github.com/getsentry/sentry-java/compare/8.32.0...8.33.0)
2323

24+
<summary><b>Internal Changes</b></summary>
25+
26+
- Add `sentry.javascript.browser.flutter` sdk name for native js errors ([#3525](https://github.com/getsentry/sentry-dart/pull/3525))
27+
28+
</details>
29+
2430
## 9.13.0
2531

2632
### Features

packages/flutter/lib/src/web/web_sentry_js_binding.dart

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import 'package:meta/meta.dart';
55

66
import 'sentry_js_binding.dart';
77

8+
@visibleForTesting
9+
const String jsSdkName = 'sentry.javascript.browser.flutter';
10+
811
SentryJsBinding createJsBinding() {
912
return WebSentryJsBinding();
1013
}
@@ -26,11 +29,18 @@ class WebSentryJsBinding implements SentryJsBinding {
2629
options['defaultIntegrations'] = options['defaultIntegrations']
2730
.map((String integration) => _createIntegration(integration));
2831
}
29-
_init(options.jsify());
32+
final jsOptions = options.jsify() as JSObject;
33+
jsOptions['beforeSend'] = _beforeSend.toJS;
34+
_init(jsOptions);
3035
_client = SentryJsClient();
3136
_options = _client?.getOptions();
3237
}
3338

39+
static _JsErrorEvent? _beforeSend(_JsErrorEvent event, JSAny? _) {
40+
(event.sdk ??= _JsSdkInfo()).name = jsSdkName.toJS;
41+
return event;
42+
}
43+
3444
@override
3545
void updateSession({int? errors, String? status}) {
3646
final isolationScope = SentryJsIsolationScope();
@@ -215,3 +225,16 @@ external JSObject _dedupeIntegration();
215225
@JS('globalThis')
216226
@internal
217227
external JSObject get globalThis;
228+
229+
@JS()
230+
extension type _JsErrorEvent._(JSObject _) implements JSObject {
231+
external _JsSdkInfo? get sdk;
232+
external set sdk(_JsSdkInfo? value);
233+
}
234+
235+
@JS()
236+
extension type _JsSdkInfo._(JSObject _) implements JSObject {
237+
external set name(JSString value);
238+
239+
factory _JsSdkInfo() => _JsSdkInfo._(JSObject());
240+
}

packages/flutter/test/web/web_sentry_js_binding_test.dart

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
@TestOn('browser')
22
library;
33

4+
import 'dart:async';
45
import 'dart:js_interop';
56
import 'dart:js_interop_unsafe';
67

@@ -36,6 +37,94 @@ void main() {
3637
expect(firstResult, cachedResult);
3738
expect(secondResult, cachedResult);
3839
});
40+
41+
test('sets the JS SDK name for native JS errors', () async {
42+
final sut = await fixture.getSut();
43+
sut.init({'dsn': fakeDsn});
44+
45+
const expectedMessage = 'native js captureException error';
46+
final client = _getClient();
47+
if (client == null) {
48+
throw StateError('Sentry client is not registered');
49+
}
50+
51+
final interceptedEvent = Completer<Map<dynamic, dynamic>>();
52+
void completeFailure(String message) {
53+
if (!interceptedEvent.isCompleted) {
54+
interceptedEvent.completeError(StateError(message));
55+
}
56+
}
57+
58+
final JSFunction beforeSendEventCallback = ((JSAny? event, JSAny? _) {
59+
if (interceptedEvent.isCompleted) {
60+
return;
61+
}
62+
if (event == null) {
63+
completeFailure('beforeSendEvent callback received a null event.');
64+
return;
65+
}
66+
if (!event.isA<JSObject>()) {
67+
completeFailure(
68+
'beforeSendEvent callback received a non-JSObject event.');
69+
return;
70+
}
71+
72+
final eventMap = (event as JSObject).dartify() as Map<dynamic, dynamic>;
73+
final exception = eventMap['exception'];
74+
if (exception is! Map) {
75+
completeFailure(
76+
"beforeSendEvent event is missing an 'exception' map.");
77+
return;
78+
}
79+
80+
final values = exception['values'];
81+
if (values is! List || values.isEmpty) {
82+
completeFailure(
83+
"beforeSendEvent event exception map is missing a non-empty 'values' list.");
84+
return;
85+
}
86+
87+
final firstValue = values.first;
88+
if (firstValue is! Map) {
89+
completeFailure(
90+
"beforeSendEvent event exception values first item is not a map.");
91+
return;
92+
}
93+
94+
final value = firstValue['value'];
95+
if (value == null) {
96+
completeFailure(
97+
"beforeSendEvent event is missing exception 'value'.");
98+
return;
99+
}
100+
101+
if (!value.toString().contains(expectedMessage)) {
102+
completeFailure(
103+
"beforeSendEvent exception value does not contain '$expectedMessage'. Actual: '$value'",
104+
);
105+
return;
106+
}
107+
108+
interceptedEvent.complete(eventMap);
109+
}).toJS;
110+
client.on('beforeSendEvent'.toJS, beforeSendEventCallback);
111+
112+
final sentry = _globalThis['Sentry'] as JSObject?;
113+
final jsError =
114+
_globalThis.callMethod('Error'.toJS, expectedMessage.toJS);
115+
sentry!.callMethod('captureException'.toJS, jsError);
116+
117+
final processedEvent =
118+
await interceptedEvent.future.timeout(const Duration(seconds: 5));
119+
final sdk = processedEvent['sdk'] as Map<dynamic, dynamic>?;
120+
final exception = processedEvent['exception'] as Map<dynamic, dynamic>?;
121+
final values = exception?['values'] as List<dynamic>?;
122+
final firstValue = values?.first as Map<dynamic, dynamic>?;
123+
124+
expect(sdk, isNotNull);
125+
expect(sdk!['name'], jsSdkName);
126+
expect(firstValue?['value'], contains(expectedMessage));
127+
});
39128
});
40129
}
41130

@@ -51,3 +140,16 @@ class Fixture {
51140

52141
@JS('globalThis')
53142
external JSObject get _globalThis;
143+
144+
@JS('Sentry.getClient')
145+
external SentryJsClient? _getClient();
146+
147+
@JS()
148+
@staticInterop
149+
class SentryJsClient {
150+
external factory SentryJsClient();
151+
}
152+
153+
extension _SentryJsClientExtension on SentryJsClient {
154+
external void on(JSString event, JSFunction callback);
155+
}

0 commit comments

Comments
 (0)