From 4524383ca51e8c1dd720345c0d762022731d7866 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 12 Nov 2024 15:59:33 +0100 Subject: [PATCH 01/76] update --- .../src/integrations/web_sdk_integration.dart | 32 ++++++ flutter/lib/src/sentry_flutter.dart | 4 + flutter/lib/src/web/sentry_script_loader.dart | 82 ++++++++++++++ .../test/web/sentry_script_loader_test.dart | 100 ++++++++++++++++++ 4 files changed, 218 insertions(+) create mode 100644 flutter/lib/src/integrations/web_sdk_integration.dart create mode 100644 flutter/lib/src/web/sentry_script_loader.dart create mode 100644 flutter/test/web/sentry_script_loader_test.dart diff --git a/flutter/lib/src/integrations/web_sdk_integration.dart b/flutter/lib/src/integrations/web_sdk_integration.dart new file mode 100644 index 0000000000..aee5abc2f0 --- /dev/null +++ b/flutter/lib/src/integrations/web_sdk_integration.dart @@ -0,0 +1,32 @@ +import 'dart:async'; + +import '../../sentry_flutter.dart'; +import '../web/sentry_script_loader.dart'; + +/// Initializes the Javascript SDK with the given options. +class WebSdkIntegration implements Integration { + WebSdkIntegration(this._scriptLoader); + + final SentryScriptLoader _scriptLoader; + + @override + FutureOr call(Hub hub, SentryFlutterOptions options) async { + try { + await _scriptLoader.loadScripts(); + + options.sdk.addIntegration('WebSdkIntegration'); + } catch (exception, stackTrace) { + options.logger( + SentryLevel.fatal, + 'WebSdkIntegration failed to be installed', + exception: exception, + stackTrace: stackTrace, + ); + } + } + + @override + FutureOr close() { + // no-op for now + } +} diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index 4760cac473..2a1cdac6c6 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -20,6 +20,7 @@ import 'integrations/frames_tracking_integration.dart'; import 'integrations/integrations.dart'; import 'integrations/native_app_start_handler.dart'; import 'integrations/screenshot_integration.dart'; +import 'integrations/web_sdk_integration.dart'; import 'native/factory.dart'; import 'native/native_scope_observer.dart'; import 'native/sentry_native_binding.dart'; @@ -27,6 +28,7 @@ import 'profiling.dart'; import 'renderer/renderer.dart'; import 'version.dart'; import 'view_hierarchy/view_hierarchy_integration.dart'; +import 'web/sentry_script_loader.dart'; /// Configuration options callback typedef FlutterOptionsConfiguration = FutureOr Function( @@ -180,6 +182,8 @@ mixin SentryFlutter { } if (platformChecker.isWeb) { + final scriptLoader = SentryScriptLoader(options); + integrations.add(WebSdkIntegration(scriptLoader)); integrations.add(ConnectivityIntegration()); } diff --git a/flutter/lib/src/web/sentry_script_loader.dart b/flutter/lib/src/web/sentry_script_loader.dart new file mode 100644 index 0000000000..cc075dc436 --- /dev/null +++ b/flutter/lib/src/web/sentry_script_loader.dart @@ -0,0 +1,82 @@ +import 'dart:async'; +import 'dart:html'; + +import '../../sentry_flutter.dart'; + +// todo: set up ci to update this and the integrity +const _sdkVersion = '8.37.1'; + +const productionScripts = [ + { + 'url': + 'https://browser.sentry-cdn.com/$_sdkVersion/bundle.tracing.replay.min.js', + 'integrity': + 'sha384-IZS0kTfvAku3LBcvcHWThKT6lKBimvLUVNZgqF/jtmVAw99L25MM+RhAnozr6iVY' + }, + { + 'url': 'https://browser.sentry-cdn.com/$_sdkVersion/replay-canvas.min.js', + 'integrity': + 'sha384-UNUCiMVh5gTr9Z45bRUPU5eOHHKGOI80UV3zM858k7yV/c6NNhtSJnIDjh+jJ8Vk' + }, +]; +const debugScripts = [ + { + 'url': + 'https://browser.sentry-cdn.com/$_sdkVersion/bundle.tracing.replay.js', + }, + { + 'url': 'https://browser.sentry-cdn.com/$_sdkVersion/replay-canvas.js', + }, +]; + +class SentryScriptLoader { + SentryScriptLoader(this.options, {List>? scripts}) + : _scripts = scripts ?? _getScriptConfigs(options.platformChecker); + + final SentryFlutterOptions options; + final List>? _scripts; + bool get isLoaded => _scriptLoaded; + bool _scriptLoaded = false; + + Future loadScripts({bool useIntegrity = true}) async { + if (_scriptLoaded || _scripts == null) return; + + try { + await Future.wait(_scripts!.map((script) => _loadScript( + script['url']!, useIntegrity ? script['integrity'] : null))); + _scriptLoaded = true; + options.logger(SentryLevel.debug, + 'JS SDK integration: all Sentry scripts loaded successfully.'); + } catch (e, stackTrace) { + options.logger( + SentryLevel.error, 'Failed to load Sentry scripts: $e\n$stackTrace'); + if (options.automatedTestMode) { + rethrow; + } + } + } +} + +List> _getScriptConfigs(PlatformChecker platformChecker) { + if (platformChecker.isDebugMode()) { + return debugScripts; + } else { + return productionScripts; + } +} + +Future _loadScript(String src, String? integrity) { + final completer = Completer(); + final script = ScriptElement() + ..src = src + ..crossOrigin = 'anonymous' + ..onLoad.listen((_) => completer.complete()) + ..onError.listen((event) => completer.completeError('Failed to load $src')); + + if (integrity != null) { + script.integrity = integrity; + } + + document.head?.append(script); + return completer.future; +} diff --git a/flutter/test/web/sentry_script_loader_test.dart b/flutter/test/web/sentry_script_loader_test.dart new file mode 100644 index 0000000000..e0dffc9559 --- /dev/null +++ b/flutter/test/web/sentry_script_loader_test.dart @@ -0,0 +1,100 @@ +@TestOn('browser') +library flutter_test; + +import 'dart:html'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:sentry_flutter/src/web/sentry_script_loader.dart'; + +import '../mocks.dart'; + +void main() { + group('$SentryScriptLoader', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + tearDown(() { + final existingScripts = + document.querySelectorAll('script[src*="sentry-cdn"]'); + for (final script in existingScripts) { + script.remove(); + } + }); + + test('loads production scripts by default', () async { + final sut = fixture.getSut(); + + await sut.loadScripts(); + + final scripts = document.querySelectorAll('script[src*="sentry-cdn"]'); + for (final script in scripts) { + final element = script as ScriptElement; + expect(element.src, contains('.min.js')); + } + }); + + test('loads debug scripts when debug is enabled', () async { + final sut = fixture.getSut(debug: true); + + await sut.loadScripts(); + + final scripts = document.querySelectorAll('script[src*="sentry-cdn"]'); + for (final script in scripts) { + final element = script as ScriptElement; + expect(element.src, isNot(contains('.min.js'))); + } + }); + + test('does not load scripts twice', () async { + final sut = fixture.getSut(); + + await sut.loadScripts(); + final initialScriptCount = document.querySelectorAll('script').length; + + await sut.loadScripts(); + expect(document.querySelectorAll('script').length, initialScriptCount); + }); + + test('handles script loading failures', () async { + final scripts = [ + { + 'url': 'https://invalid', + }, + ]; + + // Modify script URL to cause failure + final sut = fixture.getSut(scripts: scripts); + + await expectLater(() async { + await sut.loadScripts(); + }, throwsA(anything)); + }); + + test('maintains script loading order', () async { + final sut = fixture.getSut(); + + await sut.loadScripts(); + + final scripts = document + .querySelectorAll('script[src*="sentry-cdn"]') + .map((s) => (s as ScriptElement).src) + .toList(); + expect(scripts[0], contains('bundle.tracing.replay')); + expect(scripts[1], contains('replay-canvas')); + }); + }); +} + +class Fixture { + final SentryFlutterOptions options = defaultTestOptions(); + + SentryScriptLoader getSut( + {bool debug = false, List>? scripts}) { + options.platformChecker = MockPlatformChecker(isDebug: debug); + return SentryScriptLoader(options, scripts: scripts); + } +} From 02bde65b4f5e0a07e87758b4b3cdba537bfa23c7 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 12 Nov 2024 16:34:23 +0100 Subject: [PATCH 02/76] update --- .../src/integrations/web_sdk_integration.dart | 2 +- flutter/lib/src/web/sentry_script_loader.dart | 23 ++++--- .../web_sdk_integration_test.dart | 62 +++++++++++++++++++ 3 files changed, 79 insertions(+), 8 deletions(-) create mode 100644 flutter/test/integrations/web_sdk_integration_test.dart diff --git a/flutter/lib/src/integrations/web_sdk_integration.dart b/flutter/lib/src/integrations/web_sdk_integration.dart index aee5abc2f0..6dac831596 100644 --- a/flutter/lib/src/integrations/web_sdk_integration.dart +++ b/flutter/lib/src/integrations/web_sdk_integration.dart @@ -14,7 +14,7 @@ class WebSdkIntegration implements Integration { try { await _scriptLoader.loadScripts(); - options.sdk.addIntegration('WebSdkIntegration'); + options.sdk.addIntegration('webSdkIntegration'); } catch (exception, stackTrace) { options.logger( SentryLevel.fatal, diff --git a/flutter/lib/src/web/sentry_script_loader.dart b/flutter/lib/src/web/sentry_script_loader.dart index cc075dc436..75a19d2a69 100644 --- a/flutter/lib/src/web/sentry_script_loader.dart +++ b/flutter/lib/src/web/sentry_script_loader.dart @@ -1,12 +1,14 @@ import 'dart:async'; import 'dart:html'; +import 'package:meta/meta.dart'; + import '../../sentry_flutter.dart'; // todo: set up ci to update this and the integrity const _sdkVersion = '8.37.1'; -const productionScripts = [ +const _productionScripts = [ { 'url': 'https://browser.sentry-cdn.com/$_sdkVersion/bundle.tracing.replay.min.js', @@ -19,7 +21,7 @@ const productionScripts = [ 'sha384-UNUCiMVh5gTr9Z45bRUPU5eOHHKGOI80UV3zM858k7yV/c6NNhtSJnIDjh+jJ8Vk' }, ]; -const debugScripts = [ +const _debugScripts = [ { 'url': 'https://browser.sentry-cdn.com/$_sdkVersion/bundle.tracing.replay.js', @@ -29,6 +31,7 @@ const debugScripts = [ }, ]; +@internal class SentryScriptLoader { SentryScriptLoader(this.options, {List>? scripts}) : _scripts = scripts ?? _getScriptConfigs(options.platformChecker); @@ -38,12 +41,18 @@ class SentryScriptLoader { bool get isLoaded => _scriptLoaded; bool _scriptLoaded = false; - Future loadScripts({bool useIntegrity = true}) async { + Future loadScripts() async { if (_scriptLoaded || _scripts == null) return; try { - await Future.wait(_scripts!.map((script) => _loadScript( - script['url']!, useIntegrity ? script['integrity'] : null))); + await Future.wait(_scripts!.map((script) async { + final url = script['url']; + final integrity = script['integrity']; + + if (url != null) { + return _loadScript(url, integrity); + } + })); _scriptLoaded = true; options.logger(SentryLevel.debug, 'JS SDK integration: all Sentry scripts loaded successfully.'); @@ -59,9 +68,9 @@ class SentryScriptLoader { List> _getScriptConfigs(PlatformChecker platformChecker) { if (platformChecker.isDebugMode()) { - return debugScripts; + return _debugScripts; } else { - return productionScripts; + return _productionScripts; } } diff --git a/flutter/test/integrations/web_sdk_integration_test.dart b/flutter/test/integrations/web_sdk_integration_test.dart new file mode 100644 index 0000000000..f12dcf6220 --- /dev/null +++ b/flutter/test/integrations/web_sdk_integration_test.dart @@ -0,0 +1,62 @@ +@TestOn('browser') +library flutter_test; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:sentry_flutter/src/integrations/web_sdk_integration.dart'; +import 'package:sentry_flutter/src/web/sentry_script_loader.dart'; + +import '../mocks.dart'; +import '../mocks.mocks.dart'; + +void main() { + group('$WebSdkIntegration', () { + late Fixture fixture; + + setUp(() async { + fixture = Fixture(); + }); + + test('adds integration', () async { + final sut = fixture.getSut(); + + sut.call(fixture.hub, fixture.options); + + expect( + fixture.options.sdk.integrations + .contains('nativeAppStartIntegration'), + true); + }); + + test('calls executes loads scripts', () async { + final sut = fixture.getSut(); + + await sut.call(fixture.hub, fixture.options); + + expect(fixture.scriptLoader.loadScriptsCalls, 1); + }); + }); +} + +class Fixture { + final hub = MockHub(); + final options = defaultTestOptions(); + late FakeSentryScriptLoader scriptLoader; + + WebSdkIntegration getSut() { + scriptLoader = FakeSentryScriptLoader(options); + return WebSdkIntegration(scriptLoader); + } +} + +class FakeSentryScriptLoader extends SentryScriptLoader { + FakeSentryScriptLoader(super.options); + + int loadScriptsCalls = 0; + + @override + Future loadScripts() { + loadScriptsCalls += 1; + + return super.loadScripts(); + } +} From 3a995311b321783b60863ad7507044c2fb0295de Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 12 Nov 2024 16:57:01 +0100 Subject: [PATCH 03/76] updaet --- .../src/integrations/web_sdk_integration.dart | 2 +- flutter/lib/src/sentry_flutter.dart | 6 ++- .../lib/src/web/sentry_js_sdk_version.dart | 32 +++++++++++++ flutter/lib/src/web/sentry_script_loader.dart | 45 +++---------------- .../web_sdk_integration_test.dart | 9 ++-- .../test/web/sentry_script_loader_test.dart | 16 ++++--- 6 files changed, 57 insertions(+), 53 deletions(-) create mode 100644 flutter/lib/src/web/sentry_js_sdk_version.dart diff --git a/flutter/lib/src/integrations/web_sdk_integration.dart b/flutter/lib/src/integrations/web_sdk_integration.dart index 6dac831596..1d4548dc46 100644 --- a/flutter/lib/src/integrations/web_sdk_integration.dart +++ b/flutter/lib/src/integrations/web_sdk_integration.dart @@ -12,7 +12,7 @@ class WebSdkIntegration implements Integration { @override FutureOr call(Hub hub, SentryFlutterOptions options) async { try { - await _scriptLoader.loadScripts(); + await _scriptLoader.load(); options.sdk.addIntegration('webSdkIntegration'); } catch (exception, stackTrace) { diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index 2a1cdac6c6..348eeaad25 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -28,6 +28,7 @@ import 'profiling.dart'; import 'renderer/renderer.dart'; import 'version.dart'; import 'view_hierarchy/view_hierarchy_integration.dart'; +import 'web/sentry_js_sdk_version.dart'; import 'web/sentry_script_loader.dart'; /// Configuration options callback @@ -182,7 +183,10 @@ mixin SentryFlutter { } if (platformChecker.isWeb) { - final scriptLoader = SentryScriptLoader(options); + final scripts = options.platformChecker.isDebugMode() + ? debugScripts + : productionScripts; + final scriptLoader = SentryScriptLoader(options, scripts); integrations.add(WebSdkIntegration(scriptLoader)); integrations.add(ConnectivityIntegration()); } diff --git a/flutter/lib/src/web/sentry_js_sdk_version.dart b/flutter/lib/src/web/sentry_js_sdk_version.dart new file mode 100644 index 0000000000..b53b66da34 --- /dev/null +++ b/flutter/lib/src/web/sentry_js_sdk_version.dart @@ -0,0 +1,32 @@ +// todo: set up ci to update this and the integrity +import 'package:meta/meta.dart'; + +@internal +const jsSdkVersion = '8.37.1'; + +@internal +const productionScripts = [ + { + 'url': + 'https://browser.sentry-cdn.com/$jsSdkVersion/bundle.tracing.replay.min.js', + 'integrity': + 'sha384-IZS0kTfvAku3LBcvcHWThKT6lKBimvLUVNZgqF/jtmVAw99L25MM+RhAnozr6iVY' + }, + { + // todo: might need to be adjusted based on renderer (canvas vs html) later on + 'url': 'https://browser.sentry-cdn.com/$jsSdkVersion/replay-canvas.min.js', + 'integrity': + 'sha384-UNUCiMVh5gTr9Z45bRUPU5eOHHKGOI80UV3zM858k7yV/c6NNhtSJnIDjh+jJ8Vk' + }, +]; + +@internal +const debugScripts = [ + { + 'url': + 'https://browser.sentry-cdn.com/$jsSdkVersion/bundle.tracing.replay.js', + }, + { + 'url': 'https://browser.sentry-cdn.com/$jsSdkVersion/replay-canvas.js', + }, +]; diff --git a/flutter/lib/src/web/sentry_script_loader.dart b/flutter/lib/src/web/sentry_script_loader.dart index 75a19d2a69..eb2d2afe25 100644 --- a/flutter/lib/src/web/sentry_script_loader.dart +++ b/flutter/lib/src/web/sentry_script_loader.dart @@ -5,47 +5,20 @@ import 'package:meta/meta.dart'; import '../../sentry_flutter.dart'; -// todo: set up ci to update this and the integrity -const _sdkVersion = '8.37.1'; - -const _productionScripts = [ - { - 'url': - 'https://browser.sentry-cdn.com/$_sdkVersion/bundle.tracing.replay.min.js', - 'integrity': - 'sha384-IZS0kTfvAku3LBcvcHWThKT6lKBimvLUVNZgqF/jtmVAw99L25MM+RhAnozr6iVY' - }, - { - 'url': 'https://browser.sentry-cdn.com/$_sdkVersion/replay-canvas.min.js', - 'integrity': - 'sha384-UNUCiMVh5gTr9Z45bRUPU5eOHHKGOI80UV3zM858k7yV/c6NNhtSJnIDjh+jJ8Vk' - }, -]; -const _debugScripts = [ - { - 'url': - 'https://browser.sentry-cdn.com/$_sdkVersion/bundle.tracing.replay.js', - }, - { - 'url': 'https://browser.sentry-cdn.com/$_sdkVersion/replay-canvas.js', - }, -]; - @internal class SentryScriptLoader { - SentryScriptLoader(this.options, {List>? scripts}) - : _scripts = scripts ?? _getScriptConfigs(options.platformChecker); + SentryScriptLoader(this.options, this.scripts); final SentryFlutterOptions options; - final List>? _scripts; + final List> scripts; bool get isLoaded => _scriptLoaded; bool _scriptLoaded = false; - Future loadScripts() async { - if (_scriptLoaded || _scripts == null) return; + Future load() async { + if (_scriptLoaded) return; try { - await Future.wait(_scripts!.map((script) async { + await Future.wait(scripts.map((script) async { final url = script['url']; final integrity = script['integrity']; @@ -66,14 +39,6 @@ class SentryScriptLoader { } } -List> _getScriptConfigs(PlatformChecker platformChecker) { - if (platformChecker.isDebugMode()) { - return _debugScripts; - } else { - return _productionScripts; - } -} - Future _loadScript(String src, String? integrity) { final completer = Completer(); final script = ScriptElement() diff --git a/flutter/test/integrations/web_sdk_integration_test.dart b/flutter/test/integrations/web_sdk_integration_test.dart index f12dcf6220..f4efacb4d1 100644 --- a/flutter/test/integrations/web_sdk_integration_test.dart +++ b/flutter/test/integrations/web_sdk_integration_test.dart @@ -3,6 +3,7 @@ library flutter_test; import 'package:flutter_test/flutter_test.dart'; import 'package:sentry_flutter/src/integrations/web_sdk_integration.dart'; +import 'package:sentry_flutter/src/web/sentry_js_sdk_version.dart'; import 'package:sentry_flutter/src/web/sentry_script_loader.dart'; import '../mocks.dart'; @@ -43,20 +44,20 @@ class Fixture { late FakeSentryScriptLoader scriptLoader; WebSdkIntegration getSut() { - scriptLoader = FakeSentryScriptLoader(options); + scriptLoader = FakeSentryScriptLoader(options, debugScripts); return WebSdkIntegration(scriptLoader); } } class FakeSentryScriptLoader extends SentryScriptLoader { - FakeSentryScriptLoader(super.options); + FakeSentryScriptLoader(super.options, super.scripts); int loadScriptsCalls = 0; @override - Future loadScripts() { + Future load() { loadScriptsCalls += 1; - return super.loadScripts(); + return super.load(); } } diff --git a/flutter/test/web/sentry_script_loader_test.dart b/flutter/test/web/sentry_script_loader_test.dart index e0dffc9559..356053e8d2 100644 --- a/flutter/test/web/sentry_script_loader_test.dart +++ b/flutter/test/web/sentry_script_loader_test.dart @@ -5,6 +5,7 @@ import 'dart:html'; import 'package:flutter_test/flutter_test.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:sentry_flutter/src/web/sentry_js_sdk_version.dart'; import 'package:sentry_flutter/src/web/sentry_script_loader.dart'; import '../mocks.dart'; @@ -28,7 +29,7 @@ void main() { test('loads production scripts by default', () async { final sut = fixture.getSut(); - await sut.loadScripts(); + await sut.load(); final scripts = document.querySelectorAll('script[src*="sentry-cdn"]'); for (final script in scripts) { @@ -40,7 +41,7 @@ void main() { test('loads debug scripts when debug is enabled', () async { final sut = fixture.getSut(debug: true); - await sut.loadScripts(); + await sut.load(); final scripts = document.querySelectorAll('script[src*="sentry-cdn"]'); for (final script in scripts) { @@ -52,10 +53,10 @@ void main() { test('does not load scripts twice', () async { final sut = fixture.getSut(); - await sut.loadScripts(); + await sut.load(); final initialScriptCount = document.querySelectorAll('script').length; - await sut.loadScripts(); + await sut.load(); expect(document.querySelectorAll('script').length, initialScriptCount); }); @@ -70,14 +71,14 @@ void main() { final sut = fixture.getSut(scripts: scripts); await expectLater(() async { - await sut.loadScripts(); + await sut.load(); }, throwsA(anything)); }); test('maintains script loading order', () async { final sut = fixture.getSut(); - await sut.loadScripts(); + await sut.load(); final scripts = document .querySelectorAll('script[src*="sentry-cdn"]') @@ -95,6 +96,7 @@ class Fixture { SentryScriptLoader getSut( {bool debug = false, List>? scripts}) { options.platformChecker = MockPlatformChecker(isDebug: debug); - return SentryScriptLoader(options, scripts: scripts); + return SentryScriptLoader( + options, debug ? debugScripts : productionScripts); } } From bb94847baaf07015d61e5c814b289f72c421db91 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 12 Nov 2024 17:03:55 +0100 Subject: [PATCH 04/76] Update sentry_js_sdk_version.dart --- flutter/lib/src/web/sentry_js_sdk_version.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/lib/src/web/sentry_js_sdk_version.dart b/flutter/lib/src/web/sentry_js_sdk_version.dart index b53b66da34..438fb329c2 100644 --- a/flutter/lib/src/web/sentry_js_sdk_version.dart +++ b/flutter/lib/src/web/sentry_js_sdk_version.dart @@ -1,6 +1,6 @@ -// todo: set up ci to update this and the integrity import 'package:meta/meta.dart'; +// todo: set up ci to update this and the integrity @internal const jsSdkVersion = '8.37.1'; From c6c1cbd9297167d1063c53ff26604e2130a2910f Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 12 Nov 2024 17:07:22 +0100 Subject: [PATCH 05/76] temporary ci change --- .github/workflows/flutter.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/flutter.yml b/.github/workflows/flutter.yml index 95dfe52727..5b414b6e8e 100644 --- a/.github/workflows/flutter.yml +++ b/.github/workflows/flutter.yml @@ -4,6 +4,7 @@ on: branches: - main - release/** + - feat/** pull_request: paths: - '.github/workflows/flutter.yml' From b837667e87c4fd140e2db4053549a1daaacca4c3 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 12 Nov 2024 17:13:11 +0100 Subject: [PATCH 06/76] fix test --- flutter/test/web/sentry_script_loader_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/test/web/sentry_script_loader_test.dart b/flutter/test/web/sentry_script_loader_test.dart index 356053e8d2..5296e10be8 100644 --- a/flutter/test/web/sentry_script_loader_test.dart +++ b/flutter/test/web/sentry_script_loader_test.dart @@ -97,6 +97,6 @@ class Fixture { {bool debug = false, List>? scripts}) { options.platformChecker = MockPlatformChecker(isDebug: debug); return SentryScriptLoader( - options, debug ? debugScripts : productionScripts); + options, debug ? debugScripts : scripts ?? productionScripts); } } From e535701d0e72435ea8009707986969fac995815c Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 12 Nov 2024 17:49:04 +0100 Subject: [PATCH 07/76] fix compilation --- flutter/lib/src/integrations/web_sdk_integration.dart | 2 +- flutter/lib/src/sentry_flutter.dart | 4 ++-- .../src/web/script_loader/sentry_script_loader.dart | 3 +++ .../script_loader/sentry_script_loader_factory.dart | 3 +++ .../sentry_script_loader_factory_unsupported.dart | 8 ++++++++ .../sentry_script_loader_factory_web.dart | 8 ++++++++ .../sentry_script_loader_impl.dart} | 9 +++++---- .../test/integrations/web_sdk_integration_test.dart | 11 +++-------- flutter/test/web/sentry_script_loader_test.dart | 7 ++++--- 9 files changed, 37 insertions(+), 18 deletions(-) create mode 100644 flutter/lib/src/web/script_loader/sentry_script_loader.dart create mode 100644 flutter/lib/src/web/script_loader/sentry_script_loader_factory.dart create mode 100644 flutter/lib/src/web/script_loader/sentry_script_loader_factory_unsupported.dart create mode 100644 flutter/lib/src/web/script_loader/sentry_script_loader_factory_web.dart rename flutter/lib/src/web/{sentry_script_loader.dart => script_loader/sentry_script_loader_impl.dart} (86%) diff --git a/flutter/lib/src/integrations/web_sdk_integration.dart b/flutter/lib/src/integrations/web_sdk_integration.dart index 1d4548dc46..252b92e886 100644 --- a/flutter/lib/src/integrations/web_sdk_integration.dart +++ b/flutter/lib/src/integrations/web_sdk_integration.dart @@ -1,7 +1,7 @@ import 'dart:async'; import '../../sentry_flutter.dart'; -import '../web/sentry_script_loader.dart'; +import '../web/script_loader/sentry_script_loader.dart'; /// Initializes the Javascript SDK with the given options. class WebSdkIntegration implements Integration { diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index 348eeaad25..f00c2f5aa4 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -28,8 +28,8 @@ import 'profiling.dart'; import 'renderer/renderer.dart'; import 'version.dart'; import 'view_hierarchy/view_hierarchy_integration.dart'; +import 'web/script_loader/sentry_script_loader_factory.dart'; import 'web/sentry_js_sdk_version.dart'; -import 'web/sentry_script_loader.dart'; /// Configuration options callback typedef FlutterOptionsConfiguration = FutureOr Function( @@ -186,7 +186,7 @@ mixin SentryFlutter { final scripts = options.platformChecker.isDebugMode() ? debugScripts : productionScripts; - final scriptLoader = SentryScriptLoader(options, scripts); + final scriptLoader = createSentryScriptLoader(options, scripts); integrations.add(WebSdkIntegration(scriptLoader)); integrations.add(ConnectivityIntegration()); } diff --git a/flutter/lib/src/web/script_loader/sentry_script_loader.dart b/flutter/lib/src/web/script_loader/sentry_script_loader.dart new file mode 100644 index 0000000000..40c1d0c709 --- /dev/null +++ b/flutter/lib/src/web/script_loader/sentry_script_loader.dart @@ -0,0 +1,3 @@ +abstract class SentryScriptLoader { + Future load(); +} diff --git a/flutter/lib/src/web/script_loader/sentry_script_loader_factory.dart b/flutter/lib/src/web/script_loader/sentry_script_loader_factory.dart new file mode 100644 index 0000000000..59ed98d960 --- /dev/null +++ b/flutter/lib/src/web/script_loader/sentry_script_loader_factory.dart @@ -0,0 +1,3 @@ +export 'sentry_script_loader_factory_unsupported.dart' + if (dart.library.html) 'sentry_script_loader_factory_web.dart' + if (dart.library.js_interop) 'sentry_script_loader_factory_web.dart'; diff --git a/flutter/lib/src/web/script_loader/sentry_script_loader_factory_unsupported.dart b/flutter/lib/src/web/script_loader/sentry_script_loader_factory_unsupported.dart new file mode 100644 index 0000000000..0ac938c79f --- /dev/null +++ b/flutter/lib/src/web/script_loader/sentry_script_loader_factory_unsupported.dart @@ -0,0 +1,8 @@ +import '../../../sentry_flutter.dart'; +import 'sentry_script_loader.dart'; + +SentryScriptLoader createSentryScriptLoader( + SentryFlutterOptions options, List> scripts) { + throw UnsupportedError( + "Sentry script loader is not supported on this platform."); +} diff --git a/flutter/lib/src/web/script_loader/sentry_script_loader_factory_web.dart b/flutter/lib/src/web/script_loader/sentry_script_loader_factory_web.dart new file mode 100644 index 0000000000..a60978dab6 --- /dev/null +++ b/flutter/lib/src/web/script_loader/sentry_script_loader_factory_web.dart @@ -0,0 +1,8 @@ +import '../../../sentry_flutter.dart'; +import 'sentry_script_loader.dart'; +import 'sentry_script_loader_impl.dart'; + +SentryScriptLoader createSentryScriptLoader( + SentryFlutterOptions options, List> scripts) { + return SentryScriptLoaderImpl(options, scripts); +} diff --git a/flutter/lib/src/web/sentry_script_loader.dart b/flutter/lib/src/web/script_loader/sentry_script_loader_impl.dart similarity index 86% rename from flutter/lib/src/web/sentry_script_loader.dart rename to flutter/lib/src/web/script_loader/sentry_script_loader_impl.dart index eb2d2afe25..865b4ba4a7 100644 --- a/flutter/lib/src/web/sentry_script_loader.dart +++ b/flutter/lib/src/web/script_loader/sentry_script_loader_impl.dart @@ -3,17 +3,18 @@ import 'dart:html'; import 'package:meta/meta.dart'; -import '../../sentry_flutter.dart'; +import '../../../sentry_flutter.dart'; +import 'sentry_script_loader.dart'; @internal -class SentryScriptLoader { - SentryScriptLoader(this.options, this.scripts); +class SentryScriptLoaderImpl implements SentryScriptLoader { + SentryScriptLoaderImpl(this.options, this.scripts); final SentryFlutterOptions options; final List> scripts; - bool get isLoaded => _scriptLoaded; bool _scriptLoaded = false; + @override Future load() async { if (_scriptLoaded) return; diff --git a/flutter/test/integrations/web_sdk_integration_test.dart b/flutter/test/integrations/web_sdk_integration_test.dart index f4efacb4d1..449658e492 100644 --- a/flutter/test/integrations/web_sdk_integration_test.dart +++ b/flutter/test/integrations/web_sdk_integration_test.dart @@ -3,8 +3,7 @@ library flutter_test; import 'package:flutter_test/flutter_test.dart'; import 'package:sentry_flutter/src/integrations/web_sdk_integration.dart'; -import 'package:sentry_flutter/src/web/sentry_js_sdk_version.dart'; -import 'package:sentry_flutter/src/web/sentry_script_loader.dart'; +import 'package:sentry_flutter/src/web/script_loader/sentry_script_loader.dart'; import '../mocks.dart'; import '../mocks.mocks.dart'; @@ -44,20 +43,16 @@ class Fixture { late FakeSentryScriptLoader scriptLoader; WebSdkIntegration getSut() { - scriptLoader = FakeSentryScriptLoader(options, debugScripts); + scriptLoader = FakeSentryScriptLoader(); return WebSdkIntegration(scriptLoader); } } class FakeSentryScriptLoader extends SentryScriptLoader { - FakeSentryScriptLoader(super.options, super.scripts); - int loadScriptsCalls = 0; @override - Future load() { + Future load() async { loadScriptsCalls += 1; - - return super.load(); } } diff --git a/flutter/test/web/sentry_script_loader_test.dart b/flutter/test/web/sentry_script_loader_test.dart index 5296e10be8..032cbd3973 100644 --- a/flutter/test/web/sentry_script_loader_test.dart +++ b/flutter/test/web/sentry_script_loader_test.dart @@ -5,13 +5,14 @@ import 'dart:html'; import 'package:flutter_test/flutter_test.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:sentry_flutter/src/web/script_loader/sentry_script_loader.dart'; +import 'package:sentry_flutter/src/web/script_loader/sentry_script_loader_impl.dart'; import 'package:sentry_flutter/src/web/sentry_js_sdk_version.dart'; -import 'package:sentry_flutter/src/web/sentry_script_loader.dart'; import '../mocks.dart'; void main() { - group('$SentryScriptLoader', () { + group('$SentryScriptLoaderImpl', () { late Fixture fixture; setUp(() { @@ -96,7 +97,7 @@ class Fixture { SentryScriptLoader getSut( {bool debug = false, List>? scripts}) { options.platformChecker = MockPlatformChecker(isDebug: debug); - return SentryScriptLoader( + return SentryScriptLoaderImpl( options, debug ? debugScripts : scripts ?? productionScripts); } } From 0f8c6fc63e8f74aa0988161154413ffa2962f354 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 12 Nov 2024 17:58:38 +0100 Subject: [PATCH 08/76] fix compilation --- .../script_loader/sentry_script_loader_factory.dart | 2 +- ...ted.dart => sentry_script_loader_factory_noop.dart} | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) rename flutter/lib/src/web/script_loader/{sentry_script_loader_factory_unsupported.dart => sentry_script_loader_factory_noop.dart} (55%) diff --git a/flutter/lib/src/web/script_loader/sentry_script_loader_factory.dart b/flutter/lib/src/web/script_loader/sentry_script_loader_factory.dart index 59ed98d960..253b152c68 100644 --- a/flutter/lib/src/web/script_loader/sentry_script_loader_factory.dart +++ b/flutter/lib/src/web/script_loader/sentry_script_loader_factory.dart @@ -1,3 +1,3 @@ -export 'sentry_script_loader_factory_unsupported.dart' +export 'sentry_script_loader_factory_noop.dart' if (dart.library.html) 'sentry_script_loader_factory_web.dart' if (dart.library.js_interop) 'sentry_script_loader_factory_web.dart'; diff --git a/flutter/lib/src/web/script_loader/sentry_script_loader_factory_unsupported.dart b/flutter/lib/src/web/script_loader/sentry_script_loader_factory_noop.dart similarity index 55% rename from flutter/lib/src/web/script_loader/sentry_script_loader_factory_unsupported.dart rename to flutter/lib/src/web/script_loader/sentry_script_loader_factory_noop.dart index 0ac938c79f..e2f4d172d9 100644 --- a/flutter/lib/src/web/script_loader/sentry_script_loader_factory_unsupported.dart +++ b/flutter/lib/src/web/script_loader/sentry_script_loader_factory_noop.dart @@ -3,6 +3,12 @@ import 'sentry_script_loader.dart'; SentryScriptLoader createSentryScriptLoader( SentryFlutterOptions options, List> scripts) { - throw UnsupportedError( - "Sentry script loader is not supported on this platform."); + return NoopSentryScriptLoader(); +} + +class NoopSentryScriptLoader implements SentryScriptLoader { + @override + Future load() async { + // no-op + } } From 01c27380733e202566bc401a262a441d2bc4600c Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 12 Nov 2024 18:08:45 +0100 Subject: [PATCH 09/76] fix --- flutter/test/integrations/web_sdk_integration_test.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/flutter/test/integrations/web_sdk_integration_test.dart b/flutter/test/integrations/web_sdk_integration_test.dart index 449658e492..3f05df3d0e 100644 --- a/flutter/test/integrations/web_sdk_integration_test.dart +++ b/flutter/test/integrations/web_sdk_integration_test.dart @@ -22,9 +22,7 @@ void main() { sut.call(fixture.hub, fixture.options); expect( - fixture.options.sdk.integrations - .contains('nativeAppStartIntegration'), - true); + fixture.options.sdk.integrations.contains('webSdkIntegration'), true); }); test('calls executes loads scripts', () async { From e5179e47bb889d717e576fb0f7a5790114e228bc Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 12 Nov 2024 18:26:07 +0100 Subject: [PATCH 10/76] fix test --- flutter/lib/src/integrations/web_sdk_integration.dart | 9 +++++++-- flutter/test/integrations/web_sdk_integration_test.dart | 6 +++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/flutter/lib/src/integrations/web_sdk_integration.dart b/flutter/lib/src/integrations/web_sdk_integration.dart index 252b92e886..51729f5edc 100644 --- a/flutter/lib/src/integrations/web_sdk_integration.dart +++ b/flutter/lib/src/integrations/web_sdk_integration.dart @@ -1,5 +1,7 @@ import 'dart:async'; +import 'package:meta/meta.dart'; + import '../../sentry_flutter.dart'; import '../web/script_loader/sentry_script_loader.dart'; @@ -9,16 +11,19 @@ class WebSdkIntegration implements Integration { final SentryScriptLoader _scriptLoader; + @internal + static const name = 'webSdkIntegration'; + @override FutureOr call(Hub hub, SentryFlutterOptions options) async { try { await _scriptLoader.load(); - options.sdk.addIntegration('webSdkIntegration'); + options.sdk.addIntegration(name); } catch (exception, stackTrace) { options.logger( SentryLevel.fatal, - 'WebSdkIntegration failed to be installed', + '$name failed to be installed', exception: exception, stackTrace: stackTrace, ); diff --git a/flutter/test/integrations/web_sdk_integration_test.dart b/flutter/test/integrations/web_sdk_integration_test.dart index 3f05df3d0e..82720d91df 100644 --- a/flutter/test/integrations/web_sdk_integration_test.dart +++ b/flutter/test/integrations/web_sdk_integration_test.dart @@ -19,10 +19,10 @@ void main() { test('adds integration', () async { final sut = fixture.getSut(); - sut.call(fixture.hub, fixture.options); + await sut.call(fixture.hub, fixture.options); - expect( - fixture.options.sdk.integrations.contains('webSdkIntegration'), true); + expect(fixture.options.sdk.integrations.contains(WebSdkIntegration.name), + true); }); test('calls executes loads scripts', () async { From 59b4ab894ad6322d8f488b2932d8718e3e9d76d7 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 13 Nov 2024 13:05:40 +0100 Subject: [PATCH 11/76] update --- flutter/lib/src/sentry_flutter.dart | 4 +- .../script_loader/html_script_dom_api.dart | 18 ++++++ .../script_loader/noop_script_dom_api.dart | 1 + .../src/web/script_loader/script_dom_api.dart | 3 + .../script_loader/sentry_script_loader.dart | 43 +++++++++++++- .../sentry_script_loader_factory.dart | 3 - .../sentry_script_loader_factory_noop.dart | 14 ----- .../sentry_script_loader_factory_web.dart | 8 --- .../sentry_script_loader_impl.dart | 57 ------------------- .../web/script_loader/web_script_dom_api.dart | 20 +++++++ .../web_sdk_integration_test.dart | 4 +- .../test/web/sentry_script_loader_test.dart | 5 +- 12 files changed, 90 insertions(+), 90 deletions(-) create mode 100644 flutter/lib/src/web/script_loader/html_script_dom_api.dart create mode 100644 flutter/lib/src/web/script_loader/noop_script_dom_api.dart create mode 100644 flutter/lib/src/web/script_loader/script_dom_api.dart delete mode 100644 flutter/lib/src/web/script_loader/sentry_script_loader_factory.dart delete mode 100644 flutter/lib/src/web/script_loader/sentry_script_loader_factory_noop.dart delete mode 100644 flutter/lib/src/web/script_loader/sentry_script_loader_factory_web.dart delete mode 100644 flutter/lib/src/web/script_loader/sentry_script_loader_impl.dart create mode 100644 flutter/lib/src/web/script_loader/web_script_dom_api.dart diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index f00c2f5aa4..b1d3a39ca0 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -28,7 +28,7 @@ import 'profiling.dart'; import 'renderer/renderer.dart'; import 'version.dart'; import 'view_hierarchy/view_hierarchy_integration.dart'; -import 'web/script_loader/sentry_script_loader_factory.dart'; +import 'web/script_loader/sentry_script_loader.dart'; import 'web/sentry_js_sdk_version.dart'; /// Configuration options callback @@ -186,7 +186,7 @@ mixin SentryFlutter { final scripts = options.platformChecker.isDebugMode() ? debugScripts : productionScripts; - final scriptLoader = createSentryScriptLoader(options, scripts); + final scriptLoader = SentryScriptLoader(options, scripts); integrations.add(WebSdkIntegration(scriptLoader)); integrations.add(ConnectivityIntegration()); } diff --git a/flutter/lib/src/web/script_loader/html_script_dom_api.dart b/flutter/lib/src/web/script_loader/html_script_dom_api.dart new file mode 100644 index 0000000000..c84423be14 --- /dev/null +++ b/flutter/lib/src/web/script_loader/html_script_dom_api.dart @@ -0,0 +1,18 @@ +import 'dart:async'; +import 'dart:html'; + +Future loadScript(String src, String? integrity) { + final completer = Completer(); + final script = ScriptElement() + ..src = src + ..crossOrigin = 'anonymous' + ..onLoad.listen((_) => completer.complete()) + ..onError.listen((event) => completer.completeError('Failed to load $src')); + + if (integrity != null) { + script.integrity = integrity; + } + + document.head?.append(script); + return completer.future; +} diff --git a/flutter/lib/src/web/script_loader/noop_script_dom_api.dart b/flutter/lib/src/web/script_loader/noop_script_dom_api.dart new file mode 100644 index 0000000000..db45ac0914 --- /dev/null +++ b/flutter/lib/src/web/script_loader/noop_script_dom_api.dart @@ -0,0 +1 @@ +Future loadScript(String src, String? integrity) async {} diff --git a/flutter/lib/src/web/script_loader/script_dom_api.dart b/flutter/lib/src/web/script_loader/script_dom_api.dart new file mode 100644 index 0000000000..62beb3e8c1 --- /dev/null +++ b/flutter/lib/src/web/script_loader/script_dom_api.dart @@ -0,0 +1,3 @@ +export 'noop_script_dom_api.dart' + if (dart.library.html) 'html_script_dom_api.dart' + if (dart.library.js_interop) 'web_script_dom_api.dart'; diff --git a/flutter/lib/src/web/script_loader/sentry_script_loader.dart b/flutter/lib/src/web/script_loader/sentry_script_loader.dart index 40c1d0c709..4345704fa7 100644 --- a/flutter/lib/src/web/script_loader/sentry_script_loader.dart +++ b/flutter/lib/src/web/script_loader/sentry_script_loader.dart @@ -1,3 +1,42 @@ -abstract class SentryScriptLoader { - Future load(); +import 'dart:async'; + +import 'package:meta/meta.dart'; + +import '../../../sentry_flutter.dart'; +import 'script_dom_api.dart'; + +@internal +class SentryScriptLoader { + SentryScriptLoader(this.options, this.scripts); + + final SentryFlutterOptions options; + final List> scripts; + bool _scriptLoaded = false; + + /// Loads scripts into the document asynchronously. + /// + /// Idempotent: does nothing if scripts are already loaded. + Future load() async { + if (_scriptLoaded) return; + + try { + await Future.wait(scripts.map((script) async { + final url = script['url']; + final integrity = script['integrity']; + + if (url != null) { + return loadScript(url, integrity); + } + })); + _scriptLoaded = true; + options.logger(SentryLevel.debug, + 'JS SDK integration: all Sentry scripts loaded successfully.'); + } catch (e, stackTrace) { + options.logger( + SentryLevel.error, 'Failed to load Sentry scripts: $e\n$stackTrace'); + if (options.automatedTestMode) { + rethrow; + } + } + } } diff --git a/flutter/lib/src/web/script_loader/sentry_script_loader_factory.dart b/flutter/lib/src/web/script_loader/sentry_script_loader_factory.dart deleted file mode 100644 index 253b152c68..0000000000 --- a/flutter/lib/src/web/script_loader/sentry_script_loader_factory.dart +++ /dev/null @@ -1,3 +0,0 @@ -export 'sentry_script_loader_factory_noop.dart' - if (dart.library.html) 'sentry_script_loader_factory_web.dart' - if (dart.library.js_interop) 'sentry_script_loader_factory_web.dart'; diff --git a/flutter/lib/src/web/script_loader/sentry_script_loader_factory_noop.dart b/flutter/lib/src/web/script_loader/sentry_script_loader_factory_noop.dart deleted file mode 100644 index e2f4d172d9..0000000000 --- a/flutter/lib/src/web/script_loader/sentry_script_loader_factory_noop.dart +++ /dev/null @@ -1,14 +0,0 @@ -import '../../../sentry_flutter.dart'; -import 'sentry_script_loader.dart'; - -SentryScriptLoader createSentryScriptLoader( - SentryFlutterOptions options, List> scripts) { - return NoopSentryScriptLoader(); -} - -class NoopSentryScriptLoader implements SentryScriptLoader { - @override - Future load() async { - // no-op - } -} diff --git a/flutter/lib/src/web/script_loader/sentry_script_loader_factory_web.dart b/flutter/lib/src/web/script_loader/sentry_script_loader_factory_web.dart deleted file mode 100644 index a60978dab6..0000000000 --- a/flutter/lib/src/web/script_loader/sentry_script_loader_factory_web.dart +++ /dev/null @@ -1,8 +0,0 @@ -import '../../../sentry_flutter.dart'; -import 'sentry_script_loader.dart'; -import 'sentry_script_loader_impl.dart'; - -SentryScriptLoader createSentryScriptLoader( - SentryFlutterOptions options, List> scripts) { - return SentryScriptLoaderImpl(options, scripts); -} diff --git a/flutter/lib/src/web/script_loader/sentry_script_loader_impl.dart b/flutter/lib/src/web/script_loader/sentry_script_loader_impl.dart deleted file mode 100644 index 865b4ba4a7..0000000000 --- a/flutter/lib/src/web/script_loader/sentry_script_loader_impl.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'dart:async'; -import 'dart:html'; - -import 'package:meta/meta.dart'; - -import '../../../sentry_flutter.dart'; -import 'sentry_script_loader.dart'; - -@internal -class SentryScriptLoaderImpl implements SentryScriptLoader { - SentryScriptLoaderImpl(this.options, this.scripts); - - final SentryFlutterOptions options; - final List> scripts; - bool _scriptLoaded = false; - - @override - Future load() async { - if (_scriptLoaded) return; - - try { - await Future.wait(scripts.map((script) async { - final url = script['url']; - final integrity = script['integrity']; - - if (url != null) { - return _loadScript(url, integrity); - } - })); - _scriptLoaded = true; - options.logger(SentryLevel.debug, - 'JS SDK integration: all Sentry scripts loaded successfully.'); - } catch (e, stackTrace) { - options.logger( - SentryLevel.error, 'Failed to load Sentry scripts: $e\n$stackTrace'); - if (options.automatedTestMode) { - rethrow; - } - } - } -} - -Future _loadScript(String src, String? integrity) { - final completer = Completer(); - final script = ScriptElement() - ..src = src - ..crossOrigin = 'anonymous' - ..onLoad.listen((_) => completer.complete()) - ..onError.listen((event) => completer.completeError('Failed to load $src')); - - if (integrity != null) { - script.integrity = integrity; - } - - document.head?.append(script); - return completer.future; -} diff --git a/flutter/lib/src/web/script_loader/web_script_dom_api.dart b/flutter/lib/src/web/script_loader/web_script_dom_api.dart new file mode 100644 index 0000000000..ac80cdc127 --- /dev/null +++ b/flutter/lib/src/web/script_loader/web_script_dom_api.dart @@ -0,0 +1,20 @@ +import 'dart:async'; + +// ignore: depend_on_referenced_packages +import 'package:web/web.dart'; + +Future loadScript(String src, String? integrity) { + final completer = Completer(); + final script = HTMLScriptElement() + ..src = src + ..crossOrigin = 'anonymous' + ..onLoad.listen((_) => completer.complete()) + ..onError.listen((event) => completer.completeError('Failed to load $src')); + + if (integrity != null) { + script.integrity = integrity; + } + + document.head?.append(script); + return completer.future; +} diff --git a/flutter/test/integrations/web_sdk_integration_test.dart b/flutter/test/integrations/web_sdk_integration_test.dart index 82720d91df..8b591de4c5 100644 --- a/flutter/test/integrations/web_sdk_integration_test.dart +++ b/flutter/test/integrations/web_sdk_integration_test.dart @@ -41,7 +41,7 @@ class Fixture { late FakeSentryScriptLoader scriptLoader; WebSdkIntegration getSut() { - scriptLoader = FakeSentryScriptLoader(); + scriptLoader = FakeSentryScriptLoader(options, []); return WebSdkIntegration(scriptLoader); } } @@ -49,6 +49,8 @@ class Fixture { class FakeSentryScriptLoader extends SentryScriptLoader { int loadScriptsCalls = 0; + FakeSentryScriptLoader(super.options, super.scripts); + @override Future load() async { loadScriptsCalls += 1; diff --git a/flutter/test/web/sentry_script_loader_test.dart b/flutter/test/web/sentry_script_loader_test.dart index 032cbd3973..51e64219e2 100644 --- a/flutter/test/web/sentry_script_loader_test.dart +++ b/flutter/test/web/sentry_script_loader_test.dart @@ -6,13 +6,12 @@ import 'dart:html'; import 'package:flutter_test/flutter_test.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:sentry_flutter/src/web/script_loader/sentry_script_loader.dart'; -import 'package:sentry_flutter/src/web/script_loader/sentry_script_loader_impl.dart'; import 'package:sentry_flutter/src/web/sentry_js_sdk_version.dart'; import '../mocks.dart'; void main() { - group('$SentryScriptLoaderImpl', () { + group('$SentryScriptLoader', () { late Fixture fixture; setUp(() { @@ -97,7 +96,7 @@ class Fixture { SentryScriptLoader getSut( {bool debug = false, List>? scripts}) { options.platformChecker = MockPlatformChecker(isDebug: debug); - return SentryScriptLoaderImpl( + return SentryScriptLoader( options, debug ? debugScripts : scripts ?? productionScripts); } } From c3328a4fbab0e9431074ae35dbb2f905f4fc5f49 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 20 Nov 2024 15:55:44 +0100 Subject: [PATCH 12/76] fix test --- .../web/html_sentry_script_loader_test.dart | 24 +++++++++++++++ flutter/test/web/noop_script_dom_api.dart | 3 ++ flutter/test/web/script_dom_api.dart | 8 +++++ .../test/web/sentry_script_loader_test.dart | 23 +++++++-------- flutter/test/web/web_script_dom_api.dart | 29 +++++++++++++++++++ 5 files changed, 74 insertions(+), 13 deletions(-) create mode 100644 flutter/test/web/html_sentry_script_loader_test.dart create mode 100644 flutter/test/web/noop_script_dom_api.dart create mode 100644 flutter/test/web/script_dom_api.dart create mode 100644 flutter/test/web/web_script_dom_api.dart diff --git a/flutter/test/web/html_sentry_script_loader_test.dart b/flutter/test/web/html_sentry_script_loader_test.dart new file mode 100644 index 0000000000..f8f900d805 --- /dev/null +++ b/flutter/test/web/html_sentry_script_loader_test.dart @@ -0,0 +1,24 @@ +import 'dart:html'; + +import 'script_dom_api.dart'; + +class HtmlScriptElement implements SentryScriptElement { + final ScriptElement element; + + HtmlScriptElement(this.element); + + @override + void remove() { + element.remove(); + } + + @override + String get src => element.src; +} + +List querySelectorAll(String query) { + final scripts = document.querySelectorAll(query); + return scripts + .map((script) => HtmlScriptElement(script as ScriptElement)) + .toList(); +} diff --git a/flutter/test/web/noop_script_dom_api.dart b/flutter/test/web/noop_script_dom_api.dart new file mode 100644 index 0000000000..d3630f1152 --- /dev/null +++ b/flutter/test/web/noop_script_dom_api.dart @@ -0,0 +1,3 @@ +import 'script_dom_api.dart'; + +List querySelectorAll(String query) => []; diff --git a/flutter/test/web/script_dom_api.dart b/flutter/test/web/script_dom_api.dart new file mode 100644 index 0000000000..6804de971a --- /dev/null +++ b/flutter/test/web/script_dom_api.dart @@ -0,0 +1,8 @@ +export 'noop_script_dom_api.dart' + if (dart.library.html) 'html_sentry_script_loader_test.dart' + if (dart.library.js_interop) 'web_script_dom_api.dart'; + +abstract class SentryScriptElement { + String get src; + void remove(); +} diff --git a/flutter/test/web/sentry_script_loader_test.dart b/flutter/test/web/sentry_script_loader_test.dart index 51e64219e2..e6c7a0fc4e 100644 --- a/flutter/test/web/sentry_script_loader_test.dart +++ b/flutter/test/web/sentry_script_loader_test.dart @@ -1,14 +1,13 @@ @TestOn('browser') library flutter_test; -import 'dart:html'; - import 'package:flutter_test/flutter_test.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:sentry_flutter/src/web/script_loader/sentry_script_loader.dart'; import 'package:sentry_flutter/src/web/sentry_js_sdk_version.dart'; import '../mocks.dart'; +import 'script_dom_api.dart'; void main() { group('$SentryScriptLoader', () { @@ -19,8 +18,7 @@ void main() { }); tearDown(() { - final existingScripts = - document.querySelectorAll('script[src*="sentry-cdn"]'); + final existingScripts = querySelectorAll('script[src*="sentry-cdn"]'); for (final script in existingScripts) { script.remove(); } @@ -31,9 +29,9 @@ void main() { await sut.load(); - final scripts = document.querySelectorAll('script[src*="sentry-cdn"]'); + final scripts = querySelectorAll('script[src*="sentry-cdn"]'); for (final script in scripts) { - final element = script as ScriptElement; + final element = script; expect(element.src, contains('.min.js')); } }); @@ -43,9 +41,9 @@ void main() { await sut.load(); - final scripts = document.querySelectorAll('script[src*="sentry-cdn"]'); + final scripts = querySelectorAll('script[src*="sentry-cdn"]'); for (final script in scripts) { - final element = script as ScriptElement; + final element = script; expect(element.src, isNot(contains('.min.js'))); } }); @@ -54,10 +52,10 @@ void main() { final sut = fixture.getSut(); await sut.load(); - final initialScriptCount = document.querySelectorAll('script').length; + final initialScriptCount = querySelectorAll('script').length; await sut.load(); - expect(document.querySelectorAll('script').length, initialScriptCount); + expect(querySelectorAll('script').length, initialScriptCount); }); test('handles script loading failures', () async { @@ -80,9 +78,8 @@ void main() { await sut.load(); - final scripts = document - .querySelectorAll('script[src*="sentry-cdn"]') - .map((s) => (s as ScriptElement).src) + final scripts = querySelectorAll('script[src*="sentry-cdn"]') + .map((s) => (s).src) .toList(); expect(scripts[0], contains('bundle.tracing.replay')); expect(scripts[1], contains('replay-canvas')); diff --git a/flutter/test/web/web_script_dom_api.dart b/flutter/test/web/web_script_dom_api.dart new file mode 100644 index 0000000000..f6a67eecc1 --- /dev/null +++ b/flutter/test/web/web_script_dom_api.dart @@ -0,0 +1,29 @@ +import 'dart:js_interop'; + +// ignore: depend_on_referenced_packages +import 'package:web/web.dart'; + +import 'script_dom_api.dart'; + +class _ScriptElement implements SentryScriptElement { + final HTMLScriptElement element; + + _ScriptElement(this.element); + + @override + void remove() { + element.remove(); + } + + @override + String get src => element.src; +} + +List querySelectorAll(String query) { + final scripts = document.querySelectorAll(query); + // ignore: sdk_version_since + final jsArray = JSArray.from(scripts); + return jsArray.toDart + .map((script) => _ScriptElement(script as HTMLScriptElement)) + .toList(); +} From f64f749a4626375b26e62b7f653782f0085eb021 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 20 Nov 2024 16:00:42 +0100 Subject: [PATCH 13/76] fix test --- .../web/html_sentry_script_loader_test.dart | 24 ------------------- 1 file changed, 24 deletions(-) delete mode 100644 flutter/test/web/html_sentry_script_loader_test.dart diff --git a/flutter/test/web/html_sentry_script_loader_test.dart b/flutter/test/web/html_sentry_script_loader_test.dart deleted file mode 100644 index f8f900d805..0000000000 --- a/flutter/test/web/html_sentry_script_loader_test.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'dart:html'; - -import 'script_dom_api.dart'; - -class HtmlScriptElement implements SentryScriptElement { - final ScriptElement element; - - HtmlScriptElement(this.element); - - @override - void remove() { - element.remove(); - } - - @override - String get src => element.src; -} - -List querySelectorAll(String query) { - final scripts = document.querySelectorAll(query); - return scripts - .map((script) => HtmlScriptElement(script as ScriptElement)) - .toList(); -} From 9aba0c4ffc61c2c658e9d30acca2b929ed2e7a42 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 20 Nov 2024 16:03:56 +0100 Subject: [PATCH 14/76] update --- flutter/test/web/html_script_dom_api.dart | 23 +++++++++++++++++++++++ flutter/test/web/script_dom_api.dart | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 flutter/test/web/html_script_dom_api.dart diff --git a/flutter/test/web/html_script_dom_api.dart b/flutter/test/web/html_script_dom_api.dart new file mode 100644 index 0000000000..1f7e0b1fc4 --- /dev/null +++ b/flutter/test/web/html_script_dom_api.dart @@ -0,0 +1,23 @@ +import 'dart:html'; + +import 'script_dom_api.dart'; + +class HtmlScriptElement implements SentryScriptElement { + final ScriptElement element; + + HtmlScriptElement(this.element); + + @override + void remove() { + element.remove(); + } + + @override + String get src => element.src; +} + +List querySelectorAll(String query) { + final scripts = document.querySelectorAll(query); + return scripts + .map((script) => HtmlScriptElement(script as ScriptElement)) + .toList(); diff --git a/flutter/test/web/script_dom_api.dart b/flutter/test/web/script_dom_api.dart index 6804de971a..3066e4cde2 100644 --- a/flutter/test/web/script_dom_api.dart +++ b/flutter/test/web/script_dom_api.dart @@ -1,5 +1,5 @@ export 'noop_script_dom_api.dart' - if (dart.library.html) 'html_sentry_script_loader_test.dart' + if (dart.library.html) 'html_script_dom_api.dart' if (dart.library.js_interop) 'web_script_dom_api.dart'; abstract class SentryScriptElement { From 818f8a47fc973bca2a6e5b68b57e2cfacff82344 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 20 Nov 2024 16:06:22 +0100 Subject: [PATCH 15/76] update --- flutter/test/web/html_script_dom_api.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/flutter/test/web/html_script_dom_api.dart b/flutter/test/web/html_script_dom_api.dart index 1f7e0b1fc4..f8f900d805 100644 --- a/flutter/test/web/html_script_dom_api.dart +++ b/flutter/test/web/html_script_dom_api.dart @@ -21,3 +21,4 @@ List querySelectorAll(String query) { return scripts .map((script) => HtmlScriptElement(script as ScriptElement)) .toList(); +} From 15bb56c82bb50eab1cda21652e0a9474147bc048 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 20 Nov 2024 16:30:31 +0100 Subject: [PATCH 16/76] update --- flutter/pubspec.yaml | 1 + flutter/test/web/web_script_dom_api.dart | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index dbc63000d2..acdd7040bc 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -38,6 +38,7 @@ dev_dependencies: flutter_lints: '>=4.0.0' collection: ^1.16.0 remove_from_coverage: ^2.0.0 + web: ^1.1.0 flutter_localizations: sdk: flutter ffigen: diff --git a/flutter/test/web/web_script_dom_api.dart b/flutter/test/web/web_script_dom_api.dart index f6a67eecc1..11e7171acc 100644 --- a/flutter/test/web/web_script_dom_api.dart +++ b/flutter/test/web/web_script_dom_api.dart @@ -1,6 +1,5 @@ import 'dart:js_interop'; -// ignore: depend_on_referenced_packages import 'package:web/web.dart'; import 'script_dom_api.dart'; From e2b22515d093b851da85dfd7b6bbfed7c8091694 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 20 Nov 2024 16:33:25 +0100 Subject: [PATCH 17/76] fix analyze --- flutter/test/web/web_script_dom_api.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/test/web/web_script_dom_api.dart b/flutter/test/web/web_script_dom_api.dart index 11e7171acc..824eb2fe55 100644 --- a/flutter/test/web/web_script_dom_api.dart +++ b/flutter/test/web/web_script_dom_api.dart @@ -23,6 +23,6 @@ List querySelectorAll(String query) { // ignore: sdk_version_since final jsArray = JSArray.from(scripts); return jsArray.toDart - .map((script) => _ScriptElement(script as HTMLScriptElement)) + .map((JSAny script) => _ScriptElement(script as HTMLScriptElement)) .toList(); } From adad5d92f2d8a1d9eb1d17a80fcf79bd1ea5f419 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 20 Nov 2024 16:49:43 +0100 Subject: [PATCH 18/76] update --- flutter/pubspec.yaml | 1 - flutter/test/web/web_script_dom_api.dart | 16 +++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index acdd7040bc..dbc63000d2 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -38,7 +38,6 @@ dev_dependencies: flutter_lints: '>=4.0.0' collection: ^1.16.0 remove_from_coverage: ^2.0.0 - web: ^1.1.0 flutter_localizations: sdk: flutter ffigen: diff --git a/flutter/test/web/web_script_dom_api.dart b/flutter/test/web/web_script_dom_api.dart index 824eb2fe55..b413349d57 100644 --- a/flutter/test/web/web_script_dom_api.dart +++ b/flutter/test/web/web_script_dom_api.dart @@ -1,5 +1,4 @@ -import 'dart:js_interop'; - +// ignore: depend_on_referenced_packages import 'package:web/web.dart'; import 'script_dom_api.dart'; @@ -20,9 +19,12 @@ class _ScriptElement implements SentryScriptElement { List querySelectorAll(String query) { final scripts = document.querySelectorAll(query); - // ignore: sdk_version_since - final jsArray = JSArray.from(scripts); - return jsArray.toDart - .map((JSAny script) => _ScriptElement(script as HTMLScriptElement)) - .toList(); + + List elements = []; + for (int i = 0; i < scripts.length; i++) { + final node = scripts.item(i); + elements.add(_ScriptElement(node as HTMLScriptElement)); + } + + return elements; } From e01a5493bfb888bdfb3ad91dc7606e6fb331e242 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 20 Nov 2024 17:20:16 +0100 Subject: [PATCH 19/76] update --- flutter/lib/src/sentry_flutter.dart | 2 +- flutter/lib/src/web/sentry_js_bundle.dart | 21 ++++++++++++ .../lib/src/web/sentry_js_sdk_version.dart | 32 ------------------- flutter/pubspec.yaml | 1 + .../{ => dom_api}/html_script_dom_api.dart | 0 .../{ => dom_api}/noop_script_dom_api.dart | 0 .../web/{ => dom_api}/script_dom_api.dart | 0 .../web/{ => dom_api}/web_script_dom_api.dart | 0 flutter/test/web/sentry_js_bundles_test.dart | 27 ++++++++++++++++ .../test/web/sentry_script_loader_test.dart | 4 +-- .../bin/publish_validation.dart | 2 ++ 11 files changed, 54 insertions(+), 35 deletions(-) create mode 100644 flutter/lib/src/web/sentry_js_bundle.dart delete mode 100644 flutter/lib/src/web/sentry_js_sdk_version.dart rename flutter/test/web/{ => dom_api}/html_script_dom_api.dart (100%) rename flutter/test/web/{ => dom_api}/noop_script_dom_api.dart (100%) rename flutter/test/web/{ => dom_api}/script_dom_api.dart (100%) rename flutter/test/web/{ => dom_api}/web_script_dom_api.dart (100%) create mode 100644 flutter/test/web/sentry_js_bundles_test.dart diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index b1d3a39ca0..8283825b5e 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -29,7 +29,7 @@ import 'renderer/renderer.dart'; import 'version.dart'; import 'view_hierarchy/view_hierarchy_integration.dart'; import 'web/script_loader/sentry_script_loader.dart'; -import 'web/sentry_js_sdk_version.dart'; +import 'web/sentry_js_bundle.dart'; /// Configuration options callback typedef FlutterOptionsConfiguration = FutureOr Function( diff --git a/flutter/lib/src/web/sentry_js_bundle.dart b/flutter/lib/src/web/sentry_js_bundle.dart new file mode 100644 index 0000000000..7cb364e5e4 --- /dev/null +++ b/flutter/lib/src/web/sentry_js_bundle.dart @@ -0,0 +1,21 @@ +import 'package:meta/meta.dart'; + +// todo: set up ci to update this and the integrity +@internal +const jsSdkVersion = '8.39.0'; + +@internal +const productionScripts = [ + { + 'url': 'https://browser.sentry-cdn.com/$jsSdkVersion/bundle.tracing.min.js', + 'integrity': + 'sha384-GmCdCRO2s8VuYopAldiuHl/uns+EWDcLodj8AW810pK14r3vPQxoDNsMxnitCt18' + } +]; + +@internal +const debugScripts = [ + { + 'url': 'https://browser.sentry-cdn.com/$jsSdkVersion/bundle.tracing.js', + }, +]; diff --git a/flutter/lib/src/web/sentry_js_sdk_version.dart b/flutter/lib/src/web/sentry_js_sdk_version.dart deleted file mode 100644 index 438fb329c2..0000000000 --- a/flutter/lib/src/web/sentry_js_sdk_version.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:meta/meta.dart'; - -// todo: set up ci to update this and the integrity -@internal -const jsSdkVersion = '8.37.1'; - -@internal -const productionScripts = [ - { - 'url': - 'https://browser.sentry-cdn.com/$jsSdkVersion/bundle.tracing.replay.min.js', - 'integrity': - 'sha384-IZS0kTfvAku3LBcvcHWThKT6lKBimvLUVNZgqF/jtmVAw99L25MM+RhAnozr6iVY' - }, - { - // todo: might need to be adjusted based on renderer (canvas vs html) later on - 'url': 'https://browser.sentry-cdn.com/$jsSdkVersion/replay-canvas.min.js', - 'integrity': - 'sha384-UNUCiMVh5gTr9Z45bRUPU5eOHHKGOI80UV3zM858k7yV/c6NNhtSJnIDjh+jJ8Vk' - }, -]; - -@internal -const debugScripts = [ - { - 'url': - 'https://browser.sentry-cdn.com/$jsSdkVersion/bundle.tracing.replay.js', - }, - { - 'url': 'https://browser.sentry-cdn.com/$jsSdkVersion/replay-canvas.js', - }, -]; diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index dbc63000d2..0a0ca83847 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -38,6 +38,7 @@ dev_dependencies: flutter_lints: '>=4.0.0' collection: ^1.16.0 remove_from_coverage: ^2.0.0 + http: ^1.2.2 # check if js sdk cdn bundle exists flutter_localizations: sdk: flutter ffigen: diff --git a/flutter/test/web/html_script_dom_api.dart b/flutter/test/web/dom_api/html_script_dom_api.dart similarity index 100% rename from flutter/test/web/html_script_dom_api.dart rename to flutter/test/web/dom_api/html_script_dom_api.dart diff --git a/flutter/test/web/noop_script_dom_api.dart b/flutter/test/web/dom_api/noop_script_dom_api.dart similarity index 100% rename from flutter/test/web/noop_script_dom_api.dart rename to flutter/test/web/dom_api/noop_script_dom_api.dart diff --git a/flutter/test/web/script_dom_api.dart b/flutter/test/web/dom_api/script_dom_api.dart similarity index 100% rename from flutter/test/web/script_dom_api.dart rename to flutter/test/web/dom_api/script_dom_api.dart diff --git a/flutter/test/web/web_script_dom_api.dart b/flutter/test/web/dom_api/web_script_dom_api.dart similarity index 100% rename from flutter/test/web/web_script_dom_api.dart rename to flutter/test/web/dom_api/web_script_dom_api.dart diff --git a/flutter/test/web/sentry_js_bundles_test.dart b/flutter/test/web/sentry_js_bundles_test.dart new file mode 100644 index 0000000000..1151f81f7b --- /dev/null +++ b/flutter/test/web/sentry_js_bundles_test.dart @@ -0,0 +1,27 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart' as http; +import 'package:sentry_flutter/src/web/sentry_js_bundle.dart'; + +void main() { + group('Sentry Js Bundles', () { + Future checkScript(Map script) async { + final response = await http.get(Uri.parse(script['url']!)); + + expect(response.statusCode, 200); + expect(response.body, isNotEmpty); + } + + test('production script is accessible', () async { + await Future.forEach(productionScripts, + (Map script) async { + await checkScript(script); + }); + }); + + test('debug script is accessible', () async { + await Future.forEach(debugScripts, (Map script) async { + await checkScript(script); + }); + }); + }); +} diff --git a/flutter/test/web/sentry_script_loader_test.dart b/flutter/test/web/sentry_script_loader_test.dart index e6c7a0fc4e..a2e6c3bbbe 100644 --- a/flutter/test/web/sentry_script_loader_test.dart +++ b/flutter/test/web/sentry_script_loader_test.dart @@ -4,10 +4,10 @@ library flutter_test; import 'package:flutter_test/flutter_test.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:sentry_flutter/src/web/script_loader/sentry_script_loader.dart'; -import 'package:sentry_flutter/src/web/sentry_js_sdk_version.dart'; +import 'package:sentry_flutter/src/web/sentry_js_bundle.dart'; import '../mocks.dart'; -import 'script_dom_api.dart'; +import 'dom_api/script_dom_api.dart'; void main() { group('$SentryScriptLoader', () { diff --git a/scripts/publish_validation/bin/publish_validation.dart b/scripts/publish_validation/bin/publish_validation.dart index 0585d7dd00..b73ac054a9 100644 --- a/scripts/publish_validation/bin/publish_validation.dart +++ b/scripts/publish_validation/bin/publish_validation.dart @@ -36,6 +36,8 @@ void main(List arguments) async { '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/event_processor/url_filter/web_url_filter_event_processor.dart: This package does not have web in the `dependencies` section of `pubspec.yaml`', + 'lib/src/web/script_loader/web_script_dom_api.dart: This package does not have web in the `dependencies` section of `pubspec.yaml`', + 'test/web/web_script_dom_api.dart: This package does not have web in the `dependencies` or `dev_dependencies` section of `pubspec.yaml`' ]; // So far the expected errors all start with `* line` From 818d56e70c72e3dd28e0413fde8cef3a04df4c8d Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 20 Nov 2024 17:24:05 +0100 Subject: [PATCH 20/76] update validation --- scripts/publish_validation/bin/publish_validation.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/publish_validation/bin/publish_validation.dart b/scripts/publish_validation/bin/publish_validation.dart index b73ac054a9..4e63077a9f 100644 --- a/scripts/publish_validation/bin/publish_validation.dart +++ b/scripts/publish_validation/bin/publish_validation.dart @@ -37,7 +37,7 @@ void main(List arguments) async { '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`', 'lib/src/web/script_loader/web_script_dom_api.dart: This package does not have web in the `dependencies` section of `pubspec.yaml`', - 'test/web/web_script_dom_api.dart: This package does not have web in the `dependencies` or `dev_dependencies` section of `pubspec.yaml`' + 'test/web/dom_api/web_script_dom_api.dart: This package does not have web in the `dependencies` or `dev_dependencies` section of `pubspec.yaml`' ]; // So far the expected errors all start with `* line` From bd3c46eab61fa4e9a4155bcfd478d2039bb9f610 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 20 Nov 2024 17:25:32 +0100 Subject: [PATCH 21/76] update --- flutter/test/web/sentry_script_loader_test.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/flutter/test/web/sentry_script_loader_test.dart b/flutter/test/web/sentry_script_loader_test.dart index a2e6c3bbbe..223676c18a 100644 --- a/flutter/test/web/sentry_script_loader_test.dart +++ b/flutter/test/web/sentry_script_loader_test.dart @@ -81,8 +81,9 @@ void main() { final scripts = querySelectorAll('script[src*="sentry-cdn"]') .map((s) => (s).src) .toList(); - expect(scripts[0], contains('bundle.tracing.replay')); - expect(scripts[1], contains('replay-canvas')); + expect(productionScripts.length, scripts.length); + expect(debugScripts.length, scripts.length); + expect(scripts[0], contains('bundle.tracing')); }); }); } From 797097b11ab76bc28b75be5182bd8a16cdf5c198 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 20 Nov 2024 17:26:09 +0100 Subject: [PATCH 22/76] update --- flutter/test/web/sentry_js_bundles_test.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/flutter/test/web/sentry_js_bundles_test.dart b/flutter/test/web/sentry_js_bundles_test.dart index 1151f81f7b..22fc834b3a 100644 --- a/flutter/test/web/sentry_js_bundles_test.dart +++ b/flutter/test/web/sentry_js_bundles_test.dart @@ -1,3 +1,6 @@ +@TestOn('browser') +library flutter_test; + import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; import 'package:sentry_flutter/src/web/sentry_js_bundle.dart'; From 009c8ea58f4dd4e9e7f37fa28a2cc772f9618d60 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 20 Nov 2024 23:59:52 +0100 Subject: [PATCH 23/76] update --- flutter/lib/src/integrations/web_sdk_integration.dart | 3 +-- flutter/test/web/dom_api/html_script_dom_api.dart | 4 ++-- flutter/test/web/dom_api/noop_script_dom_api.dart | 2 +- flutter/test/web/dom_api/script_dom_api.dart | 2 +- flutter/test/web/dom_api/web_script_dom_api.dart | 6 +++--- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/flutter/lib/src/integrations/web_sdk_integration.dart b/flutter/lib/src/integrations/web_sdk_integration.dart index 51729f5edc..2ac8856342 100644 --- a/flutter/lib/src/integrations/web_sdk_integration.dart +++ b/flutter/lib/src/integrations/web_sdk_integration.dart @@ -5,7 +5,6 @@ import 'package:meta/meta.dart'; import '../../sentry_flutter.dart'; import '../web/script_loader/sentry_script_loader.dart'; -/// Initializes the Javascript SDK with the given options. class WebSdkIntegration implements Integration { WebSdkIntegration(this._scriptLoader); @@ -32,6 +31,6 @@ class WebSdkIntegration implements Integration { @override FutureOr close() { - // no-op for now + // no-op } } diff --git a/flutter/test/web/dom_api/html_script_dom_api.dart b/flutter/test/web/dom_api/html_script_dom_api.dart index f8f900d805..6ca4d446a6 100644 --- a/flutter/test/web/dom_api/html_script_dom_api.dart +++ b/flutter/test/web/dom_api/html_script_dom_api.dart @@ -2,7 +2,7 @@ import 'dart:html'; import 'script_dom_api.dart'; -class HtmlScriptElement implements SentryScriptElement { +class HtmlScriptElement implements TestScriptElement { final ScriptElement element; HtmlScriptElement(this.element); @@ -16,7 +16,7 @@ class HtmlScriptElement implements SentryScriptElement { String get src => element.src; } -List querySelectorAll(String query) { +List querySelectorAll(String query) { final scripts = document.querySelectorAll(query); return scripts .map((script) => HtmlScriptElement(script as ScriptElement)) diff --git a/flutter/test/web/dom_api/noop_script_dom_api.dart b/flutter/test/web/dom_api/noop_script_dom_api.dart index d3630f1152..92259f3bf9 100644 --- a/flutter/test/web/dom_api/noop_script_dom_api.dart +++ b/flutter/test/web/dom_api/noop_script_dom_api.dart @@ -1,3 +1,3 @@ import 'script_dom_api.dart'; -List querySelectorAll(String query) => []; +List querySelectorAll(String query) => []; diff --git a/flutter/test/web/dom_api/script_dom_api.dart b/flutter/test/web/dom_api/script_dom_api.dart index 3066e4cde2..e3369d16d9 100644 --- a/flutter/test/web/dom_api/script_dom_api.dart +++ b/flutter/test/web/dom_api/script_dom_api.dart @@ -2,7 +2,7 @@ export 'noop_script_dom_api.dart' if (dart.library.html) 'html_script_dom_api.dart' if (dart.library.js_interop) 'web_script_dom_api.dart'; -abstract class SentryScriptElement { +abstract class TestScriptElement { String get src; void remove(); } diff --git a/flutter/test/web/dom_api/web_script_dom_api.dart b/flutter/test/web/dom_api/web_script_dom_api.dart index b413349d57..c16cd57231 100644 --- a/flutter/test/web/dom_api/web_script_dom_api.dart +++ b/flutter/test/web/dom_api/web_script_dom_api.dart @@ -3,7 +3,7 @@ import 'package:web/web.dart'; import 'script_dom_api.dart'; -class _ScriptElement implements SentryScriptElement { +class _ScriptElement implements TestScriptElement { final HTMLScriptElement element; _ScriptElement(this.element); @@ -17,10 +17,10 @@ class _ScriptElement implements SentryScriptElement { String get src => element.src; } -List querySelectorAll(String query) { +List querySelectorAll(String query) { final scripts = document.querySelectorAll(query); - List elements = []; + List elements = []; for (int i = 0; i < scripts.length; i++) { final node = scripts.item(i); elements.add(_ScriptElement(node as HTMLScriptElement)); From 3b3f78c7afda442a8378cfe71cb1bf176e717d84 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Thu, 21 Nov 2024 00:04:38 +0100 Subject: [PATCH 24/76] rethrow on automated test mode --- flutter/lib/src/integrations/web_sdk_integration.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/flutter/lib/src/integrations/web_sdk_integration.dart b/flutter/lib/src/integrations/web_sdk_integration.dart index 2ac8856342..0bb0f32cfd 100644 --- a/flutter/lib/src/integrations/web_sdk_integration.dart +++ b/flutter/lib/src/integrations/web_sdk_integration.dart @@ -20,6 +20,9 @@ class WebSdkIntegration implements Integration { options.sdk.addIntegration(name); } catch (exception, stackTrace) { + if (options.automatedTestMode) { + rethrow; + } options.logger( SentryLevel.fatal, '$name failed to be installed', From 8be205990016f736fed4ce4ed5ce87345580eb71 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Thu, 21 Nov 2024 00:07:22 +0100 Subject: [PATCH 25/76] update rethrow --- flutter/lib/src/integrations/web_sdk_integration.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flutter/lib/src/integrations/web_sdk_integration.dart b/flutter/lib/src/integrations/web_sdk_integration.dart index 0bb0f32cfd..d91ab22c8d 100644 --- a/flutter/lib/src/integrations/web_sdk_integration.dart +++ b/flutter/lib/src/integrations/web_sdk_integration.dart @@ -20,15 +20,15 @@ class WebSdkIntegration implements Integration { options.sdk.addIntegration(name); } catch (exception, stackTrace) { - if (options.automatedTestMode) { - rethrow; - } options.logger( SentryLevel.fatal, '$name failed to be installed', exception: exception, stackTrace: stackTrace, ); + if (options.automatedTestMode) { + rethrow; + } } } From 8de74d3c05ea02107fe77fcfeb6ab667c5983ae0 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Mon, 25 Nov 2024 15:06:10 +0100 Subject: [PATCH 26/76] update --- .../lib/src/web/script_loader/sentry_script_loader.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/flutter/lib/src/web/script_loader/sentry_script_loader.dart b/flutter/lib/src/web/script_loader/sentry_script_loader.dart index 4345704fa7..823bac7f65 100644 --- a/flutter/lib/src/web/script_loader/sentry_script_loader.dart +++ b/flutter/lib/src/web/script_loader/sentry_script_loader.dart @@ -20,14 +20,15 @@ class SentryScriptLoader { if (_scriptLoaded) return; try { - await Future.wait(scripts.map((script) async { + await Future.forEach(scripts, (Map script) async { final url = script['url']; final integrity = script['integrity']; if (url != null) { - return loadScript(url, integrity); + await loadScript(url, integrity); } - })); + }); + _scriptLoaded = true; options.logger(SentryLevel.debug, 'JS SDK integration: all Sentry scripts loaded successfully.'); From ce022462d9dd284430ebcf4bdd0f3a36c4fcb4fe Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Mon, 25 Nov 2024 15:15:57 +0100 Subject: [PATCH 27/76] update tests --- .../test/web/sentry_script_loader_test.dart | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/flutter/test/web/sentry_script_loader_test.dart b/flutter/test/web/sentry_script_loader_test.dart index 223676c18a..f737a97071 100644 --- a/flutter/test/web/sentry_script_loader_test.dart +++ b/flutter/test/web/sentry_script_loader_test.dart @@ -59,6 +59,9 @@ void main() { }); test('handles script loading failures', () async { + // don't rethrow errors + fixture.options.automatedTestMode = false; + final scripts = [ { 'url': 'https://invalid', @@ -68,22 +71,8 @@ void main() { // Modify script URL to cause failure final sut = fixture.getSut(scripts: scripts); - await expectLater(() async { - await sut.load(); - }, throwsA(anything)); - }); - - test('maintains script loading order', () async { - final sut = fixture.getSut(); - - await sut.load(); - - final scripts = querySelectorAll('script[src*="sentry-cdn"]') - .map((s) => (s).src) - .toList(); - expect(productionScripts.length, scripts.length); - expect(debugScripts.length, scripts.length); - expect(scripts[0], contains('bundle.tracing')); + // Expect the load() call to complete without throwing + await expectLater(sut.load(), completes); }); }); } From fca9add7d0431614f78af07f9685a826d76d041a Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 27 Nov 2024 13:32:03 +0100 Subject: [PATCH 28/76] update appending --- .../src/integrations/web_sdk_integration.dart | 6 ++- flutter/lib/src/sentry_flutter.dart | 6 +-- .../script_loader/html_script_dom_api.dart | 10 ++++- .../script_loader/sentry_script_loader.dart | 5 +-- .../test/web/sentry_script_loader_test.dart | 44 +++++++++++++------ 5 files changed, 48 insertions(+), 23 deletions(-) diff --git a/flutter/lib/src/integrations/web_sdk_integration.dart b/flutter/lib/src/integrations/web_sdk_integration.dart index d91ab22c8d..f07fbd1341 100644 --- a/flutter/lib/src/integrations/web_sdk_integration.dart +++ b/flutter/lib/src/integrations/web_sdk_integration.dart @@ -4,6 +4,7 @@ import 'package:meta/meta.dart'; import '../../sentry_flutter.dart'; import '../web/script_loader/sentry_script_loader.dart'; +import '../web/sentry_js_bundle.dart'; class WebSdkIntegration implements Integration { WebSdkIntegration(this._scriptLoader); @@ -16,7 +17,10 @@ class WebSdkIntegration implements Integration { @override FutureOr call(Hub hub, SentryFlutterOptions options) async { try { - await _scriptLoader.load(); + final scripts = options.platformChecker.isDebugMode() + ? debugScripts + : productionScripts; + await _scriptLoader.load(scripts); options.sdk.addIntegration(name); } catch (exception, stackTrace) { diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index 8283825b5e..207319e99d 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -29,7 +29,6 @@ import 'renderer/renderer.dart'; import 'version.dart'; import 'view_hierarchy/view_hierarchy_integration.dart'; import 'web/script_loader/sentry_script_loader.dart'; -import 'web/sentry_js_bundle.dart'; /// Configuration options callback typedef FlutterOptionsConfiguration = FutureOr Function( @@ -183,10 +182,7 @@ mixin SentryFlutter { } if (platformChecker.isWeb) { - final scripts = options.platformChecker.isDebugMode() - ? debugScripts - : productionScripts; - final scriptLoader = SentryScriptLoader(options, scripts); + final scriptLoader = SentryScriptLoader(options); integrations.add(WebSdkIntegration(scriptLoader)); integrations.add(ConnectivityIntegration()); } diff --git a/flutter/lib/src/web/script_loader/html_script_dom_api.dart b/flutter/lib/src/web/script_loader/html_script_dom_api.dart index c84423be14..3448190a43 100644 --- a/flutter/lib/src/web/script_loader/html_script_dom_api.dart +++ b/flutter/lib/src/web/script_loader/html_script_dom_api.dart @@ -13,6 +13,14 @@ Future loadScript(String src, String? integrity) { script.integrity = integrity; } - document.head?.append(script); + // JS SDK needs to be loaded before everything else + final head = document.head; + if (head != null) { + if (head.hasChildNodes()) { + head.insertBefore(script, head.firstChild); + } else { + head.append(script); + } + } return completer.future; } diff --git a/flutter/lib/src/web/script_loader/sentry_script_loader.dart b/flutter/lib/src/web/script_loader/sentry_script_loader.dart index 823bac7f65..f8c24d3465 100644 --- a/flutter/lib/src/web/script_loader/sentry_script_loader.dart +++ b/flutter/lib/src/web/script_loader/sentry_script_loader.dart @@ -7,16 +7,15 @@ import 'script_dom_api.dart'; @internal class SentryScriptLoader { - SentryScriptLoader(this.options, this.scripts); + SentryScriptLoader(this.options); final SentryFlutterOptions options; - final List> scripts; bool _scriptLoaded = false; /// Loads scripts into the document asynchronously. /// /// Idempotent: does nothing if scripts are already loaded. - Future load() async { + Future load(List> scripts) async { if (_scriptLoaded) return; try { diff --git a/flutter/test/web/sentry_script_loader_test.dart b/flutter/test/web/sentry_script_loader_test.dart index f737a97071..f94cd14c2f 100644 --- a/flutter/test/web/sentry_script_loader_test.dart +++ b/flutter/test/web/sentry_script_loader_test.dart @@ -27,7 +27,7 @@ void main() { test('loads production scripts by default', () async { final sut = fixture.getSut(); - await sut.load(); + await sut.load(productionScripts); final scripts = querySelectorAll('script[src*="sentry-cdn"]'); for (final script in scripts) { @@ -39,7 +39,7 @@ void main() { test('loads debug scripts when debug is enabled', () async { final sut = fixture.getSut(debug: true); - await sut.load(); + await sut.load(debugScripts); final scripts = querySelectorAll('script[src*="sentry-cdn"]'); for (final script in scripts) { @@ -51,28 +51,48 @@ void main() { test('does not load scripts twice', () async { final sut = fixture.getSut(); - await sut.load(); + await sut.load(productionScripts); final initialScriptCount = querySelectorAll('script').length; - await sut.load(); + await sut.load(productionScripts); expect(querySelectorAll('script').length, initialScriptCount); }); test('handles script loading failures', () async { - // don't rethrow errors + final failingScripts = [ + { + 'url': 'https://invalid', + }, + ]; + + // Modify script URL to cause failure + final sut = fixture.getSut(); + + expect(() async => await sut.load(failingScripts), throwsA(anything)); + await sut.load(productionScripts); + + final scripts = querySelectorAll('script[src*="sentry-cdn"]'); + expect(scripts.first.src, contains('.min.js')); + }); + + test('handles script loading failures with automatedTestMode false', + () async { fixture.options.automatedTestMode = false; - final scripts = [ + final failingScripts = [ { 'url': 'https://invalid', }, ]; // Modify script URL to cause failure - final sut = fixture.getSut(scripts: scripts); + final sut = fixture.getSut(); + + await sut.load(failingScripts); + await sut.load(productionScripts); - // Expect the load() call to complete without throwing - await expectLater(sut.load(), completes); + final scripts = querySelectorAll('script[src*="sentry-cdn"]'); + expect(scripts.first.src, contains('.min.js')); }); }); } @@ -80,10 +100,8 @@ void main() { class Fixture { final SentryFlutterOptions options = defaultTestOptions(); - SentryScriptLoader getSut( - {bool debug = false, List>? scripts}) { + SentryScriptLoader getSut({bool debug = false}) { options.platformChecker = MockPlatformChecker(isDebug: debug); - return SentryScriptLoader( - options, debug ? debugScripts : scripts ?? productionScripts); + return SentryScriptLoader(options); } } From b3009c844d38ea29f8c5bd65b0bb63a771ca9c46 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 27 Nov 2024 13:32:57 +0100 Subject: [PATCH 29/76] update web --- .../lib/src/web/script_loader/web_script_dom_api.dart | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/flutter/lib/src/web/script_loader/web_script_dom_api.dart b/flutter/lib/src/web/script_loader/web_script_dom_api.dart index ac80cdc127..5ea17097c0 100644 --- a/flutter/lib/src/web/script_loader/web_script_dom_api.dart +++ b/flutter/lib/src/web/script_loader/web_script_dom_api.dart @@ -14,7 +14,14 @@ Future loadScript(String src, String? integrity) { if (integrity != null) { script.integrity = integrity; } - - document.head?.append(script); + // JS SDK needs to be loaded before everything else + final head = document.head; + if (head != null) { + if (head.hasChildNodes()) { + head.insertBefore(script, head.firstChild); + } else { + head.append(script); + } + } return completer.future; } From 15d16db292a2d5dbc255e2ea5ef12fe7c381dbf2 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Mon, 2 Dec 2024 17:24:27 +0100 Subject: [PATCH 30/76] add trusted types and add tests --- .../src/integrations/web_sdk_integration.dart | 2 +- flutter/lib/src/sentry_flutter.dart | 4 +- .../script_loader/html_script_dom_api.dart | 38 +++++++- .../script_loader/noop_script_dom_api.dart | 7 +- .../script_loader/sentry_script_loader.dart | 28 ++++-- .../web/script_loader/web_script_dom_api.dart | 42 ++++++++- flutter/lib/src/web/sentry_js_bundle.dart | 7 +- .../web_sdk_integration_test.dart | 12 ++- .../test/web/dom_api/html_script_dom_api.dart | 8 ++ .../test/web/dom_api/noop_script_dom_api.dart | 2 + .../test/web/dom_api/web_script_dom_api.dart | 9 ++ .../test/web/sentry_script_loader_test.dart | 89 ++++++++++++------- .../sentry_script_loader_tt_custom_test.dart | 54 +++++++++++ ...entry_script_loader_tt_forbidden_test.dart | 41 +++++++++ 14 files changed, 287 insertions(+), 56 deletions(-) create mode 100644 flutter/test/web/sentry_script_loader_tt_custom_test.dart create mode 100644 flutter/test/web/sentry_script_loader_tt_forbidden_test.dart diff --git a/flutter/lib/src/integrations/web_sdk_integration.dart b/flutter/lib/src/integrations/web_sdk_integration.dart index f07fbd1341..24e0838d03 100644 --- a/flutter/lib/src/integrations/web_sdk_integration.dart +++ b/flutter/lib/src/integrations/web_sdk_integration.dart @@ -20,7 +20,7 @@ class WebSdkIntegration implements Integration { final scripts = options.platformChecker.isDebugMode() ? debugScripts : productionScripts; - await _scriptLoader.load(scripts); + await _scriptLoader.loadWebSdk(scripts); options.sdk.addIntegration(name); } catch (exception, stackTrace) { diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index 207319e99d..49b24c33bb 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -182,8 +182,8 @@ mixin SentryFlutter { } if (platformChecker.isWeb) { - final scriptLoader = SentryScriptLoader(options); - integrations.add(WebSdkIntegration(scriptLoader)); + final loader = SentryScriptLoader(options); + integrations.add(WebSdkIntegration(loader)); integrations.add(ConnectivityIntegration()); } diff --git a/flutter/lib/src/web/script_loader/html_script_dom_api.dart b/flutter/lib/src/web/script_loader/html_script_dom_api.dart index 3448190a43..88e6a739ce 100644 --- a/flutter/lib/src/web/script_loader/html_script_dom_api.dart +++ b/flutter/lib/src/web/script_loader/html_script_dom_api.dart @@ -1,14 +1,48 @@ import 'dart:async'; import 'dart:html'; +import 'dart:js_util' as js_util; -Future loadScript(String src, String? integrity) { +import '../../../sentry_flutter.dart'; +import 'sentry_script_loader.dart'; + +Future loadScript(String src, SentryOptions? options, + {String? integrity, + String trustedTypePolicyName = defaultTrustedPolicyName}) { final completer = Completer(); + final script = ScriptElement() - ..src = src ..crossOrigin = 'anonymous' ..onLoad.listen((_) => completer.complete()) ..onError.listen((event) => completer.completeError('Failed to load $src')); + TrustedScriptUrl? trustedUrl; + + // If TrustedTypes are available, prepare a trusted URL + final trustedTypes = js_util.getProperty(window, 'trustedTypes'); + if (trustedTypes != null) { + try { + final policy = + js_util.callMethod(trustedTypes as Object, 'createPolicy', [ + trustedTypePolicyName, + js_util.jsify( + {'createScriptURL': js_util.allowInterop((String url) => src)}) + ]); + trustedUrl = + js_util.callMethod(policy as Object, 'createScriptURL', [src]); + // Set the trusted URL using js_util + } catch (e) { + if (options!.automatedTestMode) { + throw TrustedTypesException(); + } + } + } + + if (trustedUrl != null) { + js_util.setProperty(script, 'src', trustedUrl); + } else { + script.src = src; + } + if (integrity != null) { script.integrity = integrity; } diff --git a/flutter/lib/src/web/script_loader/noop_script_dom_api.dart b/flutter/lib/src/web/script_loader/noop_script_dom_api.dart index db45ac0914..02cf539440 100644 --- a/flutter/lib/src/web/script_loader/noop_script_dom_api.dart +++ b/flutter/lib/src/web/script_loader/noop_script_dom_api.dart @@ -1 +1,6 @@ -Future loadScript(String src, String? integrity) async {} +import '../../../sentry_flutter.dart'; +import 'sentry_script_loader.dart'; + +Future loadScript(String src, SentryOptions options, + {String? integrity, + String trustedTypePolicyName = defaultTrustedPolicyName}) async {} diff --git a/flutter/lib/src/web/script_loader/sentry_script_loader.dart b/flutter/lib/src/web/script_loader/sentry_script_loader.dart index f8c24d3465..b234cb7454 100644 --- a/flutter/lib/src/web/script_loader/sentry_script_loader.dart +++ b/flutter/lib/src/web/script_loader/sentry_script_loader.dart @@ -6,16 +6,19 @@ import '../../../sentry_flutter.dart'; import 'script_dom_api.dart'; @internal +const String defaultTrustedPolicyName = 'sentry-dart'; + class SentryScriptLoader { - SentryScriptLoader(this.options); + SentryScriptLoader(this._options); - final SentryFlutterOptions options; + final SentryFlutterOptions _options; bool _scriptLoaded = false; - /// Loads scripts into the document asynchronously. + /// Loads scripts into the document asynchrcriptLoader { /// /// Idempotent: does nothing if scripts are already loaded. - Future load(List> scripts) async { + Future loadWebSdk(List> scripts, + {String trustedTypePolicyName = defaultTrustedPolicyName}) async { if (_scriptLoaded) return; try { @@ -24,19 +27,28 @@ class SentryScriptLoader { final integrity = script['integrity']; if (url != null) { - await loadScript(url, integrity); + await loadScript(url, _options, + integrity: integrity, + trustedTypePolicyName: trustedTypePolicyName); } }); _scriptLoaded = true; - options.logger(SentryLevel.debug, + _options.logger(SentryLevel.debug, 'JS SDK integration: all Sentry scripts loaded successfully.'); } catch (e, stackTrace) { - options.logger( + _options.logger( SentryLevel.error, 'Failed to load Sentry scripts: $e\n$stackTrace'); - if (options.automatedTestMode) { + if (_options.automatedTestMode) { rethrow; } } } } + +/// Exception thrown if the Trusted Types feature is supported, enabled, and it +/// has prevented this loader from injecting the Sentry JS SDK +@visibleForTesting +class TrustedTypesException implements Exception { + TrustedTypesException(); +} diff --git a/flutter/lib/src/web/script_loader/web_script_dom_api.dart b/flutter/lib/src/web/script_loader/web_script_dom_api.dart index 5ea17097c0..a1d17866dd 100644 --- a/flutter/lib/src/web/script_loader/web_script_dom_api.dart +++ b/flutter/lib/src/web/script_loader/web_script_dom_api.dart @@ -1,19 +1,48 @@ import 'dart:async'; +import 'dart:js_interop'; +import 'dart:js_interop_unsafe'; // ignore: depend_on_referenced_packages import 'package:web/web.dart'; -Future loadScript(String src, String? integrity) { +import '../../../sentry_flutter.dart'; +import 'sentry_script_loader.dart'; + +Future loadScript(String src, SentryOptions options, + {String? integrity, + String trustedTypePolicyName = defaultTrustedPolicyName}) { final completer = Completer(); final script = HTMLScriptElement() - ..src = src ..crossOrigin = 'anonymous' ..onLoad.listen((_) => completer.complete()) ..onError.listen((event) => completer.completeError('Failed to load $src')); + TrustedScriptURL? trustedUrl; + if (!window.trustedTypes.isUndefinedOrNull) { + try { + final TrustedTypePolicy policy = window.trustedTypes.createPolicy( + trustedTypePolicyName, + TrustedTypePolicyOptions( + createScriptURL: ((JSString url) => src).toJS, + )); + trustedUrl = policy.createScriptURL(src, null); + } catch (e) { + if (options.automatedTestMode) { + throw TrustedTypesException(); + } + } + } + + if (trustedUrl != null) { + (script as JSObject).setProperty('src'.toJS, trustedUrl); + } else { + script.src = src; + } + if (integrity != null) { script.integrity = integrity; } + // JS SDK needs to be loaded before everything else final head = document.head; if (head != null) { @@ -25,3 +54,12 @@ Future loadScript(String src, String? integrity) { } return completer.future; } + +void injectMetaTag(Map attributes) { + final HTMLMetaElement meta = + document.createElement('meta') as HTMLMetaElement; + for (final MapEntry attribute in attributes.entries) { + meta.setAttribute(attribute.key, attribute.value); + } + document.head!.appendChild(meta); +} diff --git a/flutter/lib/src/web/sentry_js_bundle.dart b/flutter/lib/src/web/sentry_js_bundle.dart index 7cb364e5e4..96c0e098fb 100644 --- a/flutter/lib/src/web/sentry_js_bundle.dart +++ b/flutter/lib/src/web/sentry_js_bundle.dart @@ -2,14 +2,15 @@ import 'package:meta/meta.dart'; // todo: set up ci to update this and the integrity @internal -const jsSdkVersion = '8.39.0'; +const jsSdkVersion = '8.42.0'; +// The URLs from which the script should be downloaded. @internal const productionScripts = [ { 'url': 'https://browser.sentry-cdn.com/$jsSdkVersion/bundle.tracing.min.js', 'integrity': - 'sha384-GmCdCRO2s8VuYopAldiuHl/uns+EWDcLodj8AW810pK14r3vPQxoDNsMxnitCt18' + 'sha384-bG2vyJAuRm/JbGQrlET5H7y0CTvPF0atiBjekU/WUKUwKwThDXrqRhZiQ+jWaagS' } ]; @@ -17,5 +18,7 @@ const productionScripts = [ const debugScripts = [ { 'url': 'https://browser.sentry-cdn.com/$jsSdkVersion/bundle.tracing.js', + 'integrity': + 'sha384-WybdMW5lxuTpznT+4dobKr9wWgFoISsinHnIXDF8HBrG5/yGrmEhHRyMS1kfLsMi' }, ]; diff --git a/flutter/test/integrations/web_sdk_integration_test.dart b/flutter/test/integrations/web_sdk_integration_test.dart index 8b591de4c5..9d0c4da6f7 100644 --- a/flutter/test/integrations/web_sdk_integration_test.dart +++ b/flutter/test/integrations/web_sdk_integration_test.dart @@ -41,18 +41,22 @@ class Fixture { late FakeSentryScriptLoader scriptLoader; WebSdkIntegration getSut() { - scriptLoader = FakeSentryScriptLoader(options, []); + scriptLoader = FakeSentryScriptLoader(options); return WebSdkIntegration(scriptLoader); } } class FakeSentryScriptLoader extends SentryScriptLoader { - int loadScriptsCalls = 0; + FakeSentryScriptLoader(super.options); - FakeSentryScriptLoader(super.options, super.scripts); + int loadScriptsCalls = 0; @override - Future load() async { + Future loadWebSdk(List> scripts, + {String trustedTypePolicyName = defaultTrustedPolicyName}) { loadScriptsCalls += 1; + + return super + .loadWebSdk(scripts, trustedTypePolicyName: trustedTypePolicyName); } } diff --git a/flutter/test/web/dom_api/html_script_dom_api.dart b/flutter/test/web/dom_api/html_script_dom_api.dart index 6ca4d446a6..854f11eda2 100644 --- a/flutter/test/web/dom_api/html_script_dom_api.dart +++ b/flutter/test/web/dom_api/html_script_dom_api.dart @@ -22,3 +22,11 @@ List querySelectorAll(String query) { .map((script) => HtmlScriptElement(script as ScriptElement)) .toList(); } + +void injectMetaTag(Map attributes) { + final MetaElement meta = document.createElement('meta') as MetaElement; + for (final MapEntry attribute in attributes.entries) { + meta.setAttribute(attribute.key, attribute.value); + } + document.head!.append(meta); +} diff --git a/flutter/test/web/dom_api/noop_script_dom_api.dart b/flutter/test/web/dom_api/noop_script_dom_api.dart index 92259f3bf9..ea887c2a1c 100644 --- a/flutter/test/web/dom_api/noop_script_dom_api.dart +++ b/flutter/test/web/dom_api/noop_script_dom_api.dart @@ -1,3 +1,5 @@ import 'script_dom_api.dart'; List querySelectorAll(String query) => []; + +void injectMetaTag(Map attributes) {} diff --git a/flutter/test/web/dom_api/web_script_dom_api.dart b/flutter/test/web/dom_api/web_script_dom_api.dart index c16cd57231..9cfe26d8dd 100644 --- a/flutter/test/web/dom_api/web_script_dom_api.dart +++ b/flutter/test/web/dom_api/web_script_dom_api.dart @@ -28,3 +28,12 @@ List querySelectorAll(String query) { return elements; } + +void injectMetaTag(Map attributes) { + final HTMLMetaElement meta = + document.createElement('meta') as HTMLMetaElement; + for (final MapEntry attribute in attributes.entries) { + meta.setAttribute(attribute.key, attribute.value); + } + document.head!.appendChild(meta); +} diff --git a/flutter/test/web/sentry_script_loader_test.dart b/flutter/test/web/sentry_script_loader_test.dart index f94cd14c2f..8e9faad3fb 100644 --- a/flutter/test/web/sentry_script_loader_test.dart +++ b/flutter/test/web/sentry_script_loader_test.dart @@ -2,15 +2,22 @@ library flutter_test; import 'package:flutter_test/flutter_test.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:sentry_flutter/src/web/script_loader/noop_script_dom_api.dart'; import 'package:sentry_flutter/src/web/script_loader/sentry_script_loader.dart'; import 'package:sentry_flutter/src/web/sentry_js_bundle.dart'; import '../mocks.dart'; import 'dom_api/script_dom_api.dart'; +// This file will contain most of the test cases but also tests the +// trusted types by default(TT). +// +// The other TT tests will be split up into multiple files +// * sentry_script_loader_test.dart : default TT configuration (not enforced) +// * sentry_script_loader_tt_custom_test.dart : TT are customized, but allowed +// * sentry_script_loader_tt_forbidden_test.dart: TT are completely disallowed void main() { - group('$SentryScriptLoader', () { + group('loadWebSdk (no TrustedTypes)', () { late Fixture fixture; setUp(() { @@ -18,87 +25,101 @@ void main() { }); tearDown(() { - final existingScripts = querySelectorAll('script[src*="sentry-cdn"]'); + final existingScripts = querySelectorAll('script'); for (final script in existingScripts) { script.remove(); } }); - test('loads production scripts by default', () async { + test('Loads production scripts by default', () async { final sut = fixture.getSut(); - await sut.load(productionScripts); + await sut.loadWebSdk(productionScripts); - final scripts = querySelectorAll('script[src*="sentry-cdn"]'); - for (final script in scripts) { - final element = script; - expect(element.src, contains('.min.js')); - } + final scripts = querySelectorAll('script'); + + expect( + scripts.first.src, endsWith('$jsSdkVersion/bundle.tracing.min.js')); }); - test('loads debug scripts when debug is enabled', () async { - final sut = fixture.getSut(debug: true); + test('Loads debug scripts when debug is enabled', () async { + final sut = fixture.getSut(); - await sut.load(debugScripts); + await sut.loadWebSdk(debugScripts); - final scripts = querySelectorAll('script[src*="sentry-cdn"]'); - for (final script in scripts) { - final element = script; - expect(element.src, isNot(contains('.min.js'))); - } + final scripts = querySelectorAll('script'); + + expect(scripts.first.src, endsWith('$jsSdkVersion/bundle.tracing.js')); }); - test('does not load scripts twice', () async { + test('Does not load scripts twice', () async { final sut = fixture.getSut(); - await sut.load(productionScripts); + await sut.loadWebSdk(productionScripts); + final initialScriptCount = querySelectorAll('script').length; - await sut.load(productionScripts); + await sut.loadWebSdk(productionScripts); expect(querySelectorAll('script').length, initialScriptCount); }); - test('handles script loading failures', () async { + test('Handles script loading failures', () async { final failingScripts = [ { 'url': 'https://invalid', }, ]; + final sut = fixture.getSut(); // Modify script URL to cause failure - final sut = fixture.getSut(); + expect( + () async => await sut.loadWebSdk(failingScripts), throwsA(anything)); - expect(() async => await sut.load(failingScripts), throwsA(anything)); - await sut.load(productionScripts); + // loading after the failure still works + await sut.loadWebSdk(productionScripts); - final scripts = querySelectorAll('script[src*="sentry-cdn"]'); - expect(scripts.first.src, contains('.min.js')); + final scripts = querySelectorAll('script'); + expect( + scripts.first.src, endsWith('$jsSdkVersion/bundle.tracing.min.js')); }); - test('handles script loading failures with automatedTestMode false', + test('Handles script loading failures with automatedTestMode false', () async { fixture.options.automatedTestMode = false; - final failingScripts = [ { 'url': 'https://invalid', }, ]; + final sut = fixture.getSut(); // Modify script URL to cause failure + await sut.loadWebSdk(failingScripts); + + // loading after the failure still works + await sut.loadWebSdk(productionScripts); + + final scripts = querySelectorAll('script'); + expect( + scripts.first.src, endsWith('$jsSdkVersion/bundle.tracing.min.js')); + }); + + test('Loads sentry script as first element', () async { final sut = fixture.getSut(); - await sut.load(failingScripts); - await sut.load(productionScripts); + // use loadScript since that disregards the isLoaded check + await loadScript('https://google.com', fixture.options); - final scripts = querySelectorAll('script[src*="sentry-cdn"]'); - expect(scripts.first.src, contains('.min.js')); + await sut.loadWebSdk(productionScripts); + final scriptElements = querySelectorAll('script'); + expect(scriptElements.first.src, + endsWith('$jsSdkVersion/bundle.tracing.min.js')); }); }); } class Fixture { - final SentryFlutterOptions options = defaultTestOptions(); + final options = defaultTestOptions(); SentryScriptLoader getSut({bool debug = false}) { options.platformChecker = MockPlatformChecker(isDebug: debug); diff --git a/flutter/test/web/sentry_script_loader_tt_custom_test.dart b/flutter/test/web/sentry_script_loader_tt_custom_test.dart new file mode 100644 index 0000000000..30a67c72fc --- /dev/null +++ b/flutter/test/web/sentry_script_loader_tt_custom_test.dart @@ -0,0 +1,54 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:sentry_flutter/src/web/script_loader/sentry_script_loader.dart'; +import 'package:sentry_flutter/src/web/sentry_js_bundle.dart'; + +import '../mocks.dart'; +import 'dom_api/script_dom_api.dart'; + +// The other TT tests will be split up into multiple files +// * sentry_script_loader_test.dart : default TT configuration (not enforced) +// * sentry_script_loader_tt_custom_test.dart : TT are customized, but allowed +// * sentry_script_loader_tt_forbidden_test.dart: TT are completely disallowed + +void main() { + group('loadWebSdk (TrustedTypes configured)', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + injectMetaTag({ + 'http-equiv': 'Content-Security-Policy', + 'content': "trusted-types my-custom-policy-name 'allow-duplicates';", + }); + + test('Wrong policy name: Fail with TrustedTypesException', () { + expect(() async { + final sut = fixture.getSut(); + + await sut.loadWebSdk(productionScripts); + }, throwsA(isA())); + }); + + test('Correct policy name: Completes', () { + final sut = fixture.getSut(); + + final Future done = sut.loadWebSdk(productionScripts, + trustedTypePolicyName: 'my-custom-policy-name'); + expect(done, isA>()); + + final scripts = querySelectorAll('script'); + expect( + scripts.first.src, endsWith('$jsSdkVersion/bundle.tracing.min.js')); + }); + }); +} + +class Fixture { + final options = defaultTestOptions(); + + SentryScriptLoader getSut() { + return SentryScriptLoader(options); + } +} diff --git a/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart b/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart new file mode 100644 index 0000000000..f201540c1e --- /dev/null +++ b/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart @@ -0,0 +1,41 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:sentry_flutter/src/web/script_loader/sentry_script_loader.dart'; +import 'package:sentry_flutter/src/web/sentry_js_bundle.dart'; + +import '../mocks.dart'; +import 'dom_api/script_dom_api.dart'; + +// The other TT tests will be split up into multiple files +// * sentry_script_loader_test.dart : default TT configuration (not enforced) +// * sentry_script_loader_tt_custom_test.dart : TT are customized, but allowed +// * sentry_script_loader_tt_forbidden_test.dart: TT are completely disallowed +void main() { + group('loadWebSdk (TrustedTypes forbidden)', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + injectMetaTag({ + 'http-equiv': 'Content-Security-Policy', + 'content': "trusted-types 'none';", + }); + + test('Fail with TrustedTypesException', () { + expect(() async { + final sut = fixture.getSut(); + + await sut.loadWebSdk(productionScripts); + }, throwsA(isA())); + }); + }); +} + +class Fixture { + final options = defaultTestOptions(); + + SentryScriptLoader getSut() { + return SentryScriptLoader(options); + } +} From a7b6054b6e50c1dc01f035eab29966528f855330 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Mon, 2 Dec 2024 17:24:58 +0100 Subject: [PATCH 31/76] formatting --- flutter/test/web/sentry_script_loader_test.dart | 1 + flutter/test/web/sentry_script_loader_tt_forbidden_test.dart | 1 + 2 files changed, 2 insertions(+) diff --git a/flutter/test/web/sentry_script_loader_test.dart b/flutter/test/web/sentry_script_loader_test.dart index 8e9faad3fb..ed07b8445b 100644 --- a/flutter/test/web/sentry_script_loader_test.dart +++ b/flutter/test/web/sentry_script_loader_test.dart @@ -16,6 +16,7 @@ import 'dom_api/script_dom_api.dart'; // * sentry_script_loader_test.dart : default TT configuration (not enforced) // * sentry_script_loader_tt_custom_test.dart : TT are customized, but allowed // * sentry_script_loader_tt_forbidden_test.dart: TT are completely disallowed + void main() { group('loadWebSdk (no TrustedTypes)', () { late Fixture fixture; diff --git a/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart b/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart index f201540c1e..f0f72123ed 100644 --- a/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart +++ b/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart @@ -9,6 +9,7 @@ import 'dom_api/script_dom_api.dart'; // * sentry_script_loader_test.dart : default TT configuration (not enforced) // * sentry_script_loader_tt_custom_test.dart : TT are customized, but allowed // * sentry_script_loader_tt_forbidden_test.dart: TT are completely disallowed + void main() { group('loadWebSdk (TrustedTypes forbidden)', () { late Fixture fixture; From 2ef43fdea4634835777428d62caa5581c7a92cae Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Mon, 2 Dec 2024 17:26:11 +0100 Subject: [PATCH 32/76] update comment --- flutter/lib/src/web/script_loader/sentry_script_loader.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/lib/src/web/script_loader/sentry_script_loader.dart b/flutter/lib/src/web/script_loader/sentry_script_loader.dart index b234cb7454..ca6ac35a92 100644 --- a/flutter/lib/src/web/script_loader/sentry_script_loader.dart +++ b/flutter/lib/src/web/script_loader/sentry_script_loader.dart @@ -14,7 +14,7 @@ class SentryScriptLoader { final SentryFlutterOptions _options; bool _scriptLoaded = false; - /// Loads scripts into the document asynchrcriptLoader { + /// Loads scripts into the document async /// /// Idempotent: does nothing if scripts are already loaded. Future loadWebSdk(List> scripts, From 21b138283ca5f4a89d090f908de6b457f19b73ef Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Mon, 2 Dec 2024 17:30:06 +0100 Subject: [PATCH 33/76] add as browser test --- flutter/test/web/sentry_script_loader_tt_custom_test.dart | 3 +++ flutter/test/web/sentry_script_loader_tt_forbidden_test.dart | 3 +++ 2 files changed, 6 insertions(+) diff --git a/flutter/test/web/sentry_script_loader_tt_custom_test.dart b/flutter/test/web/sentry_script_loader_tt_custom_test.dart index 30a67c72fc..a66255b420 100644 --- a/flutter/test/web/sentry_script_loader_tt_custom_test.dart +++ b/flutter/test/web/sentry_script_loader_tt_custom_test.dart @@ -1,3 +1,6 @@ +@TestOn('browser') +library flutter_test; + import 'package:flutter_test/flutter_test.dart'; import 'package:sentry_flutter/src/web/script_loader/sentry_script_loader.dart'; import 'package:sentry_flutter/src/web/sentry_js_bundle.dart'; diff --git a/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart b/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart index f0f72123ed..02a6e257db 100644 --- a/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart +++ b/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart @@ -1,3 +1,6 @@ +@TestOn('browser') +library flutter_test; + import 'package:flutter_test/flutter_test.dart'; import 'package:sentry_flutter/src/web/script_loader/sentry_script_loader.dart'; import 'package:sentry_flutter/src/web/sentry_js_bundle.dart'; From af1f7cb933736af48fbdb94ac22357f3a57a78ce Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Mon, 2 Dec 2024 18:30:19 +0100 Subject: [PATCH 34/76] fix min_version --- .../lib/src/web/script_loader/html_script_dom_api.dart | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/flutter/lib/src/web/script_loader/html_script_dom_api.dart b/flutter/lib/src/web/script_loader/html_script_dom_api.dart index 88e6a739ce..312be9f1ef 100644 --- a/flutter/lib/src/web/script_loader/html_script_dom_api.dart +++ b/flutter/lib/src/web/script_loader/html_script_dom_api.dart @@ -24,13 +24,19 @@ Future loadScript(String src, SentryOptions? options, final policy = js_util.callMethod(trustedTypes as Object, 'createPolicy', [ trustedTypePolicyName, - js_util.jsify( - {'createScriptURL': js_util.allowInterop((String url) => src)}) + js_util.jsify({ + 'createScriptURL': (String url) => src, + }) ]); trustedUrl = js_util.callMethod(policy as Object, 'createScriptURL', [src]); // Set the trusted URL using js_util } catch (e) { + options?.logger( + SentryLevel.warning, + 'SentryScriptLoader: failed to created trusted url', + exception: e, + ); if (options!.automatedTestMode) { throw TrustedTypesException(); } From dbfe736382874f15693af0ea938a638a38eccad5 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Mon, 2 Dec 2024 18:31:32 +0100 Subject: [PATCH 35/76] fix warnings --- flutter/lib/src/web/script_loader/html_script_dom_api.dart | 5 +++-- flutter/lib/src/web/script_loader/web_script_dom_api.dart | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/flutter/lib/src/web/script_loader/html_script_dom_api.dart b/flutter/lib/src/web/script_loader/html_script_dom_api.dart index 312be9f1ef..fd9fd01ba5 100644 --- a/flutter/lib/src/web/script_loader/html_script_dom_api.dart +++ b/flutter/lib/src/web/script_loader/html_script_dom_api.dart @@ -18,11 +18,11 @@ Future loadScript(String src, SentryOptions? options, TrustedScriptUrl? trustedUrl; // If TrustedTypes are available, prepare a trusted URL - final trustedTypes = js_util.getProperty(window, 'trustedTypes'); + final trustedTypes = js_util.getProperty(window, 'trustedTypes'); if (trustedTypes != null) { try { final policy = - js_util.callMethod(trustedTypes as Object, 'createPolicy', [ + js_util.callMethod(trustedTypes as Object, 'createPolicy', [ trustedTypePolicyName, js_util.jsify({ 'createScriptURL': (String url) => src, @@ -37,6 +37,7 @@ Future loadScript(String src, SentryOptions? options, 'SentryScriptLoader: failed to created trusted url', exception: e, ); + // ignore: invalid_use_of_internal_member if (options!.automatedTestMode) { throw TrustedTypesException(); } diff --git a/flutter/lib/src/web/script_loader/web_script_dom_api.dart b/flutter/lib/src/web/script_loader/web_script_dom_api.dart index a1d17866dd..3d4a46441b 100644 --- a/flutter/lib/src/web/script_loader/web_script_dom_api.dart +++ b/flutter/lib/src/web/script_loader/web_script_dom_api.dart @@ -27,6 +27,7 @@ Future loadScript(String src, SentryOptions options, )); trustedUrl = policy.createScriptURL(src, null); } catch (e) { + // ignore: invalid_use_of_internal_member if (options.automatedTestMode) { throw TrustedTypesException(); } From 8538c9cb9336e46adcb07968e7e9ce8fe33327b1 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 3 Dec 2024 00:33:02 +0100 Subject: [PATCH 36/76] add another test file --- .../test_driver/web_integration_test.dart | 0 .../integration_test/web_sdk_test.dart | 0 .../test/web/sentry_script_loader_test.dart | 10 +---- ...ry_script_loader_tt_not_enforced_test.dart | 42 +++++++++++++++++++ 4 files changed, 43 insertions(+), 9 deletions(-) create mode 100644 flutter/example/integration_test/test_driver/web_integration_test.dart create mode 100644 flutter/example/integration_test/web_sdk_test.dart create mode 100644 flutter/test/web/sentry_script_loader_tt_not_enforced_test.dart diff --git a/flutter/example/integration_test/test_driver/web_integration_test.dart b/flutter/example/integration_test/test_driver/web_integration_test.dart new file mode 100644 index 0000000000..e69de29bb2 diff --git a/flutter/example/integration_test/web_sdk_test.dart b/flutter/example/integration_test/web_sdk_test.dart new file mode 100644 index 0000000000..e69de29bb2 diff --git a/flutter/test/web/sentry_script_loader_test.dart b/flutter/test/web/sentry_script_loader_test.dart index ed07b8445b..834291da71 100644 --- a/flutter/test/web/sentry_script_loader_test.dart +++ b/flutter/test/web/sentry_script_loader_test.dart @@ -9,16 +9,8 @@ import 'package:sentry_flutter/src/web/sentry_js_bundle.dart'; import '../mocks.dart'; import 'dom_api/script_dom_api.dart'; -// This file will contain most of the test cases but also tests the -// trusted types by default(TT). -// -// The other TT tests will be split up into multiple files -// * sentry_script_loader_test.dart : default TT configuration (not enforced) -// * sentry_script_loader_tt_custom_test.dart : TT are customized, but allowed -// * sentry_script_loader_tt_forbidden_test.dart: TT are completely disallowed - void main() { - group('loadWebSdk (no TrustedTypes)', () { + group('$SentryScriptLoader', () { late Fixture fixture; setUp(() { diff --git a/flutter/test/web/sentry_script_loader_tt_not_enforced_test.dart b/flutter/test/web/sentry_script_loader_tt_not_enforced_test.dart new file mode 100644 index 0000000000..5e39eeb255 --- /dev/null +++ b/flutter/test/web/sentry_script_loader_tt_not_enforced_test.dart @@ -0,0 +1,42 @@ +@TestOn('browser') +library flutter_test; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:sentry_flutter/src/web/script_loader/sentry_script_loader.dart'; +import 'package:sentry_flutter/src/web/sentry_js_bundle.dart'; + +import '../mocks.dart'; +import 'dom_api/html_script_dom_api.dart'; + +// The other TrustedTypes (TT) tests will be split up into multiple files +// * sentry_script_loader_tt_not_enforced_test.dart : default TT configuration (not enforced) +// * sentry_script_loader_tt_custom_test.dart : TT are customized, but allowed +// * sentry_script_loader_tt_forbidden_test.dart: TT are completely disallowed + +void main() { + group('loadWebSdk (no TrustedTypes configured)', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + + test('Injects script into document head', () async { + final sut = fixture.getSut(); + + await sut.loadWebSdk(productionScripts); + + final scripts = querySelectorAll('script'); + expect( + scripts.first.src, contains('$jsSdkVersion/bundle.tracing.min.js')); + }); + }); +} + +class Fixture { + final options = defaultTestOptions(); + + SentryScriptLoader getSut() { + return SentryScriptLoader(options); + } +} From 90aaedc5beed610d68c1f968b4caf1322cf13d28 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 3 Dec 2024 00:35:41 +0100 Subject: [PATCH 37/76] update --- flutter/test/web/sentry_js_bundles_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flutter/test/web/sentry_js_bundles_test.dart b/flutter/test/web/sentry_js_bundles_test.dart index 22fc834b3a..a962f7231a 100644 --- a/flutter/test/web/sentry_js_bundles_test.dart +++ b/flutter/test/web/sentry_js_bundles_test.dart @@ -14,14 +14,14 @@ void main() { expect(response.body, isNotEmpty); } - test('production script is accessible', () async { + test('Production script is accessible', () async { await Future.forEach(productionScripts, (Map script) async { await checkScript(script); }); }); - test('debug script is accessible', () async { + test('Debug script is accessible', () async { await Future.forEach(debugScripts, (Map script) async { await checkScript(script); }); From c57b950c0eaf2810dc7708fbfe96d9b6d087ce4e Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 3 Dec 2024 00:36:37 +0100 Subject: [PATCH 38/76] update comment --- flutter/test/web/sentry_script_loader_tt_custom_test.dart | 1 + flutter/test/web/sentry_script_loader_tt_forbidden_test.dart | 1 + .../test/web/sentry_script_loader_tt_not_enforced_test.dart | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/flutter/test/web/sentry_script_loader_tt_custom_test.dart b/flutter/test/web/sentry_script_loader_tt_custom_test.dart index a66255b420..bfbe99f82b 100644 --- a/flutter/test/web/sentry_script_loader_tt_custom_test.dart +++ b/flutter/test/web/sentry_script_loader_tt_custom_test.dart @@ -9,6 +9,7 @@ import '../mocks.dart'; import 'dom_api/script_dom_api.dart'; // The other TT tests will be split up into multiple files +// because TrustedTypes cannot be relaxed after they are set // * sentry_script_loader_test.dart : default TT configuration (not enforced) // * sentry_script_loader_tt_custom_test.dart : TT are customized, but allowed // * sentry_script_loader_tt_forbidden_test.dart: TT are completely disallowed diff --git a/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart b/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart index 02a6e257db..82c8855ecb 100644 --- a/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart +++ b/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart @@ -9,6 +9,7 @@ import '../mocks.dart'; import 'dom_api/script_dom_api.dart'; // The other TT tests will be split up into multiple files +// because TrustedTypes cannot be relaxed after they are set // * sentry_script_loader_test.dart : default TT configuration (not enforced) // * sentry_script_loader_tt_custom_test.dart : TT are customized, but allowed // * sentry_script_loader_tt_forbidden_test.dart: TT are completely disallowed diff --git a/flutter/test/web/sentry_script_loader_tt_not_enforced_test.dart b/flutter/test/web/sentry_script_loader_tt_not_enforced_test.dart index 5e39eeb255..6e132651cd 100644 --- a/flutter/test/web/sentry_script_loader_tt_not_enforced_test.dart +++ b/flutter/test/web/sentry_script_loader_tt_not_enforced_test.dart @@ -8,7 +8,8 @@ import 'package:sentry_flutter/src/web/sentry_js_bundle.dart'; import '../mocks.dart'; import 'dom_api/html_script_dom_api.dart'; -// The other TrustedTypes (TT) tests will be split up into multiple files +// The other TT tests will be split up into multiple files +// because TrustedTypes cannot be relaxed after they are set // * sentry_script_loader_tt_not_enforced_test.dart : default TT configuration (not enforced) // * sentry_script_loader_tt_custom_test.dart : TT are customized, but allowed // * sentry_script_loader_tt_forbidden_test.dart: TT are completely disallowed From 42aa1f860c55206b92dfefbf820d40742b8958bf Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 3 Dec 2024 00:47:31 +0100 Subject: [PATCH 39/76] try out web integration test --- .github/workflows/flutter_test.yml | 31 +++++++++++++++++++ .../test_driver/web_driver.dart | 3 ++ .../test_driver/web_integration_test.dart | 0 3 files changed, 34 insertions(+) create mode 100644 flutter/example/integration_test/test_driver/web_driver.dart delete mode 100644 flutter/example/integration_test/test_driver/web_integration_test.dart diff --git a/.github/workflows/flutter_test.yml b/.github/workflows/flutter_test.yml index 9c65c5fd4d..a75946d295 100644 --- a/.github/workflows/flutter_test.yml +++ b/.github/workflows/flutter_test.yml @@ -166,3 +166,34 @@ jobs: if: ${{ matrix.target != 'macos' }} working-directory: ./flutter/example/${{ matrix.target }} run: xcodebuild test -workspace Runner.xcworkspace -scheme Runner -configuration Debug -destination "platform=${{ steps.device.outputs.platform }}" -allowProvisioningUpdates CODE_SIGNING_ALLOWED=NO + + test-web: + runs-on: macos-13 + timeout-minutes: 30 + defaults: + run: + working-directory: ./flutter/example + strategy: + fail-fast: false + matrix: + sdk: [ "stable", "beta" ] + steps: + - name: checkout + uses: actions/checkout@v4 + + - uses: subosito/flutter-action@44ac965b96f18d999802d4b807e3256d5a3f9fa1 # pin@v2.16.0 + with: + channel: ${{ matrix.sdk }} + + - name: flutter upgrade + run: flutter upgrade + + - name: flutter pub get + run: flutter pub get + + - uses: nanasess/setup-chromedriver@v2 + run: chromedriver + + - name: run integration test + run: | + flutter drive --driver=integration_test/test_driver/web_driver.dart --target=integration_test/web_sdk_test.dart -d chrome \ No newline at end of file diff --git a/flutter/example/integration_test/test_driver/web_driver.dart b/flutter/example/integration_test/test_driver/web_driver.dart new file mode 100644 index 0000000000..b38629cca9 --- /dev/null +++ b/flutter/example/integration_test/test_driver/web_driver.dart @@ -0,0 +1,3 @@ +import 'package:integration_test/integration_test_driver.dart'; + +Future main() => integrationDriver(); diff --git a/flutter/example/integration_test/test_driver/web_integration_test.dart b/flutter/example/integration_test/test_driver/web_integration_test.dart deleted file mode 100644 index e69de29bb2..0000000000 From 77dd5d8aad2fb2d8d6f8fd97c0b94a90fd39a8eb Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 3 Dec 2024 00:47:41 +0100 Subject: [PATCH 40/76] update --- .../integration_test/web_sdk_test.dart | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/flutter/example/integration_test/web_sdk_test.dart b/flutter/example/integration_test/web_sdk_test.dart index e69de29bb2..f42838bd28 100644 --- a/flutter/example/integration_test/web_sdk_test.dart +++ b/flutter/example/integration_test/web_sdk_test.dart @@ -0,0 +1,28 @@ +@TestOn('browser') + +import 'dart:html'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; + +void main() { + group('Web SDK Integration', () { + tearDown(() async { + await Sentry.close(); + }); + + test('Injects script into document head', () async { + await SentryFlutter.init((options) { + options.dsn = 'https://abc@def.ingest.sentry.io/1234567'; + }); + + final scripts = document + .querySelectorAll('script') + .map((script) => script as ScriptElement) + .toList(); + + // should inject the debug script + expect(scripts.first.src, contains('bundle.tracing.js')); + }); + }); +} From f9c453227007328a8407b84f8931c0e740300064 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 3 Dec 2024 00:58:02 +0100 Subject: [PATCH 41/76] use ubuntu latest for web --- .github/workflows/flutter_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/flutter_test.yml b/.github/workflows/flutter_test.yml index a75946d295..6dfa12058c 100644 --- a/.github/workflows/flutter_test.yml +++ b/.github/workflows/flutter_test.yml @@ -168,7 +168,7 @@ jobs: run: xcodebuild test -workspace Runner.xcworkspace -scheme Runner -configuration Debug -destination "platform=${{ steps.device.outputs.platform }}" -allowProvisioningUpdates CODE_SIGNING_ALLOWED=NO test-web: - runs-on: macos-13 + runs-on: ubuntu-latest timeout-minutes: 30 defaults: run: From fab4044d4aced898f6ad17933c08e03bc05d3ca2 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 3 Dec 2024 01:00:11 +0100 Subject: [PATCH 42/76] see if it runs --- .github/workflows/flutter_test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/flutter_test.yml b/.github/workflows/flutter_test.yml index 6dfa12058c..4b820541f0 100644 --- a/.github/workflows/flutter_test.yml +++ b/.github/workflows/flutter_test.yml @@ -194,6 +194,6 @@ jobs: - uses: nanasess/setup-chromedriver@v2 run: chromedriver - - name: run integration test - run: | - flutter drive --driver=integration_test/test_driver/web_driver.dart --target=integration_test/web_sdk_test.dart -d chrome \ No newline at end of file +# - name: run integration test +# run: | +# flutter drive --driver=integration_test/test_driver/web_driver.dart --target=integration_test/web_sdk_test.dart -d chrome \ No newline at end of file From b128ede6110b4e99ed23caa557fae4a4dc6da44b Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 3 Dec 2024 01:01:47 +0100 Subject: [PATCH 43/76] update --- .github/workflows/flutter_test.yml | 52 +++++++++++++++--------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/.github/workflows/flutter_test.yml b/.github/workflows/flutter_test.yml index 4b820541f0..d4a98eeca4 100644 --- a/.github/workflows/flutter_test.yml +++ b/.github/workflows/flutter_test.yml @@ -167,32 +167,32 @@ jobs: working-directory: ./flutter/example/${{ matrix.target }} run: xcodebuild test -workspace Runner.xcworkspace -scheme Runner -configuration Debug -destination "platform=${{ steps.device.outputs.platform }}" -allowProvisioningUpdates CODE_SIGNING_ALLOWED=NO - test-web: - runs-on: ubuntu-latest - timeout-minutes: 30 - defaults: - run: - working-directory: ./flutter/example - strategy: - fail-fast: false - matrix: - sdk: [ "stable", "beta" ] - steps: - - name: checkout - uses: actions/checkout@v4 - - - uses: subosito/flutter-action@44ac965b96f18d999802d4b807e3256d5a3f9fa1 # pin@v2.16.0 - with: - channel: ${{ matrix.sdk }} - - - name: flutter upgrade - run: flutter upgrade - - - name: flutter pub get - run: flutter pub get - - - uses: nanasess/setup-chromedriver@v2 - run: chromedriver +# test-web: +# runs-on: ubuntu-latest +# timeout-minutes: 30 +# defaults: +# run: +# working-directory: ./flutter/example +# strategy: +# fail-fast: false +# matrix: +# sdk: [ "stable", "beta" ] +# steps: +# - name: checkout +# uses: actions/checkout@v4 +# +# - uses: subosito/flutter-action@44ac965b96f18d999802d4b807e3256d5a3f9fa1 # pin@v2.16.0 +# with: +# channel: ${{ matrix.sdk }} +# +# - name: flutter upgrade +# run: flutter upgrade +# +# - name: flutter pub get +# run: flutter pub get +# +# - uses: nanasess/setup-chromedriver@v2 +# run: chromedriver # - name: run integration test # run: | From 3e3a87e87698cb47c8fbc6b4997f0fdc3f06ebbc Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 3 Dec 2024 01:03:27 +0100 Subject: [PATCH 44/76] fix job --- .github/workflows/flutter_test.yml | 52 +++++++++++++++--------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/.github/workflows/flutter_test.yml b/.github/workflows/flutter_test.yml index d4a98eeca4..b241b7e804 100644 --- a/.github/workflows/flutter_test.yml +++ b/.github/workflows/flutter_test.yml @@ -167,32 +167,32 @@ jobs: working-directory: ./flutter/example/${{ matrix.target }} run: xcodebuild test -workspace Runner.xcworkspace -scheme Runner -configuration Debug -destination "platform=${{ steps.device.outputs.platform }}" -allowProvisioningUpdates CODE_SIGNING_ALLOWED=NO -# test-web: -# runs-on: ubuntu-latest -# timeout-minutes: 30 -# defaults: -# run: -# working-directory: ./flutter/example -# strategy: -# fail-fast: false -# matrix: -# sdk: [ "stable", "beta" ] -# steps: -# - name: checkout -# uses: actions/checkout@v4 -# -# - uses: subosito/flutter-action@44ac965b96f18d999802d4b807e3256d5a3f9fa1 # pin@v2.16.0 -# with: -# channel: ${{ matrix.sdk }} -# -# - name: flutter upgrade -# run: flutter upgrade -# -# - name: flutter pub get -# run: flutter pub get -# -# - uses: nanasess/setup-chromedriver@v2 -# run: chromedriver + test-web: + runs-on: ubuntu-latest + timeout-minutes: 30 + defaults: + run: + working-directory: ./flutter/example + strategy: + fail-fast: false + matrix: + sdk: [ "stable", "beta" ] + steps: + - name: checkout + uses: actions/checkout@v4 + + - uses: subosito/flutter-action@44ac965b96f18d999802d4b807e3256d5a3f9fa1 # pin@v2.16.0 + with: + channel: ${{ matrix.sdk }} + + - name: flutter upgrade + run: flutter upgrade + + - name: flutter pub get + run: flutter pub get + + - uses: nanasess/setup-chromedriver@v2 + - run: chromedriver # - name: run integration test # run: | From de34883f2aa4ec0739f36ecfd825d9dc28c2c5dd Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 3 Dec 2024 01:08:36 +0100 Subject: [PATCH 45/76] fix job --- .github/workflows/flutter_test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/flutter_test.yml b/.github/workflows/flutter_test.yml index b241b7e804..c1258a2529 100644 --- a/.github/workflows/flutter_test.yml +++ b/.github/workflows/flutter_test.yml @@ -194,6 +194,6 @@ jobs: - uses: nanasess/setup-chromedriver@v2 - run: chromedriver -# - name: run integration test -# run: | -# flutter drive --driver=integration_test/test_driver/web_driver.dart --target=integration_test/web_sdk_test.dart -d chrome \ No newline at end of file + - name: run integration test + run: | + flutter drive --driver=integration_test/test_driver/web_driver.dart --target=integration_test/web_sdk_test.dart -d chrome \ No newline at end of file From cb2154866b2030eb02fd51d8c5c7ba482d635132 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 3 Dec 2024 01:15:52 +0100 Subject: [PATCH 46/76] run chromedriver in background --- .github/workflows/flutter_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/flutter_test.yml b/.github/workflows/flutter_test.yml index c1258a2529..4382cb2f1a 100644 --- a/.github/workflows/flutter_test.yml +++ b/.github/workflows/flutter_test.yml @@ -192,7 +192,7 @@ jobs: run: flutter pub get - uses: nanasess/setup-chromedriver@v2 - - run: chromedriver + - run: chromedriver & - name: run integration test run: | From fdd6353d432d7bc9f2060ea18ae6b31ce854e272 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 3 Dec 2024 01:21:16 +0100 Subject: [PATCH 47/76] run correct port --- .github/workflows/flutter_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/flutter_test.yml b/.github/workflows/flutter_test.yml index 4382cb2f1a..399be7beb4 100644 --- a/.github/workflows/flutter_test.yml +++ b/.github/workflows/flutter_test.yml @@ -192,7 +192,7 @@ jobs: run: flutter pub get - uses: nanasess/setup-chromedriver@v2 - - run: chromedriver & + - run: chromedriver --port=4444 & - name: run integration test run: | From 7115c4b204e21660c45b95d352d33060f7899ae7 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 3 Dec 2024 01:30:47 +0100 Subject: [PATCH 48/76] update --- .github/workflows/flutter_test.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/flutter_test.yml b/.github/workflows/flutter_test.yml index 399be7beb4..cd159584ee 100644 --- a/.github/workflows/flutter_test.yml +++ b/.github/workflows/flutter_test.yml @@ -192,7 +192,10 @@ jobs: run: flutter pub get - uses: nanasess/setup-chromedriver@v2 - - run: chromedriver --port=4444 & + - run: | + export DISPLAY=:99 + chromedriver --url-base=/wd/hub --port=4444 & + sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & # optional - name: run integration test run: | From 621da639bf548bcc74e7410c52bc725b12acc524 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 3 Dec 2024 01:33:06 +0100 Subject: [PATCH 49/76] update --- .github/workflows/flutter_test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/flutter_test.yml b/.github/workflows/flutter_test.yml index cd159584ee..83b9a75a89 100644 --- a/.github/workflows/flutter_test.yml +++ b/.github/workflows/flutter_test.yml @@ -195,7 +195,6 @@ jobs: - run: | export DISPLAY=:99 chromedriver --url-base=/wd/hub --port=4444 & - sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & # optional - name: run integration test run: | From 5f4db666ffa3aafb2bdb951bbf3586e40c1a9973 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 3 Dec 2024 14:27:32 +0100 Subject: [PATCH 50/76] setup chrome action --- .github/workflows/flutter_test.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/flutter_test.yml b/.github/workflows/flutter_test.yml index 83b9a75a89..de05d6f011 100644 --- a/.github/workflows/flutter_test.yml +++ b/.github/workflows/flutter_test.yml @@ -181,6 +181,12 @@ jobs: - name: checkout uses: actions/checkout@v4 + - name: Install Chrome Browser + uses: browser-actions/setup-chrome@facf10a55b9caf92e0cc749b4f82bf8220989148 # pin@v1.7.2 + with: + chrome-version: stable + id: setup-chrome + - uses: subosito/flutter-action@44ac965b96f18d999802d4b807e3256d5a3f9fa1 # pin@v2.16.0 with: channel: ${{ matrix.sdk }} From 927dc651f6f5729be7eb670066d74785ffb4016a Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 3 Dec 2024 14:34:01 +0100 Subject: [PATCH 51/76] update --- .github/workflows/flutter_test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/flutter_test.yml b/.github/workflows/flutter_test.yml index de05d6f011..00b2d9d634 100644 --- a/.github/workflows/flutter_test.yml +++ b/.github/workflows/flutter_test.yml @@ -199,6 +199,7 @@ jobs: - uses: nanasess/setup-chromedriver@v2 - run: | + Xvfb :99 & export DISPLAY=:99 chromedriver --url-base=/wd/hub --port=4444 & From a8f4b454378d176c3793f278d68edec7c17408b3 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 3 Dec 2024 14:45:04 +0100 Subject: [PATCH 52/76] run chrome first --- .github/workflows/flutter_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/flutter_test.yml b/.github/workflows/flutter_test.yml index 00b2d9d634..0ec9698a8f 100644 --- a/.github/workflows/flutter_test.yml +++ b/.github/workflows/flutter_test.yml @@ -185,7 +185,7 @@ jobs: uses: browser-actions/setup-chrome@facf10a55b9caf92e0cc749b4f82bf8220989148 # pin@v1.7.2 with: chrome-version: stable - id: setup-chrome + - run: chrome --version - uses: subosito/flutter-action@44ac965b96f18d999802d4b807e3256d5a3f9fa1 # pin@v2.16.0 with: From d7daa5602a95df16b9238125343b9156c44e5fc7 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 3 Dec 2024 14:56:09 +0100 Subject: [PATCH 53/76] try --- .github/workflows/flutter_test.yml | 33 +++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/.github/workflows/flutter_test.yml b/.github/workflows/flutter_test.yml index 0ec9698a8f..ff8aeff6dc 100644 --- a/.github/workflows/flutter_test.yml +++ b/.github/workflows/flutter_test.yml @@ -197,12 +197,31 @@ jobs: - name: flutter pub get run: flutter pub get - - uses: nanasess/setup-chromedriver@v2 - - run: | - Xvfb :99 & - export DISPLAY=:99 - chromedriver --url-base=/wd/hub --port=4444 & + - name: Install Xvfb and dependencies + run: | + sudo apt-get update + sudo apt-get install -y xvfb + sudo apt-get -y install xorg xvfb gtk2-engines-pixbuf + sudo apt-get -y install dbus-x11 xfonts-base xfonts-100dpi xfonts-75dpi xfonts-cyrillic xfonts-scalable + sudo apt-get -y install imagemagick x11-apps - - name: run integration test + - name: Setup ChromeDriver + uses: nanasess/setup-chromedriver@v2 + + - name: Start Xvfb and run tests run: | - flutter drive --driver=integration_test/test_driver/web_driver.dart --target=integration_test/web_sdk_test.dart -d chrome \ No newline at end of file + # Start Xvfb with specific screen settings + Xvfb -ac :99 -screen 0 1280x1024x16 & + export DISPLAY=:99 + + # Start ChromeDriver + chromedriver --port=4444 & + + # Wait for services to start + sleep 5 + + # Run the tests + flutter drive \ + --driver=integration_test/test_driver/web_driver.dart \ + --target=integration_test/web_sdk_test.dart \ + -d chrome \ No newline at end of file From 57cf95f12ed974eb1d150cba6892e063413fc329 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 3 Dec 2024 16:04:45 +0100 Subject: [PATCH 54/76] update --- .github/workflows/flutter_test.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/flutter_test.yml b/.github/workflows/flutter_test.yml index ff8aeff6dc..31a78bdf0f 100644 --- a/.github/workflows/flutter_test.yml +++ b/.github/workflows/flutter_test.yml @@ -224,4 +224,11 @@ jobs: flutter drive \ --driver=integration_test/test_driver/web_driver.dart \ --target=integration_test/web_sdk_test.dart \ - -d chrome \ No newline at end of file + -d chrome || exit_code=$? + + # Cleanup processes + kill $CHROMEDRIVER_PID + killall Xvfb + + # Exit with the test exit code + exit ${exit_code:-0} \ No newline at end of file From 09264b1d97cf18d216d6eab834c2f155ccd530ae Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 3 Dec 2024 16:30:47 +0100 Subject: [PATCH 55/76] update --- .github/workflows/flutter_test.yml | 7 ++--- .../integration_test/web_sdk_test.dart | 30 ++++++++++++++++++- flutter/example/test.sh | 0 3 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 flutter/example/test.sh diff --git a/.github/workflows/flutter_test.yml b/.github/workflows/flutter_test.yml index 31a78bdf0f..6c8902ef37 100644 --- a/.github/workflows/flutter_test.yml +++ b/.github/workflows/flutter_test.yml @@ -224,11 +224,8 @@ jobs: flutter drive \ --driver=integration_test/test_driver/web_driver.dart \ --target=integration_test/web_sdk_test.dart \ - -d chrome || exit_code=$? + -d chrome # Cleanup processes kill $CHROMEDRIVER_PID - killall Xvfb - - # Exit with the test exit code - exit ${exit_code:-0} \ No newline at end of file + killall Xvfb \ No newline at end of file diff --git a/flutter/example/integration_test/web_sdk_test.dart b/flutter/example/integration_test/web_sdk_test.dart index f42838bd28..207ffd44aa 100644 --- a/flutter/example/integration_test/web_sdk_test.dart +++ b/flutter/example/integration_test/web_sdk_test.dart @@ -1,19 +1,33 @@ @TestOn('browser') +library flutter_test; +// ignore: avoid_web_libraries_in_flutter import 'dart:html'; import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:sentry_flutter/src/integrations/web_sdk_integration.dart'; +import 'package:sentry_flutter_example/main.dart' as app; + +// We can use dart:html, this is meant to be tested on Flutter Web and not WASM +// This integration test can be changed later when we actually do support WASM void main() { group('Web SDK Integration', () { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + tearDown(() async { await Sentry.close(); }); - test('Injects script into document head', () async { + testWidgets('Injects script into document head', (tester) async { await SentryFlutter.init((options) { options.dsn = 'https://abc@def.ingest.sentry.io/1234567'; + // ignore: invalid_use_of_internal_member + options.automatedTestMode = true; + }, appRunner: () async { + await tester.pumpWidget(const app.MyApp()); }); final scripts = document @@ -24,5 +38,19 @@ void main() { // should inject the debug script expect(scripts.first.src, contains('bundle.tracing.js')); }); + + testWidgets('Adds Integration', (tester) async { + await SentryFlutter.init((options) { + options.dsn = 'https://abc@def.ingest.sentry.io/1234567'; + // ignore: invalid_use_of_internal_member + options.automatedTestMode = true; + }, appRunner: () async { + await tester.pumpWidget(const app.MyApp()); + }); + + // ignore: invalid_use_of_internal_member + final integrations = Sentry.currentHub.options.integrations; + expect(integrations, contains(isA())); + }); }); } diff --git a/flutter/example/test.sh b/flutter/example/test.sh new file mode 100644 index 0000000000..e69de29bb2 From e79977f41ae31dc1c85af4c5c75f2c725a5182da Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 3 Dec 2024 16:31:35 +0100 Subject: [PATCH 56/76] remove file --- flutter/example/test.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 flutter/example/test.sh diff --git a/flutter/example/test.sh b/flutter/example/test.sh deleted file mode 100644 index e69de29bb2..0000000000 From a433e9f568891f1016ac9e2cc5188b5112277793 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 3 Dec 2024 16:34:27 +0100 Subject: [PATCH 57/76] update --- .github/workflows/flutter_test.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/flutter_test.yml b/.github/workflows/flutter_test.yml index 6c8902ef37..ff8aeff6dc 100644 --- a/.github/workflows/flutter_test.yml +++ b/.github/workflows/flutter_test.yml @@ -224,8 +224,4 @@ jobs: flutter drive \ --driver=integration_test/test_driver/web_driver.dart \ --target=integration_test/web_sdk_test.dart \ - -d chrome - - # Cleanup processes - kill $CHROMEDRIVER_PID - killall Xvfb \ No newline at end of file + -d chrome \ No newline at end of file From 4987b8af5825ad1d1add7a671ec9e3869ba85df0 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 3 Dec 2024 17:28:34 +0100 Subject: [PATCH 58/76] update --- flutter/example/integration_test/utils.dart | 9 ++++ .../integration_test/web_sdk_test.dart | 50 +++++++++++++++---- 2 files changed, 48 insertions(+), 11 deletions(-) create mode 100644 flutter/example/integration_test/utils.dart diff --git a/flutter/example/integration_test/utils.dart b/flutter/example/integration_test/utils.dart new file mode 100644 index 0000000000..ba7f3e8de4 --- /dev/null +++ b/flutter/example/integration_test/utils.dart @@ -0,0 +1,9 @@ +import 'package:sentry_flutter/sentry_flutter.dart'; + +const fakeDsn = 'https://abc@def.ingest.sentry.io/1234567'; + +void defaultTestOptionsInitializer(SentryFlutterOptions options) { + options.dsn = fakeDsn; + // ignore: invalid_use_of_internal_member + options.automatedTestMode = true; +} diff --git a/flutter/example/integration_test/web_sdk_test.dart b/flutter/example/integration_test/web_sdk_test.dart index 207ffd44aa..786b3e5e7c 100644 --- a/flutter/example/integration_test/web_sdk_test.dart +++ b/flutter/example/integration_test/web_sdk_test.dart @@ -1,3 +1,5 @@ +// ignore_for_file: invalid_use_of_internal_member + @TestOn('browser') library flutter_test; @@ -10,6 +12,8 @@ import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:sentry_flutter/src/integrations/web_sdk_integration.dart'; import 'package:sentry_flutter_example/main.dart' as app; +import 'utils.dart'; + // We can use dart:html, this is meant to be tested on Flutter Web and not WASM // This integration test can be changed later when we actually do support WASM @@ -21,11 +25,10 @@ void main() { await Sentry.close(); }); - testWidgets('Injects script into document head', (tester) async { + testWidgets('Production mode: injects correct script', (tester) async { await SentryFlutter.init((options) { - options.dsn = 'https://abc@def.ingest.sentry.io/1234567'; - // ignore: invalid_use_of_internal_member - options.automatedTestMode = true; + defaultTestOptionsInitializer(options); + options.platformChecker = _FakePlatformChecker(isDebug: false); }, appRunner: () async { await tester.pumpWidget(const app.MyApp()); }); @@ -35,22 +38,47 @@ void main() { .map((script) => script as ScriptElement) .toList(); - // should inject the debug script + expect(scripts.first.src, contains('bundle.tracing.min.js')); + expect(scripts.first.integrity, isNotEmpty); + expect(scripts.first.crossOrigin, isNotEmpty); + }); + + testWidgets('Debug mode: injects correct script', (tester) async { + // by default in debug mode, no need to add fake platform checker + await SentryFlutter.init(defaultTestOptionsInitializer, + appRunner: () async { + await tester.pumpWidget(const app.MyApp()); + }); + + final scripts = document + .querySelectorAll('script') + .map((script) => script as ScriptElement) + .toList(); + expect(scripts.first.src, contains('bundle.tracing.js')); + expect(scripts.first.integrity, isNotEmpty); + expect(scripts.first.crossOrigin, isNotEmpty); }); testWidgets('Adds Integration', (tester) async { - await SentryFlutter.init((options) { - options.dsn = 'https://abc@def.ingest.sentry.io/1234567'; - // ignore: invalid_use_of_internal_member - options.automatedTestMode = true; - }, appRunner: () async { + await SentryFlutter.init(defaultTestOptionsInitializer, + appRunner: () async { await tester.pumpWidget(const app.MyApp()); }); - // ignore: invalid_use_of_internal_member final integrations = Sentry.currentHub.options.integrations; expect(integrations, contains(isA())); }); }); } + +class _FakePlatformChecker extends PlatformChecker { + _FakePlatformChecker({ + this.isDebug = false, + }); + + final bool isDebug; + + @override + bool isDebugMode() => isDebug; +} From 5c237a2f65f1da3deb5f6f2f13156ffbdc1ec0cd Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 3 Dec 2024 22:29:26 +0100 Subject: [PATCH 59/76] fix tests --- flutter/test/web/sentry_script_loader_tt_not_enforced_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/test/web/sentry_script_loader_tt_not_enforced_test.dart b/flutter/test/web/sentry_script_loader_tt_not_enforced_test.dart index 6e132651cd..0da5941cb8 100644 --- a/flutter/test/web/sentry_script_loader_tt_not_enforced_test.dart +++ b/flutter/test/web/sentry_script_loader_tt_not_enforced_test.dart @@ -6,7 +6,7 @@ import 'package:sentry_flutter/src/web/script_loader/sentry_script_loader.dart'; import 'package:sentry_flutter/src/web/sentry_js_bundle.dart'; import '../mocks.dart'; -import 'dom_api/html_script_dom_api.dart'; +import 'dom_api/script_dom_api.dart'; // The other TT tests will be split up into multiple files // because TrustedTypes cannot be relaxed after they are set From 1c9010a7d7c172e357016c59349129dee48d088f Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 3 Dec 2024 23:19:29 +0100 Subject: [PATCH 60/76] update --- .github/workflows/flutter_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/flutter_test.yml b/.github/workflows/flutter_test.yml index ff8aeff6dc..c522fcb7fd 100644 --- a/.github/workflows/flutter_test.yml +++ b/.github/workflows/flutter_test.yml @@ -206,7 +206,7 @@ jobs: sudo apt-get -y install imagemagick x11-apps - name: Setup ChromeDriver - uses: nanasess/setup-chromedriver@v2 + uses: nanasess/setup-chromedriver@e93e57b843c0c92788f22483f1a31af8ee48db25 # pin@2.3.0 - name: Start Xvfb and run tests run: | From cac20f36552a1ebd809ba2dbbefaa0427a6c0e18 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 4 Dec 2024 14:31:04 +0100 Subject: [PATCH 61/76] update docs and test cases --- .../src/web/script_loader/html_script_dom_api.dart | 9 +++------ .../src/web/script_loader/sentry_script_loader.dart | 9 +++++++-- .../src/web/script_loader/web_script_dom_api.dart | 10 ++++++---- .../web/sentry_script_loader_tt_custom_test.dart | 12 +++++++----- .../web/sentry_script_loader_tt_forbidden_test.dart | 12 +++++++----- 5 files changed, 30 insertions(+), 22 deletions(-) diff --git a/flutter/lib/src/web/script_loader/html_script_dom_api.dart b/flutter/lib/src/web/script_loader/html_script_dom_api.dart index fd9fd01ba5..5c8d85b048 100644 --- a/flutter/lib/src/web/script_loader/html_script_dom_api.dart +++ b/flutter/lib/src/web/script_loader/html_script_dom_api.dart @@ -5,7 +5,7 @@ import 'dart:js_util' as js_util; import '../../../sentry_flutter.dart'; import 'sentry_script_loader.dart'; -Future loadScript(String src, SentryOptions? options, +Future loadScript(String src, SentryOptions options, {String? integrity, String trustedTypePolicyName = defaultTrustedPolicyName}) { final completer = Completer(); @@ -32,15 +32,12 @@ Future loadScript(String src, SentryOptions? options, js_util.callMethod(policy as Object, 'createScriptURL', [src]); // Set the trusted URL using js_util } catch (e) { - options?.logger( + options.logger( SentryLevel.warning, 'SentryScriptLoader: failed to created trusted url', exception: e, ); - // ignore: invalid_use_of_internal_member - if (options!.automatedTestMode) { - throw TrustedTypesException(); - } + return Future.value(); } } diff --git a/flutter/lib/src/web/script_loader/sentry_script_loader.dart b/flutter/lib/src/web/script_loader/sentry_script_loader.dart index ca6ac35a92..a628c2ab97 100644 --- a/flutter/lib/src/web/script_loader/sentry_script_loader.dart +++ b/flutter/lib/src/web/script_loader/sentry_script_loader.dart @@ -14,9 +14,14 @@ class SentryScriptLoader { final SentryFlutterOptions _options; bool _scriptLoaded = false; - /// Loads scripts into the document async + /// Loads the scripts into the web page with support for Trusted Types security policy. /// - /// Idempotent: does nothing if scripts are already loaded. + /// The function handles three Trusted Types scenarios: + /// 1. No Trusted Types configured - Scripts load normally + /// 2. Custom Trusted Types policy - Uses provided policy name to create trusted URLs + /// 3. Trusted Types forbidden - Throws TrustedTypesException + /// + /// The function is only executed once and will be guarded by a flag afterwards. Future loadWebSdk(List> scripts, {String trustedTypePolicyName = defaultTrustedPolicyName}) async { if (_scriptLoaded) return; diff --git a/flutter/lib/src/web/script_loader/web_script_dom_api.dart b/flutter/lib/src/web/script_loader/web_script_dom_api.dart index 3d4a46441b..af196a2d85 100644 --- a/flutter/lib/src/web/script_loader/web_script_dom_api.dart +++ b/flutter/lib/src/web/script_loader/web_script_dom_api.dart @@ -27,10 +27,12 @@ Future loadScript(String src, SentryOptions options, )); trustedUrl = policy.createScriptURL(src, null); } catch (e) { - // ignore: invalid_use_of_internal_member - if (options.automatedTestMode) { - throw TrustedTypesException(); - } + options.logger( + SentryLevel.warning, + 'SentryScriptLoader: failed to created trusted url', + exception: e, + ); + return Future.value(); } } diff --git a/flutter/test/web/sentry_script_loader_tt_custom_test.dart b/flutter/test/web/sentry_script_loader_tt_custom_test.dart index bfbe99f82b..15a1939e1c 100644 --- a/flutter/test/web/sentry_script_loader_tt_custom_test.dart +++ b/flutter/test/web/sentry_script_loader_tt_custom_test.dart @@ -27,12 +27,14 @@ void main() { 'content': "trusted-types my-custom-policy-name 'allow-duplicates';", }); - test('Wrong policy name: Fail with TrustedTypesException', () { - expect(() async { - final sut = fixture.getSut(); + test('Wrong policy name: does not inject script', () async { + final sut = fixture.getSut(); + + await sut.loadWebSdk(productionScripts); - await sut.loadWebSdk(productionScripts); - }, throwsA(isA())); + final script = querySelectorAll('script').where((element) => + element.src.contains('$jsSdkVersion/bundle.tracing.min.js')); + expect(script, isEmpty); }); test('Correct policy name: Completes', () { diff --git a/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart b/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart index 82c8855ecb..09ac632b93 100644 --- a/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart +++ b/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart @@ -27,12 +27,14 @@ void main() { 'content': "trusted-types 'none';", }); - test('Fail with TrustedTypesException', () { - expect(() async { - final sut = fixture.getSut(); + test('Does not inject script', () async { + final sut = fixture.getSut(); - await sut.loadWebSdk(productionScripts); - }, throwsA(isA())); + await sut.loadWebSdk(productionScripts); + + final script = querySelectorAll('script').where((element) => + element.src.contains('$jsSdkVersion/bundle.tracing.min.js')); + expect(script, isEmpty); }); }); } From d441a0b07ffa0515142a3cc544458ab4aa7e1ebb Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 4 Dec 2024 15:48:00 +0100 Subject: [PATCH 62/76] add todo --- flutter/example/integration_test/web_sdk_test.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flutter/example/integration_test/web_sdk_test.dart b/flutter/example/integration_test/web_sdk_test.dart index 786b3e5e7c..1cfb0a0eeb 100644 --- a/flutter/example/integration_test/web_sdk_test.dart +++ b/flutter/example/integration_test/web_sdk_test.dart @@ -60,6 +60,8 @@ void main() { expect(scripts.first.crossOrigin, isNotEmpty); }); + // TODO: verify javascript sdk works + testWidgets('Adds Integration', (tester) async { await SentryFlutter.init(defaultTestOptionsInitializer, appRunner: () async { From fb2494f018fab35ae0633edb751a135f82af8190 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 4 Dec 2024 17:29:00 +0100 Subject: [PATCH 63/76] update --- .../integration_test/web_sdk_test.dart | 56 ++++++------------- 1 file changed, 17 insertions(+), 39 deletions(-) diff --git a/flutter/example/integration_test/web_sdk_test.dart b/flutter/example/integration_test/web_sdk_test.dart index 1cfb0a0eeb..eeafadf7d4 100644 --- a/flutter/example/integration_test/web_sdk_test.dart +++ b/flutter/example/integration_test/web_sdk_test.dart @@ -3,13 +3,12 @@ @TestOn('browser') library flutter_test; -// ignore: avoid_web_libraries_in_flutter -import 'dart:html'; +import 'dart:js'; +import 'dart:js_interop'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; -import 'package:sentry_flutter/src/integrations/web_sdk_integration.dart'; import 'package:sentry_flutter_example/main.dart' as app; import 'utils.dart'; @@ -25,51 +24,30 @@ void main() { await Sentry.close(); }); - testWidgets('Production mode: injects correct script', (tester) async { - await SentryFlutter.init((options) { - defaultTestOptionsInitializer(options); - options.platformChecker = _FakePlatformChecker(isDebug: false); - }, appRunner: () async { - await tester.pumpWidget(const app.MyApp()); - }); - - final scripts = document - .querySelectorAll('script') - .map((script) => script as ScriptElement) - .toList(); - - expect(scripts.first.src, contains('bundle.tracing.min.js')); - expect(scripts.first.integrity, isNotEmpty); - expect(scripts.first.crossOrigin, isNotEmpty); - }); - - testWidgets('Debug mode: injects correct script', (tester) async { - // by default in debug mode, no need to add fake platform checker + testWidgets('Sentry JS SDK is callable', (tester) async { await SentryFlutter.init(defaultTestOptionsInitializer, appRunner: () async { await tester.pumpWidget(const app.MyApp()); }); - final scripts = document - .querySelectorAll('script') - .map((script) => script as ScriptElement) - .toList(); + const expectedMessage = 'test message'; + final beforeSendFn = JsFunction.withThis((thisArg, event, hint) { + final actualMessage = event['message']; + expect(actualMessage, equals(actualMessage)); - expect(scripts.first.src, contains('bundle.tracing.js')); - expect(scripts.first.integrity, isNotEmpty); - expect(scripts.first.crossOrigin, isNotEmpty); - }); + return event; + }); - // TODO: verify javascript sdk works + final Map options = { + 'dsn': app.exampleDsn, + 'beforeSend': beforeSendFn, + 'defaultIntegrations': [], + }; - testWidgets('Adds Integration', (tester) async { - await SentryFlutter.init(defaultTestOptionsInitializer, - appRunner: () async { - await tester.pumpWidget(const app.MyApp()); - }); + final sentry = context['Sentry'] as JsObject; + sentry.callMethod('init', [JsObject.jsify(options)]); - final integrations = Sentry.currentHub.options.integrations; - expect(integrations, contains(isA())); + sentry.callMethod('captureMessage', [expectedMessage.toJS]); }); }); } From 5c4c498119ce47ff2665785ebd3564a398db9be3 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 4 Dec 2024 17:29:24 +0100 Subject: [PATCH 64/76] update --- flutter/example/integration_test/web_sdk_test.dart | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/flutter/example/integration_test/web_sdk_test.dart b/flutter/example/integration_test/web_sdk_test.dart index eeafadf7d4..73c25dc9bd 100644 --- a/flutter/example/integration_test/web_sdk_test.dart +++ b/flutter/example/integration_test/web_sdk_test.dart @@ -3,6 +3,7 @@ @TestOn('browser') library flutter_test; +// ignore: avoid_web_libraries_in_flutter import 'dart:js'; import 'dart:js_interop'; @@ -51,14 +52,3 @@ void main() { }); }); } - -class _FakePlatformChecker extends PlatformChecker { - _FakePlatformChecker({ - this.isDebug = false, - }); - - final bool isDebug; - - @override - bool isDebugMode() => isDebug; -} From f78adc02dbc3a66a1e8318510513dec076352641 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 4 Dec 2024 18:28:10 +0100 Subject: [PATCH 65/76] add doc and fix tests --- .../web/script_loader/sentry_script_loader.dart | 2 ++ .../test/web/dom_api/html_script_dom_api.dart | 4 ++-- .../test/web/dom_api/noop_script_dom_api.dart | 2 +- flutter/test/web/dom_api/web_script_dom_api.dart | 4 ++-- flutter/test/web/sentry_script_loader_test.dart | 16 ++++++++-------- .../web/sentry_script_loader_tt_custom_test.dart | 12 ++++++++++-- .../sentry_script_loader_tt_forbidden_test.dart | 3 ++- ...entry_script_loader_tt_not_enforced_test.dart | 10 +++++++++- 8 files changed, 36 insertions(+), 17 deletions(-) diff --git a/flutter/lib/src/web/script_loader/sentry_script_loader.dart b/flutter/lib/src/web/script_loader/sentry_script_loader.dart index a628c2ab97..3376397e1b 100644 --- a/flutter/lib/src/web/script_loader/sentry_script_loader.dart +++ b/flutter/lib/src/web/script_loader/sentry_script_loader.dart @@ -22,6 +22,8 @@ class SentryScriptLoader { /// 3. Trusted Types forbidden - Throws TrustedTypesException /// /// The function is only executed once and will be guarded by a flag afterwards. + /// + /// TrustedTypes implementation inspired by https://pub.dev/packages/google_identity_services_web Future loadWebSdk(List> scripts, {String trustedTypePolicyName = defaultTrustedPolicyName}) async { if (_scriptLoaded) return; diff --git a/flutter/test/web/dom_api/html_script_dom_api.dart b/flutter/test/web/dom_api/html_script_dom_api.dart index 854f11eda2..55a1ac6bd5 100644 --- a/flutter/test/web/dom_api/html_script_dom_api.dart +++ b/flutter/test/web/dom_api/html_script_dom_api.dart @@ -16,8 +16,8 @@ class HtmlScriptElement implements TestScriptElement { String get src => element.src; } -List querySelectorAll(String query) { - final scripts = document.querySelectorAll(query); +List fetchAllScripts() { + final scripts = document.querySelectorAll('script'); return scripts .map((script) => HtmlScriptElement(script as ScriptElement)) .toList(); diff --git a/flutter/test/web/dom_api/noop_script_dom_api.dart b/flutter/test/web/dom_api/noop_script_dom_api.dart index ea887c2a1c..bdcd1ea66a 100644 --- a/flutter/test/web/dom_api/noop_script_dom_api.dart +++ b/flutter/test/web/dom_api/noop_script_dom_api.dart @@ -1,5 +1,5 @@ import 'script_dom_api.dart'; -List querySelectorAll(String query) => []; +List fetchAllScripts() => []; void injectMetaTag(Map attributes) {} diff --git a/flutter/test/web/dom_api/web_script_dom_api.dart b/flutter/test/web/dom_api/web_script_dom_api.dart index 9cfe26d8dd..287aaa4a6a 100644 --- a/flutter/test/web/dom_api/web_script_dom_api.dart +++ b/flutter/test/web/dom_api/web_script_dom_api.dart @@ -17,8 +17,8 @@ class _ScriptElement implements TestScriptElement { String get src => element.src; } -List querySelectorAll(String query) { - final scripts = document.querySelectorAll(query); +List fetchAllScripts() { + final scripts = document.querySelectorAll('script'); List elements = []; for (int i = 0; i < scripts.length; i++) { diff --git a/flutter/test/web/sentry_script_loader_test.dart b/flutter/test/web/sentry_script_loader_test.dart index 834291da71..a01f7d82b0 100644 --- a/flutter/test/web/sentry_script_loader_test.dart +++ b/flutter/test/web/sentry_script_loader_test.dart @@ -18,7 +18,7 @@ void main() { }); tearDown(() { - final existingScripts = querySelectorAll('script'); + final existingScripts = fetchAllScripts(); for (final script in existingScripts) { script.remove(); } @@ -29,7 +29,7 @@ void main() { await sut.loadWebSdk(productionScripts); - final scripts = querySelectorAll('script'); + final scripts = fetchAllScripts(); expect( scripts.first.src, endsWith('$jsSdkVersion/bundle.tracing.min.js')); @@ -40,7 +40,7 @@ void main() { await sut.loadWebSdk(debugScripts); - final scripts = querySelectorAll('script'); + final scripts = fetchAllScripts(); expect(scripts.first.src, endsWith('$jsSdkVersion/bundle.tracing.js')); }); @@ -50,10 +50,10 @@ void main() { await sut.loadWebSdk(productionScripts); - final initialScriptCount = querySelectorAll('script').length; + final initialScriptCount = fetchAllScripts().length; await sut.loadWebSdk(productionScripts); - expect(querySelectorAll('script').length, initialScriptCount); + expect(fetchAllScripts().length, initialScriptCount); }); test('Handles script loading failures', () async { @@ -71,7 +71,7 @@ void main() { // loading after the failure still works await sut.loadWebSdk(productionScripts); - final scripts = querySelectorAll('script'); + final scripts = fetchAllScripts(); expect( scripts.first.src, endsWith('$jsSdkVersion/bundle.tracing.min.js')); }); @@ -92,7 +92,7 @@ void main() { // loading after the failure still works await sut.loadWebSdk(productionScripts); - final scripts = querySelectorAll('script'); + final scripts = fetchAllScripts(); expect( scripts.first.src, endsWith('$jsSdkVersion/bundle.tracing.min.js')); }); @@ -104,7 +104,7 @@ void main() { await loadScript('https://google.com', fixture.options); await sut.loadWebSdk(productionScripts); - final scriptElements = querySelectorAll('script'); + final scriptElements = fetchAllScripts(); expect(scriptElements.first.src, endsWith('$jsSdkVersion/bundle.tracing.min.js')); }); diff --git a/flutter/test/web/sentry_script_loader_tt_custom_test.dart b/flutter/test/web/sentry_script_loader_tt_custom_test.dart index 15a1939e1c..fd95ed4920 100644 --- a/flutter/test/web/sentry_script_loader_tt_custom_test.dart +++ b/flutter/test/web/sentry_script_loader_tt_custom_test.dart @@ -13,6 +13,7 @@ import 'dom_api/script_dom_api.dart'; // * sentry_script_loader_test.dart : default TT configuration (not enforced) // * sentry_script_loader_tt_custom_test.dart : TT are customized, but allowed // * sentry_script_loader_tt_forbidden_test.dart: TT are completely disallowed +// tests inspired by https://pub.dev/packages/google_identity_services_web void main() { group('loadWebSdk (TrustedTypes configured)', () { @@ -22,6 +23,13 @@ void main() { fixture = Fixture(); }); + tearDown(() { + final existingScripts = fetchAllScripts(); + for (final script in existingScripts) { + script.remove(); + } + }); + injectMetaTag({ 'http-equiv': 'Content-Security-Policy', 'content': "trusted-types my-custom-policy-name 'allow-duplicates';", @@ -32,7 +40,7 @@ void main() { await sut.loadWebSdk(productionScripts); - final script = querySelectorAll('script').where((element) => + final script = fetchAllScripts().where((element) => element.src.contains('$jsSdkVersion/bundle.tracing.min.js')); expect(script, isEmpty); }); @@ -44,7 +52,7 @@ void main() { trustedTypePolicyName: 'my-custom-policy-name'); expect(done, isA>()); - final scripts = querySelectorAll('script'); + final scripts = fetchAllScripts(); expect( scripts.first.src, endsWith('$jsSdkVersion/bundle.tracing.min.js')); }); diff --git a/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart b/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart index 09ac632b93..78d71f9b17 100644 --- a/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart +++ b/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart @@ -13,6 +13,7 @@ import 'dom_api/script_dom_api.dart'; // * sentry_script_loader_test.dart : default TT configuration (not enforced) // * sentry_script_loader_tt_custom_test.dart : TT are customized, but allowed // * sentry_script_loader_tt_forbidden_test.dart: TT are completely disallowed +// tests inspired by https://pub.dev/packages/google_identity_services_web void main() { group('loadWebSdk (TrustedTypes forbidden)', () { @@ -32,7 +33,7 @@ void main() { await sut.loadWebSdk(productionScripts); - final script = querySelectorAll('script').where((element) => + final script = fetchAllScripts().where((element) => element.src.contains('$jsSdkVersion/bundle.tracing.min.js')); expect(script, isEmpty); }); diff --git a/flutter/test/web/sentry_script_loader_tt_not_enforced_test.dart b/flutter/test/web/sentry_script_loader_tt_not_enforced_test.dart index 0da5941cb8..d1a9c7f7d5 100644 --- a/flutter/test/web/sentry_script_loader_tt_not_enforced_test.dart +++ b/flutter/test/web/sentry_script_loader_tt_not_enforced_test.dart @@ -13,6 +13,7 @@ import 'dom_api/script_dom_api.dart'; // * sentry_script_loader_tt_not_enforced_test.dart : default TT configuration (not enforced) // * sentry_script_loader_tt_custom_test.dart : TT are customized, but allowed // * sentry_script_loader_tt_forbidden_test.dart: TT are completely disallowed +// tests inspired by https://pub.dev/packages/google_identity_services_web void main() { group('loadWebSdk (no TrustedTypes configured)', () { @@ -22,12 +23,19 @@ void main() { fixture = Fixture(); }); + tearDown(() { + final existingScripts = fetchAllScripts(); + for (final script in existingScripts) { + script.remove(); + } + }); + test('Injects script into document head', () async { final sut = fixture.getSut(); await sut.loadWebSdk(productionScripts); - final scripts = querySelectorAll('script'); + final scripts = fetchAllScripts(); expect( scripts.first.src, contains('$jsSdkVersion/bundle.tracing.min.js')); }); From 0584a748e7f6fd5b00cc8959fd71608efddd0455 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 4 Dec 2024 18:37:22 +0100 Subject: [PATCH 66/76] update tests --- .../test/web/sentry_script_loader_test.dart | 3 +- .../sentry_script_loader_tt_custom_test.dart | 1 - ...entry_script_loader_tt_forbidden_test.dart | 1 - ...ry_script_loader_tt_not_enforced_test.dart | 51 ------------------- 4 files changed, 1 insertion(+), 55 deletions(-) delete mode 100644 flutter/test/web/sentry_script_loader_tt_not_enforced_test.dart diff --git a/flutter/test/web/sentry_script_loader_test.dart b/flutter/test/web/sentry_script_loader_test.dart index a01f7d82b0..868020fdc2 100644 --- a/flutter/test/web/sentry_script_loader_test.dart +++ b/flutter/test/web/sentry_script_loader_test.dart @@ -24,13 +24,13 @@ void main() { } }); + // automatically tests TrustedType not configured test('Loads production scripts by default', () async { final sut = fixture.getSut(); await sut.loadWebSdk(productionScripts); final scripts = fetchAllScripts(); - expect( scripts.first.src, endsWith('$jsSdkVersion/bundle.tracing.min.js')); }); @@ -41,7 +41,6 @@ void main() { await sut.loadWebSdk(debugScripts); final scripts = fetchAllScripts(); - expect(scripts.first.src, endsWith('$jsSdkVersion/bundle.tracing.js')); }); diff --git a/flutter/test/web/sentry_script_loader_tt_custom_test.dart b/flutter/test/web/sentry_script_loader_tt_custom_test.dart index fd95ed4920..305caf1de3 100644 --- a/flutter/test/web/sentry_script_loader_tt_custom_test.dart +++ b/flutter/test/web/sentry_script_loader_tt_custom_test.dart @@ -10,7 +10,6 @@ import 'dom_api/script_dom_api.dart'; // The other TT tests will be split up into multiple files // because TrustedTypes cannot be relaxed after they are set -// * sentry_script_loader_test.dart : default TT configuration (not enforced) // * sentry_script_loader_tt_custom_test.dart : TT are customized, but allowed // * sentry_script_loader_tt_forbidden_test.dart: TT are completely disallowed // tests inspired by https://pub.dev/packages/google_identity_services_web diff --git a/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart b/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart index 78d71f9b17..fd74f1b9e4 100644 --- a/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart +++ b/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart @@ -10,7 +10,6 @@ import 'dom_api/script_dom_api.dart'; // The other TT tests will be split up into multiple files // because TrustedTypes cannot be relaxed after they are set -// * sentry_script_loader_test.dart : default TT configuration (not enforced) // * sentry_script_loader_tt_custom_test.dart : TT are customized, but allowed // * sentry_script_loader_tt_forbidden_test.dart: TT are completely disallowed // tests inspired by https://pub.dev/packages/google_identity_services_web diff --git a/flutter/test/web/sentry_script_loader_tt_not_enforced_test.dart b/flutter/test/web/sentry_script_loader_tt_not_enforced_test.dart deleted file mode 100644 index d1a9c7f7d5..0000000000 --- a/flutter/test/web/sentry_script_loader_tt_not_enforced_test.dart +++ /dev/null @@ -1,51 +0,0 @@ -@TestOn('browser') -library flutter_test; - -import 'package:flutter_test/flutter_test.dart'; -import 'package:sentry_flutter/src/web/script_loader/sentry_script_loader.dart'; -import 'package:sentry_flutter/src/web/sentry_js_bundle.dart'; - -import '../mocks.dart'; -import 'dom_api/script_dom_api.dart'; - -// The other TT tests will be split up into multiple files -// because TrustedTypes cannot be relaxed after they are set -// * sentry_script_loader_tt_not_enforced_test.dart : default TT configuration (not enforced) -// * sentry_script_loader_tt_custom_test.dart : TT are customized, but allowed -// * sentry_script_loader_tt_forbidden_test.dart: TT are completely disallowed -// tests inspired by https://pub.dev/packages/google_identity_services_web - -void main() { - group('loadWebSdk (no TrustedTypes configured)', () { - late Fixture fixture; - - setUp(() { - fixture = Fixture(); - }); - - tearDown(() { - final existingScripts = fetchAllScripts(); - for (final script in existingScripts) { - script.remove(); - } - }); - - test('Injects script into document head', () async { - final sut = fixture.getSut(); - - await sut.loadWebSdk(productionScripts); - - final scripts = fetchAllScripts(); - expect( - scripts.first.src, contains('$jsSdkVersion/bundle.tracing.min.js')); - }); - }); -} - -class Fixture { - final options = defaultTestOptions(); - - SentryScriptLoader getSut() { - return SentryScriptLoader(options); - } -} From 859719281ab661c9fd91731a85b0567dfc3eeb8a Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Thu, 5 Dec 2024 11:55:12 +0100 Subject: [PATCH 67/76] update --- .../lib/src/web/script_loader/html_script_dom_api.dart | 9 ++------- .../lib/src/web/script_loader/sentry_script_loader.dart | 9 ++++----- .../lib/src/web/script_loader/web_script_dom_api.dart | 8 ++------ .../test/web/sentry_script_loader_tt_custom_test.dart | 4 +++- .../test/web/sentry_script_loader_tt_forbidden_test.dart | 4 +++- 5 files changed, 14 insertions(+), 20 deletions(-) diff --git a/flutter/lib/src/web/script_loader/html_script_dom_api.dart b/flutter/lib/src/web/script_loader/html_script_dom_api.dart index 5c8d85b048..d2b4d46d7f 100644 --- a/flutter/lib/src/web/script_loader/html_script_dom_api.dart +++ b/flutter/lib/src/web/script_loader/html_script_dom_api.dart @@ -30,14 +30,9 @@ Future loadScript(String src, SentryOptions options, ]); trustedUrl = js_util.callMethod(policy as Object, 'createScriptURL', [src]); - // Set the trusted URL using js_util } catch (e) { - options.logger( - SentryLevel.warning, - 'SentryScriptLoader: failed to created trusted url', - exception: e, - ); - return Future.value(); + // will be caught by loadWebSdk + throw TrustedTypesException(); } } diff --git a/flutter/lib/src/web/script_loader/sentry_script_loader.dart b/flutter/lib/src/web/script_loader/sentry_script_loader.dart index 3376397e1b..9a743fb541 100644 --- a/flutter/lib/src/web/script_loader/sentry_script_loader.dart +++ b/flutter/lib/src/web/script_loader/sentry_script_loader.dart @@ -19,7 +19,7 @@ class SentryScriptLoader { /// The function handles three Trusted Types scenarios: /// 1. No Trusted Types configured - Scripts load normally /// 2. Custom Trusted Types policy - Uses provided policy name to create trusted URLs - /// 3. Trusted Types forbidden - Throws TrustedTypesException + /// 3. Trusted Types forbidden - Scripts are not loaded /// /// The function is only executed once and will be guarded by a flag afterwards. /// @@ -43,9 +43,8 @@ class SentryScriptLoader { _scriptLoaded = true; _options.logger(SentryLevel.debug, 'JS SDK integration: all Sentry scripts loaded successfully.'); - } catch (e, stackTrace) { - _options.logger( - SentryLevel.error, 'Failed to load Sentry scripts: $e\n$stackTrace'); + } catch (e) { + _options.logger(SentryLevel.error, 'Failed to load Sentry scripts: $e'); if (_options.automatedTestMode) { rethrow; } @@ -55,7 +54,7 @@ class SentryScriptLoader { /// Exception thrown if the Trusted Types feature is supported, enabled, and it /// has prevented this loader from injecting the Sentry JS SDK -@visibleForTesting +@internal class TrustedTypesException implements Exception { TrustedTypesException(); } diff --git a/flutter/lib/src/web/script_loader/web_script_dom_api.dart b/flutter/lib/src/web/script_loader/web_script_dom_api.dart index af196a2d85..1390f8b165 100644 --- a/flutter/lib/src/web/script_loader/web_script_dom_api.dart +++ b/flutter/lib/src/web/script_loader/web_script_dom_api.dart @@ -27,12 +27,8 @@ Future loadScript(String src, SentryOptions options, )); trustedUrl = policy.createScriptURL(src, null); } catch (e) { - options.logger( - SentryLevel.warning, - 'SentryScriptLoader: failed to created trusted url', - exception: e, - ); - return Future.value(); + // will be caught by loadWebSdk + throw TrustedTypesException(); } } diff --git a/flutter/test/web/sentry_script_loader_tt_custom_test.dart b/flutter/test/web/sentry_script_loader_tt_custom_test.dart index 305caf1de3..fbb4fc5b19 100644 --- a/flutter/test/web/sentry_script_loader_tt_custom_test.dart +++ b/flutter/test/web/sentry_script_loader_tt_custom_test.dart @@ -37,7 +37,9 @@ void main() { test('Wrong policy name: does not inject script', () async { final sut = fixture.getSut(); - await sut.loadWebSdk(productionScripts); + expect(() async { + await sut.loadWebSdk(productionScripts); + }, throwsA(isA())); final script = fetchAllScripts().where((element) => element.src.contains('$jsSdkVersion/bundle.tracing.min.js')); diff --git a/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart b/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart index fd74f1b9e4..9d3544ec4b 100644 --- a/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart +++ b/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart @@ -30,7 +30,9 @@ void main() { test('Does not inject script', () async { final sut = fixture.getSut(); - await sut.loadWebSdk(productionScripts); + expect(() async { + await sut.loadWebSdk(productionScripts); + }, throwsA(isA())); final script = fetchAllScripts().where((element) => element.src.contains('$jsSdkVersion/bundle.tracing.min.js')); From 6c57cc632201a11001e6e48e48c1acf4ad9f4e21 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Thu, 5 Dec 2024 11:55:45 +0100 Subject: [PATCH 68/76] update --- flutter/lib/src/web/script_loader/sentry_script_loader.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/lib/src/web/script_loader/sentry_script_loader.dart b/flutter/lib/src/web/script_loader/sentry_script_loader.dart index 9a743fb541..9099f64423 100644 --- a/flutter/lib/src/web/script_loader/sentry_script_loader.dart +++ b/flutter/lib/src/web/script_loader/sentry_script_loader.dart @@ -21,7 +21,7 @@ class SentryScriptLoader { /// 2. Custom Trusted Types policy - Uses provided policy name to create trusted URLs /// 3. Trusted Types forbidden - Scripts are not loaded /// - /// The function is only executed once and will be guarded by a flag afterwards. + /// The function is only executed successfully once and will be guarded by a flag afterwards. /// /// TrustedTypes implementation inspired by https://pub.dev/packages/google_identity_services_web Future loadWebSdk(List> scripts, From 485f1969bc1c87cbbf2260306b888a3bd53d499f Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 5 Dec 2024 12:10:22 +0100 Subject: [PATCH 69/76] Update web_sdk_test.dart --- flutter/example/integration_test/web_sdk_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/example/integration_test/web_sdk_test.dart b/flutter/example/integration_test/web_sdk_test.dart index 73c25dc9bd..d922338af8 100644 --- a/flutter/example/integration_test/web_sdk_test.dart +++ b/flutter/example/integration_test/web_sdk_test.dart @@ -34,7 +34,7 @@ void main() { const expectedMessage = 'test message'; final beforeSendFn = JsFunction.withThis((thisArg, event, hint) { final actualMessage = event['message']; - expect(actualMessage, equals(actualMessage)); + expect(actualMessage, equals(expectedMessage)); return event; }); From bf38ab3ed3799a1fda906c9eb1403e5970b528f8 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Thu, 5 Dec 2024 13:22:47 +0100 Subject: [PATCH 70/76] add test with automatedTestMode false --- .../web/sentry_script_loader_tt_custom_test.dart | 13 +++++++++++++ .../web/sentry_script_loader_tt_forbidden_test.dart | 11 +++++++++++ 2 files changed, 24 insertions(+) diff --git a/flutter/test/web/sentry_script_loader_tt_custom_test.dart b/flutter/test/web/sentry_script_loader_tt_custom_test.dart index fbb4fc5b19..8eab13c2e5 100644 --- a/flutter/test/web/sentry_script_loader_tt_custom_test.dart +++ b/flutter/test/web/sentry_script_loader_tt_custom_test.dart @@ -46,6 +46,19 @@ void main() { expect(script, isEmpty); }); + test( + 'Wrong policy name: does not inject script with automatedTestMode false', + () async { + fixture.options.automatedTestMode = false; + final sut = fixture.getSut(); + + await sut.loadWebSdk(productionScripts); + + final script = fetchAllScripts().where((element) => + element.src.contains('$jsSdkVersion/bundle.tracing.min.js')); + expect(script, isEmpty); + }); + test('Correct policy name: Completes', () { final sut = fixture.getSut(); diff --git a/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart b/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart index 9d3544ec4b..47c11d158e 100644 --- a/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart +++ b/flutter/test/web/sentry_script_loader_tt_forbidden_test.dart @@ -38,6 +38,17 @@ void main() { element.src.contains('$jsSdkVersion/bundle.tracing.min.js')); expect(script, isEmpty); }); + + test('Does not inject script with automatedTestMode false', () async { + fixture.options.automatedTestMode = false; + final sut = fixture.getSut(); + + await sut.loadWebSdk(productionScripts); + + final script = fetchAllScripts().where((element) => + element.src.contains('$jsSdkVersion/bundle.tracing.min.js')); + expect(script, isEmpty); + }); }); } From cfd36a0ad3f68208fcb6b1a650e49260e05523dd Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Fri, 6 Dec 2024 14:28:54 +0100 Subject: [PATCH 71/76] update --- flutter/example/integration_test/utils.dart | 24 ++++++--- .../integration_test/web_sdk_test.dart | 49 ++++++++++++------- 2 files changed, 48 insertions(+), 25 deletions(-) diff --git a/flutter/example/integration_test/utils.dart b/flutter/example/integration_test/utils.dart index ba7f3e8de4..ffcac840e6 100644 --- a/flutter/example/integration_test/utils.dart +++ b/flutter/example/integration_test/utils.dart @@ -1,9 +1,21 @@ -import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:flutter/cupertino.dart'; -const fakeDsn = 'https://abc@def.ingest.sentry.io/1234567'; +/// Restores the onError to it's original state. +/// This makes assertion errors readable. +/// +/// testWidgets override Flutter.onError by default +/// If a fail happens during integration tests this would complain that +/// the FlutterError.onError was overwritten and wasn't reset to its +/// state before asserting. +/// +/// This function needs to be executed before assertions. +Future restoreFlutterOnErrorAfter(Future Function() fn) async { + final originalOnError = FlutterError.onError!; + await fn(); + final overriddenOnError = FlutterError.onError!; -void defaultTestOptionsInitializer(SentryFlutterOptions options) { - options.dsn = fakeDsn; - // ignore: invalid_use_of_internal_member - options.automatedTestMode = true; + FlutterError.onError = (FlutterErrorDetails details) { + if (overriddenOnError != originalOnError) overriddenOnError(details); + originalOnError(details); + }; } diff --git a/flutter/example/integration_test/web_sdk_test.dart b/flutter/example/integration_test/web_sdk_test.dart index d922338af8..b0193d97dd 100644 --- a/flutter/example/integration_test/web_sdk_test.dart +++ b/flutter/example/integration_test/web_sdk_test.dart @@ -4,8 +4,8 @@ library flutter_test; // ignore: avoid_web_libraries_in_flutter +import 'dart:async'; import 'dart:js'; -import 'dart:js_interop'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -26,29 +26,40 @@ void main() { }); testWidgets('Sentry JS SDK is callable', (tester) async { - await SentryFlutter.init(defaultTestOptionsInitializer, - appRunner: () async { - await tester.pumpWidget(const app.MyApp()); - }); - + final completer = Completer(); const expectedMessage = 'test message'; - final beforeSendFn = JsFunction.withThis((thisArg, event, hint) { - final actualMessage = event['message']; - expect(actualMessage, equals(expectedMessage)); + String actualMessage = ''; - return event; - }); + await restoreFlutterOnErrorAfter(() async { + await SentryFlutter.init((options) { + options.dsn = app.exampleDsn; + options.automatedTestMode = false; + }, appRunner: () async { + await tester.pumpWidget(const app.MyApp()); + }); + + final beforeSendFn = JsFunction.withThis((thisArg, event, hint) { + actualMessage = event['message']; + completer.complete(); + return event; + }); - final Map options = { - 'dsn': app.exampleDsn, - 'beforeSend': beforeSendFn, - 'defaultIntegrations': [], - }; + final Map options = { + 'dsn': app.exampleDsn, + 'beforeSend': beforeSendFn, + 'defaultIntegrations': [], + }; - final sentry = context['Sentry'] as JsObject; - sentry.callMethod('init', [JsObject.jsify(options)]); + final sentry = context['Sentry'] as JsObject; + sentry.callMethod('init', [JsObject.jsify(options)]); + sentry.callMethod('captureMessage', [expectedMessage]); + }); + + await completer.future.timeout(const Duration(seconds: 5), onTimeout: () { + fail('beforeSend was not triggered'); + }); - sentry.callMethod('captureMessage', [expectedMessage.toJS]); + expect(actualMessage, equals(expectedMessage)); }); }); } From 627ac061ecec33be2c4943b48f8001b920c2d3f9 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 10 Dec 2024 13:42:47 +0100 Subject: [PATCH 72/76] remove debug --- flutter/test/web/sentry_script_loader_test.dart | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/flutter/test/web/sentry_script_loader_test.dart b/flutter/test/web/sentry_script_loader_test.dart index 868020fdc2..0d2609d244 100644 --- a/flutter/test/web/sentry_script_loader_test.dart +++ b/flutter/test/web/sentry_script_loader_test.dart @@ -24,8 +24,8 @@ void main() { } }); - // automatically tests TrustedType not configured - test('Loads production scripts by default', () async { + // automatically tests TrustedType not configured as well + test('Loads production scripts correctly', () async { final sut = fixture.getSut(); await sut.loadWebSdk(productionScripts); @@ -35,7 +35,7 @@ void main() { scripts.first.src, endsWith('$jsSdkVersion/bundle.tracing.min.js')); }); - test('Loads debug scripts when debug is enabled', () async { + test('Loads debug scripts correctly', () async { final sut = fixture.getSut(); await sut.loadWebSdk(debugScripts); @@ -113,8 +113,7 @@ void main() { class Fixture { final options = defaultTestOptions(); - SentryScriptLoader getSut({bool debug = false}) { - options.platformChecker = MockPlatformChecker(isDebug: debug); + SentryScriptLoader getSut() { return SentryScriptLoader(options); } } From 7f02fa2cb0a0627c6bbf24e5625ad4f1ef6912da Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 10 Dec 2024 13:44:31 +0100 Subject: [PATCH 73/76] remove fn --- .../lib/src/web/script_loader/web_script_dom_api.dart | 9 --------- 1 file changed, 9 deletions(-) diff --git a/flutter/lib/src/web/script_loader/web_script_dom_api.dart b/flutter/lib/src/web/script_loader/web_script_dom_api.dart index 1390f8b165..92fc748059 100644 --- a/flutter/lib/src/web/script_loader/web_script_dom_api.dart +++ b/flutter/lib/src/web/script_loader/web_script_dom_api.dart @@ -53,12 +53,3 @@ Future loadScript(String src, SentryOptions options, } return completer.future; } - -void injectMetaTag(Map attributes) { - final HTMLMetaElement meta = - document.createElement('meta') as HTMLMetaElement; - for (final MapEntry attribute in attributes.entries) { - meta.setAttribute(attribute.key, attribute.value); - } - document.head!.appendChild(meta); -} From 1a75a61db6946200ca816dcec6e0ff9d2b268027 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 10 Dec 2024 13:48:27 +0100 Subject: [PATCH 74/76] update restore flutter onError --- flutter/example/devtools_options.yaml | 3 +++ flutter/example/integration_test/utils.dart | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 flutter/example/devtools_options.yaml diff --git a/flutter/example/devtools_options.yaml b/flutter/example/devtools_options.yaml new file mode 100644 index 0000000000..fa0b357c4f --- /dev/null +++ b/flutter/example/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/flutter/example/integration_test/utils.dart b/flutter/example/integration_test/utils.dart index ffcac840e6..c7abf74c33 100644 --- a/flutter/example/integration_test/utils.dart +++ b/flutter/example/integration_test/utils.dart @@ -10,12 +10,12 @@ import 'package:flutter/cupertino.dart'; /// /// This function needs to be executed before assertions. Future restoreFlutterOnErrorAfter(Future Function() fn) async { - final originalOnError = FlutterError.onError!; + final originalOnError = FlutterError.onError; await fn(); - final overriddenOnError = FlutterError.onError!; + final overriddenOnError = FlutterError.onError; FlutterError.onError = (FlutterErrorDetails details) { - if (overriddenOnError != originalOnError) overriddenOnError(details); - originalOnError(details); + if (overriddenOnError != originalOnError) overriddenOnError?.call(details); + originalOnError?.call(details); }; } From 8592e116365e2b7182e17ee1ab390ef81e3ced88 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 10 Dec 2024 16:27:04 +0100 Subject: [PATCH 75/76] fix analyze --- flutter/example/integration_test/web_sdk_test.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/flutter/example/integration_test/web_sdk_test.dart b/flutter/example/integration_test/web_sdk_test.dart index b0193d97dd..d4d876820f 100644 --- a/flutter/example/integration_test/web_sdk_test.dart +++ b/flutter/example/integration_test/web_sdk_test.dart @@ -1,9 +1,8 @@ -// ignore_for_file: invalid_use_of_internal_member +// ignore_for_file: invalid_use_of_internal_member, avoid_web_libraries_in_flutter @TestOn('browser') library flutter_test; -// ignore: avoid_web_libraries_in_flutter import 'dart:async'; import 'dart:js'; From 3f97c2e92aecfef460143c6f418c4bd694656f8d Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 10 Dec 2024 17:40:41 +0100 Subject: [PATCH 76/76] Update flutter.yml --- .github/workflows/flutter.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/flutter.yml b/.github/workflows/flutter.yml index 5b414b6e8e..95dfe52727 100644 --- a/.github/workflows/flutter.yml +++ b/.github/workflows/flutter.yml @@ -4,7 +4,6 @@ on: branches: - main - release/** - - feat/** pull_request: paths: - '.github/workflows/flutter.yml'