From 7e7f0b1ba00d87ca0c564c0aee2b80b9e66a8253 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos <6349682+vaind@users.noreply.github.com> Date: Tue, 25 Jun 2024 10:19:08 +0200 Subject: [PATCH] feat: enable wasm compilation (#2113) * feat: migrates to package:web and js_interop * fix: enhances comment * chore: Adds changelog * chore: formatting * fix: changes min flutter version to 3.13.0 and dart sdk 3.1.0 to be compatible with package:web 0.5.1 * compat with dart:html & dart:web * fixups * more fixups * analyzer * chore: changelog entry * run dart test for all supported dart version * update web example tests * update ci * update deps so that we can run test with old dart versions * fix ci * fix web enricher test * fix ci * ci fixes * ignore pana error * fix CI * fix ci * remove dart 2.17 build * fixes * fix CI * test dart2wasm * cleanup * disable dart2wasm on windows * fix tests for wasm --------- Co-authored-by: Josh Burton --- .github/actions/dart-test/action.yml | 8 +- .github/workflows/analyze.yml | 4 +- .github/workflows/dart.yml | 39 +++-- .github/workflows/flutter.yml | 1 + CHANGELOG.md | 12 +- dart/example_web/pubspec.yaml | 1 + dart/example_web/web/main.dart | 33 +++-- dart/example_web_legacy/.gitignore | 9 ++ dart/example_web_legacy/README.md | 8 ++ dart/example_web_legacy/analysis_options.yaml | 5 + dart/example_web_legacy/pubspec.yaml | 17 +++ dart/example_web_legacy/web/event.dart | 76 ++++++++++ dart/example_web_legacy/web/favicon.ico | Bin 0 -> 3559 bytes dart/example_web_legacy/web/index.html | 69 +++++++++ dart/example_web_legacy/web/main.dart | 135 ++++++++++++++++++ dart/example_web_legacy/web/styles.css | 14 ++ .../environment/environment_variables.dart | 3 +- .../enricher/enricher_event_processor.dart | 3 +- .../html_enricher_event_processor.dart | 103 +++++++++++++ .../web_enricher_event_processor.dart | 33 +++-- .../exception/exception_event_processor.dart | 3 +- dart/lib/src/origin.dart | 7 +- dart/lib/src/origin_html.dart | 4 + .../src/{noop_origin.dart => origin_io.dart} | 0 dart/lib/src/origin_web.dart | 6 + dart/lib/src/platform/_html_platform.dart | 51 +++++++ dart/lib/src/platform/_web_platform.dart | 14 +- dart/lib/src/platform/platform.dart | 5 +- dart/lib/src/sentry_client_stub.dart | 11 -- dart/lib/src/sentry_stack_trace_factory.dart | 2 +- dart/lib/src/utils/isolate_utils.dart | 3 +- dart/pubspec.yaml | 5 +- .../enricher/web_enricher_test.dart | 18 +-- dart/test/example_web_compile_test.dart | 27 ++-- dart/test/sentry_client_test.dart | 5 +- dart/test/stack_trace_test.dart | 3 +- dart/test/test_utils.dart | 5 + file/lib/src/sentry_file_extension.dart | 4 +- .../connectivity/connectivity_provider.dart | 3 +- .../html_connectivity_provider.dart | 32 +++++ .../web_connectivity_provider.dart | 17 ++- flutter/lib/src/native/factory.dart | 4 +- flutter/lib/src/renderer/renderer.dart | 1 + flutter/lib/src/renderer/web_renderer.dart | 18 +++ hive/lib/src/sentry_box_collection.dart | 1 + hive/test/mocks/mocks.dart | 1 + hive/test/mocks/mocks.mocks.dart | 1 + min_version_test/lib/main.dart | 7 +- .../lib/transaction/file_transaction.dart | 4 +- ...ion_locator.dart => transaction_stub.dart} | 2 +- .../lib/transaction/web_transaction.dart | 5 +- min_version_test/pubspec.yaml | 1 - 52 files changed, 716 insertions(+), 127 deletions(-) create mode 100644 dart/example_web_legacy/.gitignore create mode 100644 dart/example_web_legacy/README.md create mode 100644 dart/example_web_legacy/analysis_options.yaml create mode 100644 dart/example_web_legacy/pubspec.yaml create mode 100644 dart/example_web_legacy/web/event.dart create mode 100644 dart/example_web_legacy/web/favicon.ico create mode 100644 dart/example_web_legacy/web/index.html create mode 100644 dart/example_web_legacy/web/main.dart create mode 100644 dart/example_web_legacy/web/styles.css create mode 100644 dart/lib/src/event_processor/enricher/html_enricher_event_processor.dart create mode 100644 dart/lib/src/origin_html.dart rename dart/lib/src/{noop_origin.dart => origin_io.dart} (100%) create mode 100644 dart/lib/src/origin_web.dart create mode 100644 dart/lib/src/platform/_html_platform.dart delete mode 100644 dart/lib/src/sentry_client_stub.dart create mode 100644 flutter/lib/src/integrations/connectivity/html_connectivity_provider.dart create mode 100644 flutter/lib/src/renderer/web_renderer.dart rename min_version_test/lib/transaction/{transaction_locator.dart => transaction_stub.dart} (60%) diff --git a/.github/actions/dart-test/action.yml b/.github/actions/dart-test/action.yml index 606f145c64..bb13fa0300 100644 --- a/.github/actions/dart-test/action.yml +++ b/.github/actions/dart-test/action.yml @@ -23,7 +23,7 @@ runs: working-directory: ${{ inputs.directory }} - name: Test VM - run: dart test -p vm --coverage=coverage --test-randomize-ordering-seed=random --chain-stack-traces + run: dart test -p vm ${{ (runner.os == 'Linux' && matrix.sdk == 'stable' && '--coverage=coverage') || '' }} --test-randomize-ordering-seed=random --chain-stack-traces shell: bash working-directory: ${{ inputs.directory }} @@ -32,3 +32,9 @@ runs: run: dart test -p chrome --test-randomize-ordering-seed=random --chain-stack-traces shell: bash working-directory: ${{ inputs.directory }} + + - name: Test dart2wasm + if: ${{ inputs.web == 'true' && (matrix.sdk == 'stable' || matrix.sdk == 'beta') && runner.os != 'Windows' }} + run: dart test -p chrome --compiler dart2wasm --test-randomize-ordering-seed=random --chain-stack-traces + shell: bash + working-directory: ${{ inputs.directory }} diff --git a/.github/workflows/analyze.yml b/.github/workflows/analyze.yml index bf31098e20..fa3ef62068 100644 --- a/.github/workflows/analyze.yml +++ b/.github/workflows/analyze.yml @@ -39,7 +39,7 @@ jobs: - run: ${{ inputs.sdk }} pub get - run: dart format --set-exit-if-changed ./ - + - name: dart analyze uses: invertase/github-action-dart-analyzer@e981b01a458d0bab71ee5da182e5b26687b7101b # pin@v3.0.0 with: @@ -78,6 +78,6 @@ jobs: PERCENTAGE=$(( $TOTAL * 100 / $TOTAL_MAX )) if (( $PERCENTAGE < ${{ inputs.panaThreshold }} )) then - echo Score too low! + echo "Score too low ($PERCENTAGE % is less than the expected ${{ inputs.panaThreshold }} %)!" exit 1 fi diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index ef6a7de8e5..8f2c2f2b10 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -21,19 +21,28 @@ jobs: access_token: ${{ github.token }} build: - name: Build ${{matrix.sdk}} on ${{matrix.os}} - runs-on: ${{ matrix.os }} + name: Dart ${{matrix.sdk}} on ${{matrix.os}} + runs-on: ${{ matrix.os }}-latest timeout-minutes: 30 strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - sdk: [stable, beta] - exclude: - - os: windows-latest - sdk: beta - - os: macos-latest - sdk: beta + os: [ubuntu] + sdk: + - '2.18' + - '2.19' + - '3.0' + - '3.1' + - '3.2' + - '3.3' + - '3.4' + - stable + - beta + include: + - os: windows + sdk: stable + - os: macos + sdk: stable steps: - uses: actions/checkout@v4 @@ -49,24 +58,14 @@ jobs: coverage: sentry min-coverage: 85 - - name: Install webdev - if: runner.os != 'Windows' - run: dart pub global activate webdev - - name: Build example working-directory: dart/example run: | dart pub get dart compile aot-snapshot bin/example.dart - - name: Build Web example - if: runner.os != 'Windows' - working-directory: dart/example_web - run: | - dart pub get - webdev build - analyze: uses: ./.github/workflows/analyze.yml with: package: dart + panaThreshold: 87 diff --git a/.github/workflows/flutter.yml b/.github/workflows/flutter.yml index cec1edecab..32130d4765 100644 --- a/.github/workflows/flutter.yml +++ b/.github/workflows/flutter.yml @@ -144,6 +144,7 @@ jobs: with: package: flutter sdk: flutter + panaThreshold: 87 pod-lint: runs-on: macos-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ae8139d38..0bc2f45ab2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Features + +- Support WebAssembly compilation (dart2wasm) ([#2113](https://github.com/getsentry/sentry-dart/pull/2113)) + ### Dependencies - Bump Cocoa SDK from v8.29.0 to v8.29.1 ([#2109](https://github.com/getsentry/sentry-dart/pull/2109)) @@ -122,7 +126,7 @@ This release contains breaking changes, please read the changelog carefully. ### Features -- Experimental: Add support for Sentry Developer Metrics ([#1940](https://github.com/getsentry/sentry-dart/pull/1940), [#1949](https://github.com/getsentry/sentry-dart/pull/1949), [#1954](https://github.com/getsentry/sentry-dart/pull/1954), [#1958](https://github.com/getsentry/sentry-dart/pull/1958)) +- Experimental: Add support for Sentry Developer Metrics ([#1940](https://github.com/getsentry/sentry-dart/pull/1940), [#1949](https://github.com/getsentry/sentry-dart/pull/1949), [#1954](https://github.com/getsentry/sentry-dart/pull/1954), [#1958](https://github.com/getsentry/sentry-dart/pull/1958)) Use the Metrics API to track processing time, download sizes, user signups, and conversion rates and correlate them back to tracing data in order to get deeper insights and solve issues faster. Our API supports counters, distributions, sets, gauges and timers, and it's easy to get started: ```dart Sentry.metrics() @@ -235,14 +239,14 @@ This release contains breaking changes, please read the changelog carefully. - Now the device context from Android is available in `BeforeSendCallback` - Set ip_address to {{auto}} by default, even if sendDefaultPII is disabled ([#1665](https://github.com/getsentry/sentry-dart/pull/1665)) - Instead use the "Prevent Storing of IP Addresses" option in the "Security & Privacy" project settings on sentry.io - -### Fixes + +### Fixes - Remove Flutter dependency from Drift integration ([#1867](https://github.com/getsentry/sentry-dart/pull/1867)) - Remove dead code, cold start bool is now always present ([#1861](https://github.com/getsentry/sentry-dart/pull/1861)) - Fix iOS "Arithmetic Overflow" ([#1874](https://github.com/getsentry/sentry-dart/pull/1874)) -### Dependencies +### Dependencies - Bump Cocoa SDK from v8.19.0 to v8.20.0 ([#1856](https://github.com/getsentry/sentry-dart/pull/1856)) - [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#8200) diff --git a/dart/example_web/pubspec.yaml b/dart/example_web/pubspec.yaml index 7bcb64d06e..141e5120ac 100644 --- a/dart/example_web/pubspec.yaml +++ b/dart/example_web/pubspec.yaml @@ -9,6 +9,7 @@ environment: dependencies: sentry: path: ../../dart/ + web: ^0.5.1 dev_dependencies: build_runner: ^2.4.2 diff --git a/dart/example_web/web/main.dart b/dart/example_web/web/main.dart index 3034effe9b..b39a05e0dc 100644 --- a/dart/example_web/web/main.dart +++ b/dart/example_web/web/main.dart @@ -1,5 +1,5 @@ import 'dart:async'; -import 'dart:html'; +import 'package:web/web.dart'; import 'package:sentry/sentry.dart'; import 'package:sentry/src/version.dart'; @@ -24,7 +24,7 @@ Future main() async { Future runApp() async { print('runApp'); - querySelector('#output')?.text = 'Your Dart app is running.'; + document.querySelector('#output')?.text = 'Your Dart app is running.'; await Sentry.addBreadcrumb( Breadcrumb( @@ -57,12 +57,20 @@ Future runApp() async { ); }); - querySelector('#btEvent') + document + .querySelector('#btEvent') ?.onClick .listen((event) => captureCompleteExampleEvent()); - querySelector('#btMessage')?.onClick.listen((event) => captureMessage()); - querySelector('#btException')?.onClick.listen((event) => captureException()); - querySelector('#btUnhandledException') + document + .querySelector('#btMessage') + ?.onClick + .listen((event) => captureMessage()); + document + .querySelector('#btException') + ?.onClick + .listen((event) => captureException()); + document + .querySelector('#btUnhandledException') ?.onClick .listen((event) => captureUnhandledException()); } @@ -76,7 +84,8 @@ Future captureMessage() async { ); print('capture message result : $sentryId'); if (sentryId != SentryId.empty()) { - querySelector('#messageResult')?.style.display = 'block'; + (document.querySelector('#messageResult') as HTMLElement?)?.style.display = + 'block'; } } @@ -93,13 +102,16 @@ Future captureException() async { print('Capture exception : SentryId: $sentryId'); if (sentryId != SentryId.empty()) { - querySelector('#exceptionResult')?.style.display = 'block'; + (document.querySelector('#exceptionResult') as HTMLElement?) + ?.style + .display = 'block'; } } } Future captureUnhandledException() async { - querySelector('#unhandledResult')?.style.display = 'block'; + (document.querySelector('#unhandledResult') as HTMLElement?)?.style.display = + 'block'; await buildCard(); } @@ -111,7 +123,8 @@ Future captureCompleteExampleEvent() async { print('Response SentryId: $sentryId'); if (sentryId != SentryId.empty()) { - querySelector('#eventResult')?.style.display = 'block'; + (document.querySelector('#eventResult') as HTMLElement?)?.style.display = + 'block'; } } diff --git a/dart/example_web_legacy/.gitignore b/dart/example_web_legacy/.gitignore new file mode 100644 index 0000000000..3d64647b50 --- /dev/null +++ b/dart/example_web_legacy/.gitignore @@ -0,0 +1,9 @@ +# Files and directories created by pub +.dart_tool/ +.packages + +# Conventional directory for build outputs +build/ + +# Directory created by dartdoc +doc/api/ diff --git a/dart/example_web_legacy/README.md b/dart/example_web_legacy/README.md new file mode 100644 index 0000000000..98202566f6 --- /dev/null +++ b/dart/example_web_legacy/README.md @@ -0,0 +1,8 @@ +# Sentry Dart : web example + +```sh +dart pub get + +# run the project ( see https://dart.dev/tools/webdev#serve ) +dart run webdev serve --release +``` diff --git a/dart/example_web_legacy/analysis_options.yaml b/dart/example_web_legacy/analysis_options.yaml new file mode 100644 index 0000000000..be16ace7d1 --- /dev/null +++ b/dart/example_web_legacy/analysis_options.yaml @@ -0,0 +1,5 @@ +include: package:lints/recommended.yaml + +analyzer: + errors: + path_does_not_exist: ignore diff --git a/dart/example_web_legacy/pubspec.yaml b/dart/example_web_legacy/pubspec.yaml new file mode 100644 index 0000000000..49b7807084 --- /dev/null +++ b/dart/example_web_legacy/pubspec.yaml @@ -0,0 +1,17 @@ +name: sentry_dart_web_example +description: An absolute bare-bones web app. + +publish_to: 'none' + +environment: + sdk: '>=2.17.0 <4.0.0' + +dependencies: + sentry: + path: ../../dart/ + +dev_dependencies: + build_runner: ^2.3.0 + build_web_compilers: ^3.2.3 + lints: ^2.0.0 + webdev: ^2.7.0 diff --git a/dart/example_web_legacy/web/event.dart b/dart/example_web_legacy/web/event.dart new file mode 100644 index 0000000000..6e3e8b3e0b --- /dev/null +++ b/dart/example_web_legacy/web/event.dart @@ -0,0 +1,76 @@ +import 'package:sentry/src/protocol.dart'; + +final event = SentryEvent( + logger: 'main', + serverName: 'server.dart', + release: '1.4.0-preview.1', + environment: 'Test', + message: SentryMessage('This is an example Dart event.'), + tags: const {'project-id': '7371'}, + // ignore: deprecated_member_use, deprecated_member_use_from_same_package + extra: const {'section': '1'}, + // fingerprint: const ['example-dart'], + user: SentryUser( + id: '800', + username: 'first-user', + email: 'first@user.lan', + // ipAddress: '127.0.0.1', + data: {'first-sign-in': '2020-01-01'}, + ), + breadcrumbs: [ + Breadcrumb( + message: 'UI Lifecycle', + timestamp: DateTime.now().toUtc(), + category: 'ui.lifecycle', + type: 'navigation', + data: {'screen': 'MainActivity', 'state': 'created'}, + level: SentryLevel.info, + ) + ], + contexts: Contexts( + operatingSystem: const SentryOperatingSystem( + name: 'Android', + version: '5.0.2', + build: 'LRX22G.P900XXS0BPL2', + kernelVersion: + 'Linux version 3.4.39-5726670 (dpi@SWHC3807) (gcc version 4.8 (GCC) ) #1 SMP PREEMPT Thu Dec 1 19:42:39 KST 2016', + rooted: false, + ), + runtimes: [const SentryRuntime(name: 'ART', version: '5')], + app: SentryApp( + name: 'Example Dart App', + version: '1.42.0', + identifier: 'HGT-App-13', + build: '93785', + buildType: 'release', + deviceAppHash: '5afd3a6', + startTime: DateTime.now().toUtc(), + ), + browser: const SentryBrowser(name: 'Firefox', version: '42.0.1'), + device: SentryDevice( + name: 'SM-P900', + family: 'SM-P900', + model: 'SM-P900 (LRX22G)', + modelId: 'LRX22G', + arch: 'armeabi-v7a', + batteryLevel: 99, + orientation: SentryOrientation.landscape, + manufacturer: 'samsung', + brand: 'samsung', + screenDensity: 2.1, + screenDpi: 320, + online: true, + charging: true, + lowMemory: true, + simulator: false, + memorySize: 1500, + freeMemory: 200, + usableMemory: 4294967296, + storageSize: 4294967296, + freeStorage: 2147483648, + externalStorageSize: 8589934592, + externalFreeStorage: 2863311530, + bootTime: DateTime.now().toUtc(), + ), + ), +); diff --git a/dart/example_web_legacy/web/favicon.ico b/dart/example_web_legacy/web/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..7ba349b3e628d2423d4a2ed217422a4722f73739 GIT binary patch literal 3559 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 + + + + + + + + + + + dart_web + + + + + + + + + + +
+ +
+ +
Captured
+
+ +
+ +
Captured
+
+ +
+ +
Captured
+
+ +
+ +
Captured
+
+ + + diff --git a/dart/example_web_legacy/web/main.dart b/dart/example_web_legacy/web/main.dart new file mode 100644 index 0000000000..3034effe9b --- /dev/null +++ b/dart/example_web_legacy/web/main.dart @@ -0,0 +1,135 @@ +import 'dart:async'; +import 'dart:html'; + +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/version.dart'; + +import 'event.dart'; + +// ATTENTION: Change the DSN below with your own to see the events in Sentry. Get one at sentry.io +const dsn = + 'https://e85b375ffb9f43cf8bdf9787768149e0@o447951.ingest.sentry.io/5428562'; + +Future main() async { + await Sentry.init( + (options) => options + ..dsn = dsn + ..debug = true + ..sendDefaultPii = true + ..addEventProcessor(TagEventProcessor()), + appRunner: runApp, + ); +} + +Future runApp() async { + print('runApp'); + + querySelector('#output')?.text = 'Your Dart app is running.'; + + await Sentry.addBreadcrumb( + Breadcrumb( + message: 'Authenticated user', + category: 'auth', + type: 'debug', + data: { + 'admin': true, + 'permissions': [1, 2, 3] + }, + ), + ); + + await Sentry.configureScope((scope) async { + scope + // ..fingerprint = ['example-dart'] + ..transaction = '/example/app' + ..level = SentryLevel.warning; + await scope.setTag('build', '579'); + await scope.setExtra('company-name', 'Dart Inc'); + + await scope.setUser( + SentryUser( + id: '800', + username: 'first-user', + email: 'first@user.lan', + // ipAddress: '127.0.0.1', + data: {'first-sign-in': '2020-01-01'}, + ), + ); + }); + + querySelector('#btEvent') + ?.onClick + .listen((event) => captureCompleteExampleEvent()); + querySelector('#btMessage')?.onClick.listen((event) => captureMessage()); + querySelector('#btException')?.onClick.listen((event) => captureException()); + querySelector('#btUnhandledException') + ?.onClick + .listen((event) => captureUnhandledException()); +} + +Future captureMessage() async { + print('Capturing Message : '); + final sentryId = await Sentry.captureMessage( + 'Message 2', + template: 'Message %s', + params: ['2'], + ); + print('capture message result : $sentryId'); + if (sentryId != SentryId.empty()) { + querySelector('#messageResult')?.style.display = 'block'; + } +} + +Future captureException() async { + try { + await buildCard(); + } catch (error, stackTrace) { + print('\nReporting the following stack trace: '); + final sentryId = await Sentry.captureException( + error, + stackTrace: stackTrace, + ); + + print('Capture exception : SentryId: $sentryId'); + + if (sentryId != SentryId.empty()) { + querySelector('#exceptionResult')?.style.display = 'block'; + } + } +} + +Future captureUnhandledException() async { + querySelector('#unhandledResult')?.style.display = 'block'; + + await buildCard(); +} + +Future captureCompleteExampleEvent() async { + print('\nReporting a complete event example: $sdkName'); + final sentryId = await Sentry.captureEvent(event); + + print('Response SentryId: $sentryId'); + + if (sentryId != SentryId.empty()) { + querySelector('#eventResult')?.style.display = 'block'; + } +} + +Future buildCard() async { + await loadData(); +} + +Future loadData() async { + await parseData(); +} + +Future parseData() async { + throw StateError('This is a test error'); +} + +class TagEventProcessor implements EventProcessor { + @override + SentryEvent? apply(SentryEvent event, Hint hint) { + return event..tags?.addAll({'page-locale': 'en-us'}); + } +} diff --git a/dart/example_web_legacy/web/styles.css b/dart/example_web_legacy/web/styles.css new file mode 100644 index 0000000000..cc035c95c9 --- /dev/null +++ b/dart/example_web_legacy/web/styles.css @@ -0,0 +1,14 @@ +@import url(https://fonts.googleapis.com/css?family=Roboto); + +html, body { + width: 100%; + height: 100%; + margin: 0; + padding: 0; + font-family: 'Roboto', sans-serif; +} + +#output { + padding: 20px; + text-align: center; +} diff --git a/dart/lib/src/environment/environment_variables.dart b/dart/lib/src/environment/environment_variables.dart index 916cdea47d..3dcb1674b3 100644 --- a/dart/lib/src/environment/environment_variables.dart +++ b/dart/lib/src/environment/environment_variables.dart @@ -1,6 +1,7 @@ import '../platform_checker.dart'; import '_io_environment_variables.dart' - if (dart.library.html) '_web_environment_variables.dart' as env; + if (dart.library.html) '_web_environment_variables.dart' + if (dart.library.js_interop) '_web_environment_variables.dart' as env; /// Reads environment variables from the system. /// In an Flutter environment these can be set via diff --git a/dart/lib/src/event_processor/enricher/enricher_event_processor.dart b/dart/lib/src/event_processor/enricher/enricher_event_processor.dart index 78d19738bd..779d64b700 100644 --- a/dart/lib/src/event_processor/enricher/enricher_event_processor.dart +++ b/dart/lib/src/event_processor/enricher/enricher_event_processor.dart @@ -1,7 +1,8 @@ import '../../event_processor.dart'; import '../../sentry_options.dart'; import 'io_enricher_event_processor.dart' - if (dart.library.html) 'web_enricher_event_processor.dart'; + if (dart.library.html) 'html_enricher_event_processor.dart' + if (dart.library.js_interop) 'web_enricher_event_processor.dart'; abstract class EnricherEventProcessor implements EventProcessor { factory EnricherEventProcessor(SentryOptions options) => diff --git a/dart/lib/src/event_processor/enricher/html_enricher_event_processor.dart b/dart/lib/src/event_processor/enricher/html_enricher_event_processor.dart new file mode 100644 index 0000000000..e51cff4b71 --- /dev/null +++ b/dart/lib/src/event_processor/enricher/html_enricher_event_processor.dart @@ -0,0 +1,103 @@ +import 'dart:html' as html show window, Window; + +import '../../../sentry.dart'; +import 'enricher_event_processor.dart'; + +EnricherEventProcessor enricherEventProcessor(SentryOptions options) { + return WebEnricherEventProcessor( + html.window, + options, + ); +} + +class WebEnricherEventProcessor implements EnricherEventProcessor { + WebEnricherEventProcessor( + this._window, + this._options, + ); + + final html.Window _window; + + final SentryOptions _options; + + @override + SentryEvent? apply(SentryEvent event, Hint hint) { + // Web has no native integration, so no need to check for it + + final contexts = event.contexts.copyWith( + device: _getDevice(event.contexts.device), + culture: _getSentryCulture(event.contexts.culture), + ); + + contexts['dart_context'] = _getDartContext(); + + return event.copyWith( + contexts: contexts, + request: _getRequest(event.request), + transaction: event.transaction ?? _window.location.pathname, + ); + } + + // As seen in + // https://github.com/getsentry/sentry-javascript/blob/a6f8dc26a4c7ae2146ae64995a2018c8578896a6/packages/browser/src/integrations/useragent.ts + SentryRequest _getRequest(SentryRequest? request) { + final requestHeader = request?.headers; + final header = requestHeader == null + ? {} + : Map.from(requestHeader); + + header.putIfAbsent('User-Agent', () => _window.navigator.userAgent); + + final url = request?.url ?? _window.location.toString(); + return (request ?? SentryRequest(url: url)) + .copyWith(headers: header) + .sanitized(); + } + + SentryDevice _getDevice(SentryDevice? device) { + return (device ?? SentryDevice()).copyWith( + online: device?.online ?? _window.navigator.onLine, + memorySize: device?.memorySize ?? _getMemorySize(), + orientation: device?.orientation ?? _getScreenOrientation(), + screenHeightPixels: device?.screenHeightPixels ?? + _window.screen?.available.height.toInt(), + screenWidthPixels: + device?.screenWidthPixels ?? _window.screen?.available.width.toInt(), + screenDensity: + device?.screenDensity ?? _window.devicePixelRatio.toDouble(), + ); + } + + int? _getMemorySize() { + // https://developer.mozilla.org/en-US/docs/Web/API/Navigator/deviceMemory + final size = _window.navigator.deviceMemory?.toDouble(); + final memoryByteSize = size != null ? size * 1024 * 1024 * 1024 : null; + return memoryByteSize?.toInt(); + } + + SentryOrientation? _getScreenOrientation() { + // https://developer.mozilla.org/en-US/docs/Web/API/ScreenOrientation + final screenOrientation = _window.screen?.orientation; + if (screenOrientation != null) { + if (screenOrientation.type?.startsWith('portrait') ?? false) { + return SentryOrientation.portrait; + } + if (screenOrientation.type?.startsWith('landscape') ?? false) { + return SentryOrientation.landscape; + } + } + return null; + } + + Map _getDartContext() { + return { + 'compile_mode': _options.platformChecker.compileMode, + }; + } + + SentryCulture _getSentryCulture(SentryCulture? culture) { + return (culture ?? SentryCulture()).copyWith( + timezone: culture?.timezone ?? DateTime.now().timeZoneName, + ); + } +} diff --git a/dart/lib/src/event_processor/enricher/web_enricher_event_processor.dart b/dart/lib/src/event_processor/enricher/web_enricher_event_processor.dart index e51cff4b71..d6d588ac0a 100644 --- a/dart/lib/src/event_processor/enricher/web_enricher_event_processor.dart +++ b/dart/lib/src/event_processor/enricher/web_enricher_event_processor.dart @@ -1,11 +1,13 @@ -import 'dart:html' as html show window, Window; +// We would lose compatibility with old dart versions by adding web to pubspec. +// ignore: depend_on_referenced_packages +import 'package:web/web.dart' as web show window, Window, Navigator; import '../../../sentry.dart'; import 'enricher_event_processor.dart'; EnricherEventProcessor enricherEventProcessor(SentryOptions options) { return WebEnricherEventProcessor( - html.window, + web.window, options, ); } @@ -16,7 +18,7 @@ class WebEnricherEventProcessor implements EnricherEventProcessor { this._options, ); - final html.Window _window; + final web.Window _window; final SentryOptions _options; @@ -59,10 +61,9 @@ class WebEnricherEventProcessor implements EnricherEventProcessor { online: device?.online ?? _window.navigator.onLine, memorySize: device?.memorySize ?? _getMemorySize(), orientation: device?.orientation ?? _getScreenOrientation(), - screenHeightPixels: device?.screenHeightPixels ?? - _window.screen?.available.height.toInt(), - screenWidthPixels: - device?.screenWidthPixels ?? _window.screen?.available.width.toInt(), + screenHeightPixels: + device?.screenHeightPixels ?? _window.screen.availHeight, + screenWidthPixels: device?.screenWidthPixels ?? _window.screen.availWidth, screenDensity: device?.screenDensity ?? _window.devicePixelRatio.toDouble(), ); @@ -77,14 +78,12 @@ class WebEnricherEventProcessor implements EnricherEventProcessor { SentryOrientation? _getScreenOrientation() { // https://developer.mozilla.org/en-US/docs/Web/API/ScreenOrientation - final screenOrientation = _window.screen?.orientation; - if (screenOrientation != null) { - if (screenOrientation.type?.startsWith('portrait') ?? false) { - return SentryOrientation.portrait; - } - if (screenOrientation.type?.startsWith('landscape') ?? false) { - return SentryOrientation.landscape; - } + final screenOrientation = _window.screen.orientation; + if (screenOrientation.type.startsWith('portrait')) { + return SentryOrientation.portrait; + } + if (screenOrientation.type.startsWith('landscape')) { + return SentryOrientation.landscape; } return null; } @@ -101,3 +100,7 @@ class WebEnricherEventProcessor implements EnricherEventProcessor { ); } } + +extension on web.Navigator { + external double? get deviceMemory; +} diff --git a/dart/lib/src/event_processor/exception/exception_event_processor.dart b/dart/lib/src/event_processor/exception/exception_event_processor.dart index e928f476f0..ab4f5e9878 100644 --- a/dart/lib/src/event_processor/exception/exception_event_processor.dart +++ b/dart/lib/src/event_processor/exception/exception_event_processor.dart @@ -1,7 +1,8 @@ import '../../event_processor.dart'; import '../../sentry_options.dart'; import 'io_exception_event_processor.dart' - if (dart.library.html) 'web_exception_event_processor.dart'; + if (dart.library.html) 'web_exception_event_processor.dart' + if (dart.library.js_interop) 'web_exception_event_processor.dart'; abstract class ExceptionEventProcessor implements EventProcessor { factory ExceptionEventProcessor(SentryOptions options) => diff --git a/dart/lib/src/origin.dart b/dart/lib/src/origin.dart index ed1e066c5e..1d5b3dc4bf 100644 --- a/dart/lib/src/origin.dart +++ b/dart/lib/src/origin.dart @@ -1,4 +1,3 @@ -import 'dart:html'; - -/// request origin, used for browser stacktrace -String get eventOrigin => '${window.location.origin}/'; +export 'origin_io.dart' + if (dart.library.html) 'origin_html.dart' + if (dart.library.js_interop) 'origin_web.dart'; diff --git a/dart/lib/src/origin_html.dart b/dart/lib/src/origin_html.dart new file mode 100644 index 0000000000..ed1e066c5e --- /dev/null +++ b/dart/lib/src/origin_html.dart @@ -0,0 +1,4 @@ +import 'dart:html'; + +/// request origin, used for browser stacktrace +String get eventOrigin => '${window.location.origin}/'; diff --git a/dart/lib/src/noop_origin.dart b/dart/lib/src/origin_io.dart similarity index 100% rename from dart/lib/src/noop_origin.dart rename to dart/lib/src/origin_io.dart diff --git a/dart/lib/src/origin_web.dart b/dart/lib/src/origin_web.dart new file mode 100644 index 0000000000..db99f33c56 --- /dev/null +++ b/dart/lib/src/origin_web.dart @@ -0,0 +1,6 @@ +// We would lose compatibility with old dart versions by adding web to pubspec. +// ignore: depend_on_referenced_packages +import 'package:web/web.dart'; + +/// request origin, used for browser stacktrace +String get eventOrigin => '${window.location.origin}/'; diff --git a/dart/lib/src/platform/_html_platform.dart b/dart/lib/src/platform/_html_platform.dart new file mode 100644 index 0000000000..d3fa84eed9 --- /dev/null +++ b/dart/lib/src/platform/_html_platform.dart @@ -0,0 +1,51 @@ +import 'dart:html' as html; +import 'platform.dart'; + +const Platform instance = WebPlatform(); + +/// [Platform] implementation that delegates to `dart:html`. +class WebPlatform extends Platform { + /// Creates a new [Platform]. + const WebPlatform(); + + @override + String get operatingSystem => _browserPlatform(); + + @override + String get operatingSystemVersion => 'unknown'; + + @override + String get localHostname => html.window.location.hostname ?? 'unknown'; + + String _browserPlatform() { + final navigatorPlatform = + html.window.navigator.platform?.toLowerCase() ?? ''; + if (navigatorPlatform.startsWith('mac')) { + return 'macos'; + } + if (navigatorPlatform.startsWith('win')) { + return 'windows'; + } + if (navigatorPlatform.contains('iphone') || + navigatorPlatform.contains('ipad') || + navigatorPlatform.contains('ipod')) { + return 'ios'; + } + if (navigatorPlatform.contains('android')) { + return 'android'; + } + if (navigatorPlatform.contains('fuchsia')) { + return 'fuchsia'; + } + + // Since some phones can report a window.navigator.platform as Linux, fall + // back to use CSS to disambiguate Android vs Linux desktop. If the CSS + // indicates that a device has a "fine pointer" (mouse) as the primary + // pointing device, then we'll assume desktop linux, and otherwise we'll + // assume Android. + if (html.window.matchMedia('only screen and (pointer: fine)').matches) { + return 'linux'; + } + return 'android'; + } +} diff --git a/dart/lib/src/platform/_web_platform.dart b/dart/lib/src/platform/_web_platform.dart index d3fa84eed9..da403b254d 100644 --- a/dart/lib/src/platform/_web_platform.dart +++ b/dart/lib/src/platform/_web_platform.dart @@ -1,9 +1,12 @@ -import 'dart:html' as html; +// We would lose compatibility with old dart versions by adding web to pubspec. +// ignore: depend_on_referenced_packages +import 'package:web/web.dart' as web; + import 'platform.dart'; const Platform instance = WebPlatform(); -/// [Platform] implementation that delegates to `dart:html`. +/// [Platform] implementation that delegates to `dart:web`. class WebPlatform extends Platform { /// Creates a new [Platform]. const WebPlatform(); @@ -15,11 +18,10 @@ class WebPlatform extends Platform { String get operatingSystemVersion => 'unknown'; @override - String get localHostname => html.window.location.hostname ?? 'unknown'; + String get localHostname => web.window.location.hostname; String _browserPlatform() { - final navigatorPlatform = - html.window.navigator.platform?.toLowerCase() ?? ''; + final navigatorPlatform = web.window.navigator.platform.toLowerCase(); if (navigatorPlatform.startsWith('mac')) { return 'macos'; } @@ -43,7 +45,7 @@ class WebPlatform extends Platform { // indicates that a device has a "fine pointer" (mouse) as the primary // pointing device, then we'll assume desktop linux, and otherwise we'll // assume Android. - if (html.window.matchMedia('only screen and (pointer: fine)').matches) { + if (web.window.matchMedia('only screen and (pointer: fine)').matches) { return 'linux'; } return 'android'; diff --git a/dart/lib/src/platform/platform.dart b/dart/lib/src/platform/platform.dart index 8f6d0760e8..dffd3e81fd 100644 --- a/dart/lib/src/platform/platform.dart +++ b/dart/lib/src/platform/platform.dart @@ -1,5 +1,6 @@ -import '_io_platform.dart' if (dart.library.html) '_web_platform.dart' - as platform; +import '_io_platform.dart' + if (dart.library.html) '_html_platform.dart' + if (dart.library.js_interop) '_web_platform.dart' as platform; const Platform instance = platform.instance; diff --git a/dart/lib/src/sentry_client_stub.dart b/dart/lib/src/sentry_client_stub.dart deleted file mode 100644 index e212b39a0d..0000000000 --- a/dart/lib/src/sentry_client_stub.dart +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'sentry_client.dart'; -import 'sentry_options.dart'; - -/// Implemented in `sentry_browser_client.dart` and `sentry_io_client.dart`. -SentryClient createSentryClient(SentryOptions options) => - throw UnsupportedError( - 'Cannot create a client without dart:html or dart:io.'); diff --git a/dart/lib/src/sentry_stack_trace_factory.dart b/dart/lib/src/sentry_stack_trace_factory.dart index 9d4a42bffc..25a97e6eab 100644 --- a/dart/lib/src/sentry_stack_trace_factory.dart +++ b/dart/lib/src/sentry_stack_trace_factory.dart @@ -1,7 +1,7 @@ import 'package:meta/meta.dart'; import 'package:stack_trace/stack_trace.dart'; -import 'noop_origin.dart' if (dart.library.html) 'origin.dart'; +import 'origin.dart'; import 'protocol.dart'; import 'sentry_options.dart'; diff --git a/dart/lib/src/utils/isolate_utils.dart b/dart/lib/src/utils/isolate_utils.dart index 3e9c4b20bc..6575965ff9 100644 --- a/dart/lib/src/utils/isolate_utils.dart +++ b/dart/lib/src/utils/isolate_utils.dart @@ -1,7 +1,8 @@ import 'package:meta/meta.dart'; import '_io_get_isolate_name.dart' - if (dart.library.html) '_web_get_isolate_name.dart' as isolate_getter; + if (dart.library.html) '_web_get_isolate_name.dart' + if (dart.library.js_interop) '_web_get_isolate_name.dart' as isolate_getter; @internal String? getIsolateName() => isolate_getter.getIsolateName(); diff --git a/dart/pubspec.yaml b/dart/pubspec.yaml index 41aa1b7d0c..9e63a7b079 100644 --- a/dart/pubspec.yaml +++ b/dart/pubspec.yaml @@ -26,11 +26,12 @@ dependencies: uuid: '>=3.0.0 <5.0.0' dev_dependencies: - build_runner: ^2.4.2 + build_runner: ^2.3.0 mockito: ^5.1.0 - lints: ^4.0.0 + lints: '>=2.0.0 <5.0.0' test: ^1.21.1 yaml: ^3.1.0 # needed for version match (code and pubspec) collection: ^1.16.0 coverage: ^1.3.0 intl: '>=0.17.0 <1.0.0' + version: ^3.0.2 diff --git a/dart/test/event_processor/enricher/web_enricher_test.dart b/dart/test/event_processor/enricher/web_enricher_test.dart index cdc7310fab..d2590a09fa 100644 --- a/dart/test/event_processor/enricher/web_enricher_test.dart +++ b/dart/test/event_processor/enricher/web_enricher_test.dart @@ -1,10 +1,10 @@ @TestOn('browser') library dart_test; -import 'dart:html' as html; - import 'package:sentry/sentry.dart'; -import 'package:sentry/src/event_processor/enricher/web_enricher_event_processor.dart'; +import 'package:sentry/src/event_processor/enricher/html_enricher_event_processor.dart' + if (dart.library.html) 'package:sentry/src/event_processor/enricher/html_enricher_event_processor.dart' + if (dart.library.js_interop) 'package:sentry/src/event_processor/enricher/web_enricher_event_processor.dart'; import 'package:test/test.dart'; import '../../mocks.dart'; @@ -193,10 +193,8 @@ void main() { ); await Sentry.close(); - final ioEnricherCount = sentryOptions.eventProcessors - .whereType() - .length; - expect(ioEnricherCount, 1); + expect(sentryOptions.eventProcessors.map((e) => e.runtimeType.toString()), + contains('$WebEnricherEventProcessor')); }); }); } @@ -206,10 +204,6 @@ class Fixture { final options = SentryOptions( dsn: fakeDsn, checker: MockPlatformChecker(hasNativeIntegration: false)); - - return WebEnricherEventProcessor( - html.window, - options, - ); + return enricherEventProcessor(options) as WebEnricherEventProcessor; } } diff --git a/dart/test/example_web_compile_test.dart b/dart/test/example_web_compile_test.dart index 4199a47dda..1a2a0cf13f 100644 --- a/dart/test/example_web_compile_test.dart +++ b/dart/test/example_web_compile_test.dart @@ -5,29 +5,35 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:version/version.dart'; import 'package:test/test.dart'; // Tests for the following issue // https://github.com/getsentry/sentry-dart/issues/1893 void main() { - group('Compile example_web', () { + final dartVersion = Version.parse(Platform.version.split(' ')[0]); + final isLegacy = dartVersion < Version.parse('3.3.0'); + final exampleAppDir = isLegacy ? 'example_web_legacy' : 'example_web'; + final exampleAppWorkingDir = + '${Directory.current.path}${Platform.pathSeparator}$exampleAppDir'; + group('Compile $exampleAppDir', () { test( 'dart pub get and compilation should run successfully', () async { final result = await _runProcess('dart pub get', - workingDirectory: _exampleWebWorkingDir); + workingDirectory: exampleAppWorkingDir); expect(result.exitCode, 0, - reason: 'Could run `dart pub get` for example_web. ' + reason: 'Could run `dart pub get` for $exampleAppDir. ' 'Likely caused by outdated dependencies'); // running this test locally require clean working directory final cleanResult = await _runProcess('dart run build_runner clean', - workingDirectory: _exampleWebWorkingDir); + workingDirectory: exampleAppWorkingDir); expect(cleanResult.exitCode, 0); final compileResult = await _runProcess( 'dart run build_runner build -r web -o build --delete-conflicting-outputs', - workingDirectory: _exampleWebWorkingDir); + workingDirectory: exampleAppWorkingDir); expect(compileResult.exitCode, 0, - reason: 'Could not compile example_web project'); + reason: 'Could not compile $exampleAppDir project'); expect( compileResult.stdout, isNot(contains( @@ -36,8 +42,9 @@ void main() { 'Could not compile main.dart, likely because of dart:io import.'); expect( compileResult.stdout, - contains( - 'build_web_compilers:entrypoint on web/main.dart:Compiled')); + contains(isLegacy + ? 'Succeeded after ' + : 'build_web_compilers:entrypoint on web/main.dart:Compiled')); }, timeout: Timeout(const Duration(minutes: 1)), // double of detault timeout ); @@ -76,10 +83,6 @@ Future<_CommandResult> _runProcess(String command, return _CommandResult(exitCode: exitCode, stdout: processOut); } -String get _exampleWebWorkingDir { - return '${Directory.current.path}${Platform.pathSeparator}example_web'; -} - class _CommandResult { final int exitCode; final String stdout; diff --git a/dart/test/sentry_client_test.dart b/dart/test/sentry_client_test.dart index b8c6bee716..153c1515f0 100644 --- a/dart/test/sentry_client_test.dart +++ b/dart/test/sentry_client_test.dart @@ -139,7 +139,10 @@ void main() { capturedEvent.threads?.first.id, ); }, - onPlatform: {'js': Skip("Isolates don't exist on the web")}, + onPlatform: { + 'js': Skip("Isolates don't exist on the web"), + 'wasm': Skip("Isolates don't exist on the web") + }, ); test( diff --git a/dart/test/stack_trace_test.dart b/dart/test/stack_trace_test.dart index 6524581794..3ab8f9d116 100644 --- a/dart/test/stack_trace_test.dart +++ b/dart/test/stack_trace_test.dart @@ -3,8 +3,7 @@ // found in the LICENSE file. import 'package:sentry/sentry.dart'; -import 'package:sentry/src/noop_origin.dart' - if (dart.library.html) 'package:sentry/src/origin.dart'; +import 'package:sentry/src/origin.dart'; import 'package:sentry/src/sentry_stack_trace_factory.dart'; import 'package:stack_trace/stack_trace.dart'; import 'package:test/test.dart'; diff --git a/dart/test/test_utils.dart b/dart/test/test_utils.dart index 627d19903d..87a0be0f0a 100644 --- a/dart/test/test_utils.dart +++ b/dart/test/test_utils.dart @@ -117,6 +117,11 @@ Future testCaptureException( final topFrame = (stacktrace['frames'] as Iterable).last as Map; + if (topFrame['function'].contains('browser_test.dart.wasm')) { + // TODO stacktrace parsing for wasm is not implemented yet + // {filename: unparsed, function: at testCaptureException (http://localhost:59959/9R3KYfjvkWCySr4h2hI0pVO7PqmPFeE6/test/sentry_browser_test.dart.browser_test.dart.wasm:wasm-function[1007]:0x4bc18), abs_path: http://localhost:59959/unparsed, in_app: true} + return; + } expect( topFrame.keys, ['filename', 'function', 'lineno', 'colno', 'abs_path', 'in_app'], diff --git a/file/lib/src/sentry_file_extension.dart b/file/lib/src/sentry_file_extension.dart index f6f0c70de2..3cc764c36e 100644 --- a/file/lib/src/sentry_file_extension.dart +++ b/file/lib/src/sentry_file_extension.dart @@ -1,6 +1,8 @@ // ignore_for_file: invalid_use_of_internal_member -import 'dart:io' if (dart.library.html) 'dart:html'; +import 'dart:io' + if (dart.library.html) 'dart:html' + if (dart.library.js_interop) 'dart:js_interop'; import 'package:meta/meta.dart'; import 'package:sentry/sentry.dart'; diff --git a/flutter/lib/src/integrations/connectivity/connectivity_provider.dart b/flutter/lib/src/integrations/connectivity/connectivity_provider.dart index 30095dda0d..ea27f80f72 100644 --- a/flutter/lib/src/integrations/connectivity/connectivity_provider.dart +++ b/flutter/lib/src/integrations/connectivity/connectivity_provider.dart @@ -1,5 +1,6 @@ import 'noop_connectivity_provider.dart' - if (dart.library.html) 'web_connectivity_provider.dart'; + if (dart.library.html) 'html_connectivity_provider.dart' + if (dart.library.js_interop) 'web_connectivity_provider.dart'; abstract class ConnectivityProvider { factory ConnectivityProvider() => connectivityProvider(); diff --git a/flutter/lib/src/integrations/connectivity/html_connectivity_provider.dart b/flutter/lib/src/integrations/connectivity/html_connectivity_provider.dart new file mode 100644 index 0000000000..34d0e0ab42 --- /dev/null +++ b/flutter/lib/src/integrations/connectivity/html_connectivity_provider.dart @@ -0,0 +1,32 @@ +import 'dart:async'; +import 'dart:html' as html; + +import 'connectivity_provider.dart'; + +ConnectivityProvider connectivityProvider() { + return WebConnectivityProvider(); +} + +class WebConnectivityProvider implements ConnectivityProvider { + StreamSubscription? _onOnlineSub; + StreamSubscription? _onOfflineSub; + + @override + void listen(void Function(String connectivity) onChange) { + _onOnlineSub = html.window.onOnline.listen((_) { + onChange('wifi'); + }); + _onOfflineSub = html.window.onOffline.listen((_) { + onChange('none'); + }); + } + + @override + void cancel() { + _onOnlineSub?.cancel(); + _onOnlineSub = null; + + _onOfflineSub?.cancel(); + _onOfflineSub = null; + } +} diff --git a/flutter/lib/src/integrations/connectivity/web_connectivity_provider.dart b/flutter/lib/src/integrations/connectivity/web_connectivity_provider.dart index 34d0e0ab42..d1c18af777 100644 --- a/flutter/lib/src/integrations/connectivity/web_connectivity_provider.dart +++ b/flutter/lib/src/integrations/connectivity/web_connectivity_provider.dart @@ -1,5 +1,8 @@ import 'dart:async'; -import 'dart:html' as html; + +// We would lose compatibility with old dart versions by adding web to pubspec. +// ignore: depend_on_referenced_packages +import 'package:web/web.dart' as web; import 'connectivity_provider.dart'; @@ -8,15 +11,19 @@ ConnectivityProvider connectivityProvider() { } class WebConnectivityProvider implements ConnectivityProvider { - StreamSubscription? _onOnlineSub; - StreamSubscription? _onOfflineSub; + StreamSubscription? _onOnlineSub; + StreamSubscription? _onOfflineSub; @override void listen(void Function(String connectivity) onChange) { - _onOnlineSub = html.window.onOnline.listen((_) { + _onOnlineSub = web.EventStreamProviders.onlineEvent + .forElement(web.document.body!) + .listen((_) { onChange('wifi'); }); - _onOfflineSub = html.window.onOffline.listen((_) { + _onOfflineSub = web.EventStreamProviders.offlineEvent + .forElement(web.document.body!) + .listen((_) { onChange('none'); }); } diff --git a/flutter/lib/src/native/factory.dart b/flutter/lib/src/native/factory.dart index 981e1d6ead..c81c526594 100644 --- a/flutter/lib/src/native/factory.dart +++ b/flutter/lib/src/native/factory.dart @@ -1 +1,3 @@ -export 'factory_real.dart' if (dart.library.html) 'factory_web.dart'; +export 'factory_real.dart' + if (dart.library.html) 'factory_web.dart' + if (dart.library.js_interop) 'factory_web.dart'; diff --git a/flutter/lib/src/renderer/renderer.dart b/flutter/lib/src/renderer/renderer.dart index 3e41eced70..dc9d81276b 100644 --- a/flutter/lib/src/renderer/renderer.dart +++ b/flutter/lib/src/renderer/renderer.dart @@ -2,6 +2,7 @@ import 'package:meta/meta.dart'; import 'unknown_renderer.dart' if (dart.library.html) 'html_renderer.dart' + if (dart.library.js_interop) 'web_renderer.dart' if (dart.library.io) 'io_renderer.dart' as implementation; @internal diff --git a/flutter/lib/src/renderer/web_renderer.dart b/flutter/lib/src/renderer/web_renderer.dart new file mode 100644 index 0000000000..6baa3ca8b4 --- /dev/null +++ b/flutter/lib/src/renderer/web_renderer.dart @@ -0,0 +1,18 @@ +import 'dart:js_interop'; + +import 'renderer.dart'; + +FlutterRenderer? getRenderer() { + return isCanvasKitRenderer ? FlutterRenderer.canvasKit : FlutterRenderer.html; +} + +bool get isCanvasKitRenderer { + return _windowFlutterCanvasKit != null; +} + +// These values are set by the engine. They are used to determine if the +// application is using canvaskit or skwasm. +// +// See https://github.com/flutter/flutter/blob/414d9238720a3cde85475f49ce0ba313f95046f7/packages/flutter/lib/src/foundation/_capabilities_web.dart#L10 +@JS('window.flutterCanvasKit') +external JSAny? get _windowFlutterCanvasKit; diff --git a/hive/lib/src/sentry_box_collection.dart b/hive/lib/src/sentry_box_collection.dart index d4c605efe2..0dee9e9831 100644 --- a/hive/lib/src/sentry_box_collection.dart +++ b/hive/lib/src/sentry_box_collection.dart @@ -10,6 +10,7 @@ import 'package:hive/src/box_collection/box_collection_stub.dart' as stub; // ignore: implementation_imports import 'package:hive/src/box_collection/box_collection_stub.dart' if (dart.library.html) 'package:hive/src/box_collection/box_collection_indexed_db.dart' + if (dart.library.js_interop) 'package:hive/src/box_collection/box_collection_indexed_db.dart' if (dart.library.io) 'package:hive/src/box_collection/box_collection.dart' as impl; diff --git a/hive/test/mocks/mocks.dart b/hive/test/mocks/mocks.dart index 9b2db0e30d..8b6d7f4db9 100644 --- a/hive/test/mocks/mocks.dart +++ b/hive/test/mocks/mocks.dart @@ -4,6 +4,7 @@ import 'package:sentry/sentry.dart'; import 'package:hive/src/box_collection/box_collection_stub.dart' if (dart.library.html) 'package:hive/src/box_collection/box_collection_indexed_db.dart' + if (dart.library.js_interop) 'package:hive/src/box_collection/box_collection_indexed_db.dart' if (dart.library.io) 'package:hive/src/box_collection/box_collection.dart' as impl; diff --git a/hive/test/mocks/mocks.mocks.dart b/hive/test/mocks/mocks.mocks.dart index 6d1bb907d7..5cd8fc7e30 100644 --- a/hive/test/mocks/mocks.mocks.dart +++ b/hive/test/mocks/mocks.mocks.dart @@ -18,6 +18,7 @@ import 'package:hive/src/box_collection/box_collection_stub.dart' as stub; // ignore: implementation_imports import 'package:hive/src/box_collection/box_collection_stub.dart' if (dart.library.html) 'package:hive/src/box_collection/box_collection_indexed_db.dart' + if (dart.library.js_interop) 'package:hive/src/box_collection/box_collection_indexed_db.dart' if (dart.library.io) 'package:hive/src/box_collection/box_collection.dart' as impl; diff --git a/min_version_test/lib/main.dart b/min_version_test/lib/main.dart index 72a8aea404..504c706a43 100644 --- a/min_version_test/lib/main.dart +++ b/min_version_test/lib/main.dart @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:min_version_test/transaction/transaction_locator.dart' - if (dart.library.html) 'package:min_version_test/transaction/file_transaction.dart' - if (dart.library.io) 'package:min_version_test/transaction/web_transaction.dart'; +import 'package:min_version_test/transaction/transaction_stub.dart' + if (dart.library.html) 'package:min_version_test/transaction/web_transaction.dart' + if (dart.library.js_interop) 'package:min_version_test/transaction/web_transaction.dart' + if (dart.library.io) 'package:min_version_test/transaction/file_transaction.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:sentry_logging/sentry_logging.dart'; diff --git a/min_version_test/lib/transaction/file_transaction.dart b/min_version_test/lib/transaction/file_transaction.dart index 6f85ddc337..5afa62f6ce 100644 --- a/min_version_test/lib/transaction/file_transaction.dart +++ b/min_version_test/lib/transaction/file_transaction.dart @@ -1,12 +1,12 @@ -import 'package:min_version_test/transaction/transaction.dart'; import 'dart:io'; import 'package:logging/logging.dart'; import 'package:dio/dio.dart'; - import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:sentry_dio/sentry_dio.dart'; +import 'transaction.dart'; + class FileTransaction implements Transaction { @override Future start() async { diff --git a/min_version_test/lib/transaction/transaction_locator.dart b/min_version_test/lib/transaction/transaction_stub.dart similarity index 60% rename from min_version_test/lib/transaction/transaction_locator.dart rename to min_version_test/lib/transaction/transaction_stub.dart index 7e3a3f75d5..1869f2ee17 100644 --- a/min_version_test/lib/transaction/transaction_locator.dart +++ b/min_version_test/lib/transaction/transaction_stub.dart @@ -1,4 +1,4 @@ -import 'package:min_version_test/transaction/transaction.dart'; +import 'transaction.dart'; Transaction getTransaction() => throw UnsupportedError('Cannot create sample transaction.'); diff --git a/min_version_test/lib/transaction/web_transaction.dart b/min_version_test/lib/transaction/web_transaction.dart index 99ce8f3508..03c8cdd368 100644 --- a/min_version_test/lib/transaction/web_transaction.dart +++ b/min_version_test/lib/transaction/web_transaction.dart @@ -1,11 +1,10 @@ -import 'package:min_version_test/transaction/transaction.dart'; - import 'package:logging/logging.dart'; import 'package:dio/dio.dart'; - import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:sentry_dio/sentry_dio.dart'; +import 'transaction.dart'; + class WebTransaction implements Transaction { @override Future start() async { diff --git a/min_version_test/pubspec.yaml b/min_version_test/pubspec.yaml index 8fdfe47329..e8e76dc9df 100644 --- a/min_version_test/pubspec.yaml +++ b/min_version_test/pubspec.yaml @@ -91,4 +91,3 @@ flutter: # # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages - \ No newline at end of file