From 3ba2a54965b69b149812448e7e046a796e25d186 Mon Sep 17 00:00:00 2001 From: chungdaniel Date: Wed, 8 Jan 2025 12:10:09 -0800 Subject: [PATCH] feat: use latest Amplitude Browser 2 SDK (2.11.10) for flutter SDK web (#216) Primary changes: - Update web files for using Browser SDK 2 as opposed to maintenance Javascript SDK - Update web bridge file - Update dart-js interface file - Update example app to use new script loader - Add web-specific configuration options - Session replay, autocapture are not currently supported (autocapture functionality to be investigated later) - Use js_interop and js_interop_unsafe rather than package:js/js.dart and package:js/js_util.dart - Added minimum flutter version of 3.3 to dependency matrix to support js_interop Extras: - Improved documentation for members of Configuration in configuration.dart - Fixed lint issues/linter github actions workflow Note: - No tests have been added for web since dart tests default to the Dart VM platform which does not have access to js libraries (e.g. js_interop, package:js/js.dart) Jira tickets: - Update web bridge https://amplitude.atlassian.net/browse/AMP-89343 - Update dart-js interface file https://amplitude.atlassian.net/browse/AMP-89344 - Migrate to js_interop https://amplitude.atlassian.net/browse/AMP-104922 --- .github/workflows/swift-lint.yml | 4 +- README.md | 6 +- example/README.md | 11 +- example/android/app/build.gradle | 2 +- example/android/build.gradle | 2 +- example/ios/Flutter/AppFrameworkInfo.plist | 2 +- example/ios/Podfile.lock | 4 +- example/ios/Runner.xcodeproj/project.pbxproj | 13 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- example/ios/Runner/AppDelegate.swift | 2 +- example/ios/Runner/Info.plist | 4 + example/lib/app_state.dart | 1 + example/lib/device_id_form.dart | 7 +- example/lib/event_form.dart | 4 +- example/lib/flush_thresholds_form.dart | 6 +- example/lib/group_form.dart | 2 +- example/lib/group_identify_form.dart | 3 +- example/lib/identify_form.dart | 3 +- example/lib/my_app.dart | 3 +- example/lib/reset.dart | 2 +- example/lib/revenue_form.dart | 3 +- example/lib/user_id_form.dart | 18 +- example/pubspec.lock | 24 +- example/pubspec.yaml | 2 +- example/web/index.html | 27 +- lib/amplitude.dart | 13 + lib/amplitude_web.dart | 304 +++++------------- lib/configuration.dart | 139 +++++++- lib/constants.dart | 10 +- lib/cookie_options.dart | 29 ++ lib/web/amplitude_js.dart | 71 +--- lib/web/flutter_library_plugin.dart | 21 ++ pubspec.yaml | 2 +- test/amplitude_test.dart | 13 + test/configuration_test.dart | 70 +++- 35 files changed, 458 insertions(+), 371 deletions(-) create mode 100644 lib/cookie_options.dart create mode 100644 lib/web/flutter_library_plugin.dart diff --git a/.github/workflows/swift-lint.yml b/.github/workflows/swift-lint.yml index a0c497a..cfe473d 100644 --- a/.github/workflows/swift-lint.yml +++ b/.github/workflows/swift-lint.yml @@ -4,12 +4,12 @@ on: [pull_request] jobs: lint: - runs-on: macos-12 + runs-on: macos-13 steps: - name: Checkout uses: actions/checkout@v3 - - name: Set Xcode 14 + - name: Set Xcode 14.1 run: | sudo xcode-select -switch /Applications/Xcode_14.1.app diff --git a/README.md b/README.md index dbaaed1..b1671e9 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,9 @@ From Amplitude Flutter v4, we bump up the kotlin version to v1.9.22 to support l The following matrix lists the minimum support for Amplitude Flutter SDK version. -| Amplitude Flutter | Gradle | Android Gradle Plugin | Kotlin Gradle Plugin | -|-------------------|-------|-----------------------|----------------------| -| `4.+` | `8.2` | `8.2.2` | `1.9.22` | +| Flutter | Amplitude Flutter | Gradle | Android Gradle Plugin | Kotlin Gradle Plugin | +|---------|-------------------|-------|-----------------------|-----------------------| +|`3.3+` | `4.+` | `8.2` | `8.2.2` | `1.9.22` | Learn more about the Android [Gradle Plugin compatibility](https://developer.android.com/studio/releases/gradle-plugin#updating-gradle), [Gradle compatibility](https://docs.gradle.org/current/userguide/compatibility.html#kotlin), and [Kotlin compatibility](https://kotlinlang.org/docs/whatsnew17.html#bumping-minimum-supported-versions). diff --git a/example/README.md b/example/README.md index 041e03f..a76abee 100644 --- a/example/README.md +++ b/example/README.md @@ -11,13 +11,13 @@ A few resources to get you started if this is your first Flutter project: - [Lab: Write your first Flutter app](https://flutter.io/docs/get-started/codelab) - [Cookbook: Useful Flutter samples](https://flutter.io/docs/cookbook) -For help getting started with Flutter, view our -[online documentation](https://flutter.io/docs), which offers tutorials, +For help getting started with Flutter, view our +[online documentation](https://flutter.io/docs), which offers tutorials, samples, guidance on mobile development, and a full API reference. ## Run the example -Assuming you have Flutter setup on your machine. +Assuming you have Flutter setup on your machine. Update your Amplitude API key in `lib/main.dart`. @@ -31,3 +31,8 @@ flutter run ```shell flutter run -d chrome ``` +In some cases (e.g. Chrome with forced sign-in), above command may not work well. +Use the below command to start the server, then follow the printed link in console. +```shell +flutter run -d web-server --web-port=5000 --web-enable-expression-evaluation +``` diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index be0e479..bd8ab60 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -41,7 +41,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.example.amplitude_flutter_example" - minSdkVersion 16 + minSdkVersion flutter.minSdkVersion targetSdkVersion 33 versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/example/android/build.gradle b/example/android/build.gradle index 6a3d7ee..6554ae9 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -26,6 +26,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index 8d4492f..7c56964 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 9.0 + 12.0 diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 02f853e..3df4d27 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -26,8 +26,8 @@ SPEC CHECKSUMS: amplitude_flutter: aed4ad5d2da06894245fde7e8c56fed39ac6dca8 AmplitudeSwift: cc22038404dc5581e2dea5dc2501302959dd3c90 AnalyticsConnector: a53214d38ae22734c6266106c0492b37832633a9 - Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 PODFILE CHECKSUM: cc1f88378b4bfcf93a6ce00d2c587857c6008d3b -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 387b2e8..30b43ac 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -164,7 +164,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0910; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = "The Chromium Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -232,10 +232,12 @@ }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -246,6 +248,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -349,7 +352,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; @@ -427,7 +430,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -474,7 +477,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 786d6aa..a00db7a 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ UIViewControllerBasedStatusBarAppearance + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/example/lib/app_state.dart b/example/lib/app_state.dart index 5e48e6e..01378cb 100644 --- a/example/lib/app_state.dart +++ b/example/lib/app_state.dart @@ -1,3 +1,4 @@ +// ignore_for_file: depend_on_referenced_packages import 'package:amplitude_flutter/amplitude.dart'; import 'package:flutter/material.dart'; diff --git a/example/lib/device_id_form.dart b/example/lib/device_id_form.dart index ad7c5b3..39d261c 100644 --- a/example/lib/device_id_form.dart +++ b/example/lib/device_id_form.dart @@ -3,7 +3,8 @@ import 'app_state.dart'; class DeviceIdForm extends StatefulWidget { @override - _DeviceIdFormState createState() => _DeviceIdFormState(); + // ignore: library_private_types_in_public_api + State createState() => _DeviceIdFormState(); } class _DeviceIdFormState extends State { @@ -12,7 +13,7 @@ class _DeviceIdFormState extends State { AppState .of(context) .analytics - ..setDeviceId(deviceId); + .setDeviceId(deviceId); }; } @@ -23,7 +24,7 @@ class _DeviceIdFormState extends State { children: [ Text('Device Id', style: Theme.of(context).textTheme.headlineSmall), const SizedBox(height: 10), - new TextField( + TextField( autocorrect: false, decoration: InputDecoration(labelText: 'Device Id'), onChanged: makeHandler(context), diff --git a/example/lib/event_form.dart b/example/lib/event_form.dart index d95fe8e..bd1619b 100644 --- a/example/lib/event_form.dart +++ b/example/lib/event_form.dart @@ -1,11 +1,13 @@ import 'package:flutter/material.dart'; + +// ignore_for_file: depend_on_referenced_packages import 'package:amplitude_flutter/events/base_event.dart'; import 'app_state.dart'; class EventForm extends StatefulWidget { @override - _EventFormState createState() => _EventFormState(); + State createState() => _EventFormState(); } class _EventFormState extends State { diff --git a/example/lib/flush_thresholds_form.dart b/example/lib/flush_thresholds_form.dart index f4a4a1f..4a6e6e4 100644 --- a/example/lib/flush_thresholds_form.dart +++ b/example/lib/flush_thresholds_form.dart @@ -4,7 +4,7 @@ import 'app_state.dart'; class FlushThresholdForm extends StatefulWidget { @override - _FlushThresholdFormState createState() => _FlushThresholdFormState(); + State createState() => _FlushThresholdFormState(); } class _FlushThresholdFormState extends State { @@ -20,7 +20,7 @@ class _FlushThresholdFormState extends State { if (eventUploadThresholdInput.text.isNotEmpty && value != null) { AppState.of(context) // ..analytics.setEventUploadThreshold(value) - ..setMessage('Event upload threshold set.'); + .setMessage('Event upload threshold set.'); } } @@ -30,7 +30,7 @@ class _FlushThresholdFormState extends State { if (eventUploadPeriodMillisInput.text.isNotEmpty && value != null) { AppState.of(context) // ..analytics.setEventUploadPeriodMillis(value) - ..setMessage('Event upload period millis set.'); + .setMessage('Event upload period millis set.'); } } diff --git a/example/lib/group_form.dart b/example/lib/group_form.dart index ac671e1..18a4566 100644 --- a/example/lib/group_form.dart +++ b/example/lib/group_form.dart @@ -4,7 +4,7 @@ import 'app_state.dart'; class GroupForm extends StatefulWidget { @override - _GroupFormState createState() => _GroupFormState(); + State createState() => _GroupFormState(); } class _GroupFormState extends State { diff --git a/example/lib/group_identify_form.dart b/example/lib/group_identify_form.dart index 787dd3f..0ce6ef0 100644 --- a/example/lib/group_identify_form.dart +++ b/example/lib/group_identify_form.dart @@ -1,3 +1,4 @@ +// ignore_for_file: depend_on_referenced_packages import 'package:amplitude_flutter/events/identify.dart'; import 'package:flutter/material.dart'; @@ -5,7 +6,7 @@ import 'app_state.dart'; class GroupIdentifyForm extends StatefulWidget { @override - _GroupIdentifyFormState createState() => _GroupIdentifyFormState(); + State createState() => _GroupIdentifyFormState(); } class _GroupIdentifyFormState extends State { diff --git a/example/lib/identify_form.dart b/example/lib/identify_form.dart index e30736c..2e84c78 100644 --- a/example/lib/identify_form.dart +++ b/example/lib/identify_form.dart @@ -1,3 +1,4 @@ +// ignore_for_file: depend_on_referenced_packages import 'package:amplitude_flutter/events/identify.dart'; import 'package:flutter/material.dart'; @@ -5,7 +6,7 @@ import 'app_state.dart'; class IdentifyForm extends StatefulWidget { @override - _IdentifyFormState createState() => _IdentifyFormState(); + State createState() => _IdentifyFormState(); } class _IdentifyFormState extends State { diff --git a/example/lib/my_app.dart b/example/lib/my_app.dart index d95b806..af572f5 100644 --- a/example/lib/my_app.dart +++ b/example/lib/my_app.dart @@ -1,5 +1,6 @@ import 'dart:async'; +// ignore_for_file: depend_on_referenced_packages import 'package:amplitude_flutter/amplitude.dart'; import 'package:amplitude_flutter/configuration.dart'; import 'package:amplitude_flutter/constants.dart'; @@ -22,7 +23,7 @@ class MyApp extends StatefulWidget { final String apiKey; @override - _MyAppState createState() => _MyAppState(); + State createState() => _MyAppState(); } class _MyAppState extends State { diff --git a/example/lib/reset.dart b/example/lib/reset.dart index b8a4413..07eaef1 100644 --- a/example/lib/reset.dart +++ b/example/lib/reset.dart @@ -4,7 +4,7 @@ import 'app_state.dart'; class ResetForm extends StatefulWidget { @override - _DeviceState createState() => _DeviceState(); + State createState() => _DeviceState(); } class _DeviceState extends State { diff --git a/example/lib/revenue_form.dart b/example/lib/revenue_form.dart index 4ab4214..9a0dcf2 100644 --- a/example/lib/revenue_form.dart +++ b/example/lib/revenue_form.dart @@ -1,12 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +// ignore_for_file: depend_on_referenced_packages import 'package:amplitude_flutter/events/revenue.dart'; import 'app_state.dart'; class RevenueForm extends StatefulWidget { @override - _RevenueFormState createState() => _RevenueFormState(); + State createState() => _RevenueFormState(); } class _RevenueFormState extends State { diff --git a/example/lib/user_id_form.dart b/example/lib/user_id_form.dart index 3fb33ad..0ef209c 100644 --- a/example/lib/user_id_form.dart +++ b/example/lib/user_id_form.dart @@ -1,16 +1,18 @@ import 'package:flutter/material.dart'; - import 'app_state.dart'; class UserIdForm extends StatefulWidget { @override - _UserIdFormState createState() => _UserIdFormState(); + State createState() => _UserIdFormState(); } class _UserIdFormState extends State { void Function(String) makeHandler(BuildContext context) { return (String userId) { - AppState.of(context).analytics..setUserId(userId.isEmpty ? null : userId); + AppState + .of(context) + .analytics + .setUserId(userId.isEmpty ? null : userId); }; } @@ -19,17 +21,9 @@ class _UserIdFormState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Text('Current User Id', style: Theme.of(context).textTheme.headlineSmall), - // FutureBuilder( - // // future: AppState.of(context).analytics.getUserId(), - // builder: (BuildContext context, AsyncSnapshot snapshot) { - // return Text(snapshot.data.toString()); - // }, - // ), - // const SizedBox(height: 10), Text('User Id', style: Theme.of(context).textTheme.headlineSmall), const SizedBox(height: 10), - new TextField( + TextField( autocorrect: false, decoration: InputDecoration(labelText: 'User Id'), onChanged: makeHandler(context)), diff --git a/example/pubspec.lock b/example/pubspec.lock index 0e78d19..e1a7bb7 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -91,18 +91,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -123,18 +123,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" path: dependency: transitive description: @@ -192,10 +192,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.2" vector_math: dependency: transitive description: @@ -208,10 +208,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.5" sdks: dart: ">=3.3.0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 31a9ef4..b7137b7 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -3,7 +3,7 @@ description: Demonstrates how to use the amplitude_flutter plugin. publish_to: 'none' environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=3.3.0 < 4.0.0" dependencies: flutter: diff --git a/example/web/index.html b/example/web/index.html index 44512eb..46e9b67 100644 --- a/example/web/index.html +++ b/example/web/index.html @@ -29,32 +29,9 @@ - - diff --git a/lib/amplitude.dart b/lib/amplitude.dart index 2657cba..52fd07d 100644 --- a/lib/amplitude.dart +++ b/lib/amplitude.dart @@ -211,6 +211,19 @@ class Amplitude { return await _channel.invokeMethod('setDeviceId', properties); } + /// Web only. + /// Disables tracking. + /// + /// Set setOptOut to true to disable logging for a specific user. + /// Set setOptOut to false to re-enable logging. + Future setOptOut(bool enabled) async { + Map properties = {}; + properties['setOptOut'] = enabled; + + return await _channel.invokeMethod('setOptOut', properties); + } + + /// Resets userId to 'null' and deviceId to a random UUID. /// /// Note different devices on different platforms should have different device Ids. diff --git a/lib/amplitude_web.dart b/lib/amplitude_web.dart index 7b97ac6..a793cc9 100644 --- a/lib/amplitude_web.dart +++ b/lib/amplitude_web.dart @@ -1,12 +1,17 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:js_interop'; +import 'dart:js_interop_unsafe'; import 'package:flutter/services.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; -import 'package:js/js_util.dart' as js; import 'web/amplitude_js.dart'; +import 'web/flutter_library_plugin.dart'; +import 'constants.dart'; +@JS() +external Amplitude get amplitude; class AmplitudeFlutterPlugin { static void registerWith(Registrar registrar) { final channel = MethodChannel( @@ -22,166 +27,51 @@ class AmplitudeFlutterPlugin { /// Note: Check the "federated" architecture for a new way of doing this: /// https://flutter.dev/go/federated-plugins Future handleMethodCall(MethodCall call) async { - var args = jsonDecode(call.arguments.toString()); - var instanceName = args['instanceName']; - Amplitude amplitude = Amplitude.getInstance(instanceName); switch (call.method) { case "init": { + var args = call.arguments; String apiKey = args['apiKey']; - var userId = args['userId'] ?? null; - return amplitude.init(apiKey, userId); - } - case "enableCoppaControl": - { - return false; - } - case "disableCoppaControl": - { - return false; - } - case "setOptOut": - { - bool optOut = args['optOut']; - return amplitude.setOptOut(optOut); - } - case "trackingSessionEvents": - { - return false; - } - case "setUserId": - { - String? userId = args['userId'] ?? null; - bool startNewSession = args['startNewSession'] ?? false; - return amplitude.setUserId(userId, startNewSession); - } - case "setDeviceId": - { - String deviceId = args['deviceId']; - return amplitude.setDeviceId(deviceId); - } - case "setServerUrl": - { - String serverUrl = args['serverUrl']; - return amplitude.setServerUrl(serverUrl); - } - case "setEventUploadThreshold": - { - int eventUploadThreshold = args['eventUploadThreshold']; - return amplitude.setEventUploadThreshold(eventUploadThreshold); - } - case "setEventUploadPeriodMillis": - { - int eventUploadPeriodMillis = args['eventUploadPeriodMillis']; - return amplitude.options.eventUploadPeriodMillis = eventUploadPeriodMillis; - } - case "regenerateDeviceId": - { - return amplitude.regenerateDeviceId(); - } - case "setUseDynamicConfig": - { - bool useDynamicConfig = args['useDynamicConfig']; - return amplitude.setUseDynamicConfig(useDynamicConfig); - } - case "logEvent": - { - String eventType = args['eventType']; - var eventProperties = (args['eventProperties'] != null) - ? mapToJSObj(args['eventProperties']) - : null; - bool outOfSession = args['outOfSession'] ?? false; - return amplitude.logEvent( - eventType, eventProperties, null, null, outOfSession); - } - case "logRevenue": - { - double price = args['price'] ?? 0; - int quantity = args['quantity'] ?? 1; - var productIdentifier = args['productIdentifier'] ?? null; - return amplitude.logRevenue(price, quantity, productIdentifier); - } - case "logRevenueAmount": - { - double amount = args['amount'] ?? 0; - return amplitude.logRevenue(amount, 1, null); + JSObject configuration = getConfiguration(call); + + // Set library + amplitude.add(createJSInteropWrapper( + FlutterLibraryPlugin(args['library'] ?? 'amplitude_flutter/unknown') + )); + amplitude.init(apiKey, configuration); } + case "track": case "identify": - { - var userProperties = args['userProperties']; - Identify identify = createIdentify(userProperties); - return amplitude.identify(identify); - } - case "setGroup": - { - String groupType = args['groupType']; - dynamic groupName = args['groupName']; - return amplitude.setGroup(groupType, groupName); - } case "groupIdentify": - { - String groupType = args['groupType']; - dynamic groupName = args['groupName']; - var userProperties = args['userProperties']; - Identify groupIdentify = createIdentify(userProperties); - bool outOfSession = args['outOfSession'] ?? false; - return amplitude.groupIdentify( - groupType, groupName, groupIdentify, null, null, outOfSession); - } - case "setUserProperties": - { - var userProperties = mapToJSObj(args['userProperties']); - return amplitude.setUserProperties(userProperties); - } - case "clearUserProperties": - { - return amplitude.clearUserProperties(); - } - case "uploadEvents": - { - return amplitude.sendEvents(); - } - case "setLibraryName": - { - String libraryName = args['libraryName']; - return amplitude.setLibrary(libraryName, null); - } - case "setLibraryVersion": - { - String libraryVersion = args['libraryVersion']; - return amplitude.setLibrary(null, libraryVersion); - } - case "getDeviceId": - { - return amplitude.getDeviceId(); - } - case "getUserId": - { - return amplitude.getUserId(); - } - case "getSessionId": - { - return amplitude.getSessionId(); - } - case "useAppSetIdForDeviceId": - { - return false; - } - case "setMinTimeBetweenSessionsMillis": - { - int timeInMillis = args['timeInMillis']; - return amplitude.setMinTimeBetweenSessionsMillis(timeInMillis); - } - case "setServerZone": - { - String serverZone = args['serverZone']; - bool updateServerUrl = args['updateServerUrl']; - return amplitude.setServerZone(serverZone, updateServerUrl); - } - case "setOffline": - { - return false; - } + case "setGroup": + case "revenue": + { + JSObject event = getEvent(call); + amplitude.track(event); + } + case "setUserId": + { + String userId = call.arguments['setUserId']; + amplitude.setUserId(userId.toJS); + } + case "setDeviceId": + { + String deviceId = call.arguments['setDeviceId']; + amplitude.setDeviceId(deviceId.toJS); + } + case "reset": + { + amplitude.reset(); + } + case "flush": + { + amplitude.flush(); + } + case "setOptOut": + { + bool enabled = call.arguments['setOptOut']; + amplitude.setOptOut(enabled); + } default: throw PlatformException( code: 'Unimplemented', @@ -191,77 +81,55 @@ class AmplitudeFlutterPlugin { } } - Object mapToJSObj(Map map) { - var object = js.newObject(); + /// Converts a Dart Map to a JavaScript object using `js_interop_unsafe`. + /// + /// This method takes a Dart Map and recursively converts it into a JavaScript + /// object. Each key-value pair in the Dart Map is set as a property on the + /// JavaScript object. If a value in the Dart Map is another Map, this method + /// will recursively convert that Map as well. + /// + /// - Parameter map: The Dart Map to convert. + /// - Returns: A JavaScript object with the same properties as the Dart Map. + JSObject mapToJSObj(Map map) { + var object = JSObject(); map.forEach((k, v) { var key = k; var value = (v is Map) ? mapToJSObj(v) : v; - js.setProperty(object, key, value); + object[key] = value; }); return object; } - Identify createIdentify(Map userProperties) { - Identify identify = new Identify(); - userProperties.forEach((String operation, dynamic properties) { - properties.forEach((String key, dynamic value) { - switch (operation) { - case "\$add": - { - identify.add(key, value); - break; - } + /// Extracts an event from call.arguments and converts it to a JSObject representing an Event object. + /// + /// This method extracts event properties from the provided MethodCall argument + /// and converts them into a JavaScript object representing an Event using the mapToJSObj method. + /// + /// Returns: + /// - `JSObject`: A JavaScript object representing the event. + JSObject getEvent(MethodCall call) { + var eventMap = call.arguments; + return mapToJSObj(eventMap); + } - case "\$append": - { - identify.append(key, value); - break; - } - case "\$prepend": - { - identify.prepend(key, value); - break; - } - case "\$set": - { - identify.set(key, value); - break; - } - case "\$setOnce": - { - identify.setOnce(key, value); - break; - } - case "\$unset": - { - identify.unset(key); - break; - } - case "\$preInsert": - { - identify.preInsert(key, value); - break; - } - case "\$postInsert": - { - identify.postInsert(key, value); - break; - } - case "\$remove": - { - identify.remove(key, value); - break; - } - case "\$clearAll": - { - identify.clearAll(); - break; - } - default: - break; - } - }); - }); - return identify; + + /// Maps the configuration settings for the Amplitude SDK to a JavaScript object. + /// + /// For more details on configuring the SDK, refer to the official documentation: + /// https://amplitude.com/docs/sdks/analytics/browser/browser-sdk-2#configure-the-sdk + /// + /// Returns a map containing the configuration settings. + JSObject getConfiguration(MethodCall call) { + var configuration = mapToJSObj(call.arguments); + + // autocapture is not supported in flutter web + configuration['autocapture'] = false.toJS; + + if (call.arguments.containsKey('logLevel')) { + var logLevelString = call.arguments['logLevel'] as String; + configuration['logLevel'] = LogLevel.values.byName(logLevelString).index.toJS; + } + + return configuration; } } diff --git a/lib/configuration.dart b/lib/configuration.dart index 053d7f4..18e4935 100644 --- a/lib/configuration.dart +++ b/lib/configuration.dart @@ -1,39 +1,151 @@ import 'constants.dart'; import 'tracking_options.dart'; import 'default_tracking.dart'; +import 'cookie_options.dart'; class Configuration { + /// Applicable to all platforms (iOS, Android, Web) + /// + /// The API key for the Amplitude project. String apiKey; + /// Applicable to all platforms (iOS, Android, Web) + /// + /// Sets the maximum number of events batched in a single upload atempt. int flushQueueSize; + /// Applicable to all platforms (iOS, Android, Web) + /// + /// Sets the interval of uploading events to Amplitude in milliseconds. int flushIntervalMillis; + /// Applicable to all platforms (iOS, Android, Web) + /// + /// The name of the instance. Instances with the same name will share storage and identity. + /// For isolated storage and identity use a unique instanceName for each instance. late String instanceName; + /// Applicable to all platforms (iOS, Android, Web) + /// + /// Sets permission to track events. Setting a value of true prevents Amplitude + /// from tracking and uploading events. bool optOut; + /// Applicable to all platforms (iOS, Android, Web) + /// + /// Sets the level of logging. The default value is LogLevel.warn. LogLevel logLevel; + /// Applicable to all platforms (iOS, Android, Web) + /// + /// Sets the minimum length for the value of userId and deviceId properties. int? minIdLength; + /// Applicable to all platforms (iOS, Android, Web) + /// + /// Sets partner ID. Amplitude requires the customer who built an event + /// ingestion integration to add the partner identifier to partner_id. String? partnerId; + /// Applicable to all platforms (iOS, Android, Web) + /// + /// Sets the maximum number of retries for failed upload attempts. + /// This is only applicable to errors that the SDK can retry. int flushMaxRetries; + /// Applicable to all platforms (iOS, Android, Web) + /// + /// Sets whether to upload events to Batch API instead of the default HTTP V2 API or not. bool useBatch; + /// Applicable to all platforms (iOS, Android, Web) + /// + /// 'EU' or 'US'. Sets the Amplitude server zone. Set this to EU for Amplitude + /// projects created in EU data center. ServerZone serverZone; + /// Applicable to all platforms (iOS, Android, Web) + /// + /// Sets the URL where events are upload to. String? serverUrl; + /// Applicable to all platforms (iOS, Android, Web) + /// + /// The amount of time for session timeout. The value is in milliseconds. + /// + /// Defaults to 300,000 milliseconds (5 minutes) for iOS/Android and + /// 1,800,000 milliseconds (30 minutes) for Web. Overriding this value will + /// change the session timeout for all platforms. + /// This maps to `minTimeBetweenSessionsMillis` for iOS/Android and `sessionTimeout` for Web. int minTimeBetweenSessionsMillis; - DefaultTrackingOptions defaultTracking; + /// Applicable to all platforms (iOS, Android, Web) + /// + /// Configures tracking of extra properties. + /// Check platform-specific documentation for more information. TrackingOptions trackingOptions; + /// Mobile (iOS and Android) specific + /// + /// Deprecated. Enable tracking of default events for sessions, app lifecycles, screen views, and deep links. + DefaultTrackingOptions defaultTracking; + /// Mobile (iOS and Android) specific + /// + /// Whether to enable COPPA control for tracking options. bool enableCoppaControl; /// Mobile (iOS and Android) specific + /// + /// Flushing of unsent events on app close. bool flushEventsOnClose; /// Mobile (iOS and Android) specific + /// + /// The amount of time SDK will attempt to batch intercepted identify events. The value is in milliseconds. int identifyBatchIntervalMillis; /// Mobile (iOS and Android) specific + /// + /// Whether to migrate maintenance SDK data (events, user/device ID). + /// See platform-specific documentation for more information. bool migrateLegacyData; + + /// Applicable to Web and Android + /// + /// The device ID to use for this device. If no deviceID is provided one will be generated automatically. + String? deviceId; + /// Android specific + /// + /// Whether to enable Android location service. Learn more at + /// https://amplitude.com/docs/sdks/analytics/android/android-kotlin-sdk#location-tracking bool locationListening; /// Android specific + /// + /// Whether to use advertising id as device id. + /// See https://amplitude.com/docs/sdks/analytics/android/android-kotlin-sdk#advertiser-id for more information. bool useAdvertisingIdForDeviceId; /// Android specific + /// + /// Whether to use app set id as device id. + /// See https://amplitude.com/docs/sdks/analytics/android/android-kotlin-sdk#app-set-id for more information. bool useAppSetIdForDeviceId; + /// Web specific + /// + /// Sets an app version for events tracked. This can be the version of your application. For example: "1.0.0". String? appVersion; + /// Web specific + /// + /// Sets cookie options. See https://amplitude.com/docs/sdks/analytics/browser/browser-sdk-2#configure-the-sdk + /// for more information. + CookieOptions cookieOptions; + /// Web specific + /// + /// Sets storage API for user identity. Options include cookie for document.cookie, localStorage for localStorage, + /// or none to opt-out of persisting user identity. + String identityStorage; + /// Web specific + /// + /// Sets an identifier for the tracked user. Must have a minimum length of 5 characters unless overridden with the + /// minIdLength option. + String? userId; + /// Web specific + /// + /// Sets request API to use by name. Options include fetch for fetch, xhr for XMLHTTPRequest, or beacon for + /// navigator.sendBeacon. + String? transport; + /// Web specific + /// + /// Whether the SDK fetches remote configuration. + /// See https://amplitude.com/docs/sdks/analytics/browser/browser-sdk-2#remote-configuration for more information. + bool fetchRemoteConfig; + + /// Configuration for Amplitude instance. /// @@ -42,6 +154,11 @@ class Configuration { /// /// Note the Configuration is immutable (cannot be changed) after being passed to Amplitude /// `optOut` can be changed later by calling `setOptOut()`. + /// + /// See platform-specific documentation for more information. + /// Android: https://amplitude.com/docs/sdks/analytics/android/android-kotlin-sdk#configure-the-sdk + /// iOS: https://amplitude.com/docs/sdks/analytics/ios/ios-swift-sdk#configure-the-sdk + /// Web: https://amplitude.com/docs/sdks/analytics/browser/browser-sdk-2#configure-the-sdk Configuration({ required this.apiKey, this.flushQueueSize = Constants.flushQueueSize, @@ -55,7 +172,7 @@ class Configuration { this.useBatch = false, this.serverZone = ServerZone.us, this.serverUrl, - this.minTimeBetweenSessionsMillis = Constants.minTimeBetweenSessionsMillis, + this.minTimeBetweenSessionsMillis = Constants.minTimeBetweenSessionsMillisUnset, this.defaultTracking = const DefaultTrackingOptions(), TrackingOptions? trackingOptions, this.enableCoppaControl = false, @@ -66,7 +183,14 @@ class Configuration { this.useAdvertisingIdForDeviceId = false, this.useAppSetIdForDeviceId = false, this.appVersion, - }): trackingOptions = trackingOptions ?? TrackingOptions() { + this.deviceId, + CookieOptions? cookieOptions, + this.identityStorage = 'cookie', + this.userId, + this.transport = 'fetch', + this.fetchRemoteConfig = false, + }): trackingOptions = trackingOptions ?? TrackingOptions(), + cookieOptions = cookieOptions ?? CookieOptions() { this.instanceName = instanceName.isEmpty ? Constants.defaultInstanceName : instanceName; } @@ -84,7 +208,7 @@ class Configuration { 'useBatch': useBatch, 'serverZone': serverZone.name, 'serverUrl': serverUrl, - 'minTimeBetweenSessionsMillis': minTimeBetweenSessionsMillis, + 'minTimeBetweenSessionsMillis': minTimeBetweenSessionsMillis != Constants.minTimeBetweenSessionsMillisUnset ? minTimeBetweenSessionsMillis : Constants.minTimeBetweenSessionsMillisForMobile, 'defaultTracking': defaultTracking.toMap(), 'trackingOptions': trackingOptions.toMap(), 'enableCoppaControl': enableCoppaControl, @@ -95,6 +219,13 @@ class Configuration { 'useAdvertisingIdForDeviceId': useAdvertisingIdForDeviceId, 'useAppSetIdForDeviceId': useAppSetIdForDeviceId, 'appVersion': appVersion, + 'deviceId': deviceId, + 'cookieOptions': cookieOptions.toMap(), + 'identityStorage': identityStorage, + 'sessionTimeout': minTimeBetweenSessionsMillis != Constants.minTimeBetweenSessionsMillisUnset ? minTimeBetweenSessionsMillis : Constants.minTimeBetweenSessionsMillisForWeb, + 'userId': userId, + 'transport': transport, + 'fetchRemoteConfig': fetchRemoteConfig, // This field doesn't belong to Configuration // Pass it for FlutterLibraryPlugin 'library': '${Constants.packageName}/${Constants.packageVersion}' diff --git a/lib/constants.dart b/lib/constants.dart index 778b08c..5dc8696 100644 --- a/lib/constants.dart +++ b/lib/constants.dart @@ -11,7 +11,9 @@ class Constants { static const defaultInstanceName = '\$default_instance'; static const logLevel = 'info'; static const flushMaxRetries = 5; - static const minTimeBetweenSessionsMillis = 5 * 60 * 1000; // 5 minutes + static const minTimeBetweenSessionsMillisUnset = -1; + static const minTimeBetweenSessionsMillisForMobile = 5 * 60 * 1000; // 5 minutes + static const minTimeBetweenSessionsMillisForWeb = 30 * 60 * 1000; // 30 minutes } enum LogLevel { @@ -26,3 +28,9 @@ enum ServerZone { us, eu, } + +enum IdentityStorage { + cookie, + localStorage, + none, +} diff --git a/lib/cookie_options.dart b/lib/cookie_options.dart new file mode 100644 index 0000000..10ca6c6 --- /dev/null +++ b/lib/cookie_options.dart @@ -0,0 +1,29 @@ +/// Cookie options for Web SDK +/// +/// Refer to the official documentation for Web more details: +/// https://amplitude.com/docs/sdks/analytics/browser/browser-sdk-2#configure-the-sdk +class CookieOptions { + final String domain; + final int expiration; + final String sameSite; + final bool secure; + final bool upgrade; + + CookieOptions({ + this.domain = '', + this.expiration = 365, + this.sameSite = 'Lax', + this.secure = false, + this.upgrade = true, + }); + + Map toMap() { + return { + 'domain': domain, + 'expiration': expiration, + 'sameSite': sameSite, + 'secure': secure, + 'upgrade': upgrade, + }; + } +} diff --git a/lib/web/amplitude_js.dart b/lib/web/amplitude_js.dart index 98a2bea..8ded00d 100644 --- a/lib/web/amplitude_js.dart +++ b/lib/web/amplitude_js.dart @@ -1,63 +1,14 @@ -@JS() - -import 'package:js/js.dart'; - -@JS('amplitude.options') -class Options { - external set eventUploadPeriodMillis(int value); -} +import 'dart:js_interop'; +import 'flutter_library_plugin.dart'; @JS('amplitude') -class Amplitude { - external Amplitude(String instanceName); - external static Amplitude getInstance(String instanceName); - external void init(String api, String? userId); - external void setOptOut(bool optOut); - external void setUserId(String? userId, bool startNewSession); - external void setDeviceId(String deviceId); - external void setServerUrl(String serverUrl); - external void setEventUploadThreshold(int); - external void regenerateDeviceId(); - external void setUseDynamicConfig(bool useDynamicConfig); - external void logEvent(String eventType, Object? eventProperties, - Function? optCallback, Function? optErrorCallback, bool? outOfSession); - external void logRevenue( - double price, int quantity, String? productIdentifier); - external void setGroup(String groupType, dynamic groupName); - external void setUserProperties(Object userProperties); - external void clearUserProperties(); - external void sendEvents(); - external void setLibrary(String? libraryName, String? libraryVersion); - external String getUserId(); - external String getDeviceId(); - external void getSessionId(); - external void setMinTimeBetweenSessionsMillis(int timeInMillis); - external void setServerZone(String serverZone, bool updateServerUrl); - external void identify(Identify identify); - external void groupIdentify( - String groupType, - String groupName, - Identify groupIdentify, - // ignore: non_constant_identifier_names - Function? opt_callback, - // ignore: non_constant_identifier_names - Function? opt_error_callback, - bool? outOfSession); - external bool setOffline(bool enabled); - external Options options; -} - -@JS('amplitude.Identify') -class Identify { - external Identify(); - external void add(String key, dynamic value); - external void append(String key, dynamic value); - external void prepend(String key, dynamic value); - external void set(String key, dynamic value); - external void setOnce(String key, dynamic value); - external void unset(String key); - external void preInsert(String key, dynamic value); - external void postInsert(String key, dynamic value); - external void remove(String key, dynamic value); - external void clearAll(); +extension type Amplitude(JSObject _) implements JSObject { + external JSPromise init(String apiKey, JSObject? configuration); + external void add(JSObject plugin); + external void track(JSObject event); + external void setUserId(JSString userId); + external void setDeviceId(JSString devideId); + external void setOptOut(bool enabled); + external void reset(); + external void flush(); } diff --git a/lib/web/flutter_library_plugin.dart b/lib/web/flutter_library_plugin.dart new file mode 100644 index 0000000..81e3cb1 --- /dev/null +++ b/lib/web/flutter_library_plugin.dart @@ -0,0 +1,21 @@ +import 'dart:js_interop'; +import 'dart:js_interop_unsafe'; + +var libraryJSKey = 'library'.toJS; + +@JSExport() +class FlutterLibraryPlugin { + String library = 'amplitude-flutter/unknown'; + String name = 'FlutterLibraryPlugin'; + + JSObject execute(JSObject event) { + event.hasProperty('library'.toJS); + if (!event.hasProperty(libraryJSKey).toDart) { + event.setProperty(libraryJSKey, library.toJS); + } else { + event.setProperty(libraryJSKey, '${library}_${event.getProperty(libraryJSKey)}'.toJS); + } + return event; + } + FlutterLibraryPlugin(this.library); +} diff --git a/pubspec.yaml b/pubspec.yaml index e488949..880d282 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -6,7 +6,7 @@ repository: https://github.com/amplitude/Amplitude-Flutter environment: - sdk: ">=2.15.0 <3.0.0" + sdk: ">=3.3.0 < 4.0.0" flutter: ">=2.0.0" dependencies: flutter: diff --git a/test/amplitude_test.dart b/test/amplitude_test.dart index 377de8f..c5151b7 100644 --- a/test/amplitude_test.dart +++ b/test/amplitude_test.dart @@ -82,6 +82,19 @@ void main() { 'useAdvertisingIdForDeviceId': false, 'useAppSetIdForDeviceId': false, 'appVersion': null, + 'deviceId': null, + 'cookieOptions': { + 'domain': '', + 'expiration': 365, + 'sameSite': 'Lax', + 'secure': false, + 'upgrade': true, + }, + 'identityStorage': 'cookie', + 'sessionTimeout': 30 * 60 * 1000, + 'userId': null, + 'transport': 'fetch', + 'fetchRemoteConfig': false, // This field doesn't belong to Configuration // Pass it for FlutterLibraryPlugin 'library': '${Constants.packageName}/${Constants.packageVersion}' diff --git a/test/configuration_test.dart b/test/configuration_test.dart index 2357c44..e9e5a54 100644 --- a/test/configuration_test.dart +++ b/test/configuration_test.dart @@ -3,6 +3,7 @@ import 'package:amplitude_flutter/constants.dart'; import 'package:amplitude_flutter/configuration.dart'; import 'package:amplitude_flutter/tracking_options.dart'; import 'package:amplitude_flutter/default_tracking.dart'; +import 'package:amplitude_flutter/cookie_options.dart'; void main() { group('Configuration', () { @@ -21,9 +22,23 @@ void main() { expect(config.useBatch, false); expect(config.serverZone, ServerZone.us); expect(config.serverUrl, isNull); - expect(config.minTimeBetweenSessionsMillis, Constants.minTimeBetweenSessionsMillis); - expect(config.defaultTracking, isA()); + expect(config.minTimeBetweenSessionsMillis, Constants.minTimeBetweenSessionsMillisUnset); expect(config.trackingOptions, isA()); + expect(config.defaultTracking, isA()); + expect(config.enableCoppaControl, false); + expect(config.flushEventsOnClose, true); + expect(config.identifyBatchIntervalMillis, Constants.identifyBatchIntervalMillis); + expect(config.migrateLegacyData, true); + expect(config.deviceId, isNull); + expect(config.locationListening, true); + expect(config.useAdvertisingIdForDeviceId, false); + expect(config.useAppSetIdForDeviceId, false); + expect(config.appVersion, isNull); + expect(config.cookieOptions, isA()); + expect(config.identityStorage, 'cookie'); + expect(config.userId, isNull); + expect(config.transport, 'fetch'); + expect(config.fetchRemoteConfig, false); var map = config.toMap(); expect(map['apiKey'], 'test_api_key'); @@ -38,9 +53,24 @@ void main() { expect(map['useBatch'], false); expect(map['serverZone'], 'us'); expect(map['serverUrl'], isNull); - expect(map['minTimeBetweenSessionsMillis'], Constants.minTimeBetweenSessionsMillis); - expect(map.containsKey('defaultTracking'), true); + expect(map['minTimeBetweenSessionsMillis'], Constants.minTimeBetweenSessionsMillisForMobile); + expect(map['sessionTimeout'], Constants.minTimeBetweenSessionsMillisForWeb); expect(map.containsKey('trackingOptions'), true); + expect(map.containsKey('defaultTracking'), true); + expect(map['enableCoppaControl'], false); + expect(map['flushEventsOnClose'], true); + expect(map['identifyBatchIntervalMillis'], Constants.identifyBatchIntervalMillis); + expect(map['migrateLegacyData'], true); + expect(map['deviceId'], isNull); + expect(map['locationListening'], true); + expect(map['useAdvertisingIdForDeviceId'], false); + expect(map['useAppSetIdForDeviceId'], false); + expect(map['appVersion'], isNull); + expect(map.containsKey('cookieOptions'), true); + expect(map['identityStorage'], 'cookie'); + expect(map['userId'], isNull); + expect(map['transport'], 'fetch'); + expect(map['fetchRemoteConfig'], false); }); test('custom values should be set correctly', () { @@ -58,6 +88,22 @@ void main() { serverZone: ServerZone.eu, serverUrl: 'https://custom.server.url', minTimeBetweenSessionsMillis: 2000, + trackingOptions: TrackingOptions(language: false), + defaultTracking: DefaultTrackingOptions(sessions: false), + enableCoppaControl: true, + flushEventsOnClose: true, + identifyBatchIntervalMillis: 2000, + migrateLegacyData: true, + deviceId: 'custom_device_id', + locationListening: true, + useAdvertisingIdForDeviceId: true, + useAppSetIdForDeviceId: true, + appVersion: '1.0.0', + cookieOptions: CookieOptions(domain: 'custom.domain.com'), + identityStorage: 'localStorage', + userId: 'custom_user_id', + transport: 'xhr', + fetchRemoteConfig: true, ); expect(customConfig.apiKey, 'custom_api_key'); @@ -72,6 +118,22 @@ void main() { expect(customConfig.serverZone, ServerZone.eu); expect(customConfig.serverUrl, 'https://custom.server.url'); expect(customConfig.minTimeBetweenSessionsMillis, 2000); + expect(customConfig.trackingOptions.language, false); + expect(customConfig.defaultTracking.sessions, false); + expect(customConfig.enableCoppaControl, true); + expect(customConfig.flushEventsOnClose, true); + expect(customConfig.identifyBatchIntervalMillis, 2000); + expect(customConfig.migrateLegacyData, true); + expect(customConfig.deviceId, 'custom_device_id'); + expect(customConfig.locationListening, true); + expect(customConfig.useAdvertisingIdForDeviceId, true); + expect(customConfig.useAppSetIdForDeviceId, true); + expect(customConfig.appVersion, '1.0.0'); + expect(customConfig.cookieOptions.domain, 'custom.domain.com'); + expect(customConfig.identityStorage, 'localStorage'); + expect(customConfig.userId, 'custom_user_id'); + expect(customConfig.transport, 'xhr'); + expect(customConfig.fetchRemoteConfig, true); }); }); }