diff --git a/.metadata b/.metadata index 4129ce95..b05acbb3 100644 --- a/.metadata +++ b/.metadata @@ -1,10 +1,36 @@ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # -# This file should be version controlled and should not be manually edited. +# This file should be version controlled. version: - revision: 84f3d28555368a70270e9ac8390a9441df95e752 + revision: 2ad6cd72c040113b47ee9055e722606a490ef0da channel: stable project_type: plugin + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da + base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da + - platform: android + create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da + base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da + - platform: ios + create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da + base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da + - platform: web + create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da + base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/analysis_options.yaml b/analysis_options.yaml index 252a174a..1093cbfd 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -11,6 +11,10 @@ analyzer: strict-inference: true strict-raw-types: true + # TODO: remove this once it is no longer an experiment + enable-experiment: + - inline-class + linter: rules: always_declare_return_types: true diff --git a/example/android/app/src/main/res/drawable-v21/launch_background.xml b/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 00000000..f74085f3 --- /dev/null +++ b/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/android/app/src/main/res/values-night/styles.xml b/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 00000000..06952be7 --- /dev/null +++ b/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/example/lib/main.dart b/example/lib/main.dart index e9ffcb71..56aab2bd 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -17,8 +17,7 @@ void main() async { // Demonstrate explicit initialization before calling `runApp()`, // using the configuration for the example app. - const String exampleAppApiKey = - 'YOUR-API-KEY-HERE'; + const String exampleAppApiKey = 'YOUR-API-KEY-HERE'; const String exampleAppApiDomain = 'us1.gigya.com'; try { diff --git a/example/lib/routes/account_information_page.dart b/example/lib/routes/account_information_page.dart index df9462a8..f200f9f4 100644 --- a/example/lib/routes/account_information_page.dart +++ b/example/lib/routes/account_information_page.dart @@ -238,9 +238,9 @@ class _AccountInformationPageState extends State { case ConnectionState.active: case ConnectionState.none: case ConnectionState.waiting: - return Column( + return const Column( mainAxisAlignment: MainAxisAlignment.center, - children: const [ + children: [ CircularProgressIndicator(), Text('Fetching account...'), ], diff --git a/example/lib/routes/home_page.dart b/example/lib/routes/home_page.dart index c0e2acb3..381db7af 100644 --- a/example/lib/routes/home_page.dart +++ b/example/lib/routes/home_page.dart @@ -192,9 +192,9 @@ class _HomePageState extends State { case ConnectionState.none: case ConnectionState.active: case ConnectionState.waiting: - return Column( + return const Column( mainAxisAlignment: MainAxisAlignment.center, - children: const [ + children: [ CircularProgressIndicator(), Padding( padding: EdgeInsets.all(8), diff --git a/example/lib/routes/login_with_credentials_page.dart b/example/lib/routes/login_with_credentials_page.dart index c7827dca..d88c327f 100644 --- a/example/lib/routes/login_with_credentials_page.dart +++ b/example/lib/routes/login_with_credentials_page.dart @@ -66,7 +66,7 @@ class _LoginWithCredentialsPageState extends State { if (mounted) { setState(() { _inProgress = false; - _requestResult = 'Login success: \n\n ${account.uid}'; + _requestResult = 'Login success: \n\n ${account.toJson()}'; }); } } @@ -112,15 +112,15 @@ class _LoginWithCredentialsPageState extends State { } void _resolveLinkAccount(LinkAccountResolver resolver) async { - final ConflictingAccounts? conflictingAccounts = - await resolver.conflictingAccounts; + final ConflictingAccount? conflictingAccount = + await resolver.conflictingAccount; - if (!mounted || conflictingAccounts == null) { + if (!mounted || conflictingAccount == null) { return; } - if (conflictingAccounts.loginProviders.contains('site')) { - _showLinkToSiteBottomSheet(conflictingAccounts.loginID, resolver); + if (conflictingAccount.loginProviders.contains('site')) { + _showLinkToSiteBottomSheet(conflictingAccount.loginID, resolver); } else { _showLinkToSocialBottomSheet(resolver); } @@ -234,6 +234,7 @@ class _LoginWithCredentialsPageState extends State { ), TextField( controller: _linkPasswordController, + obscureText: true, decoration: const InputDecoration(hintText: 'password'), ), Padding( @@ -351,6 +352,7 @@ class _LoginWithCredentialsPageState extends State { padding: const EdgeInsets.all(8.0), child: TextFormField( controller: _passwordController, + obscureText: true, decoration: const InputDecoration(hintText: 'Enter password'), validator: (String? value) { if (value == null || value.trim().isEmpty) { diff --git a/example/lib/routes/manage_connections_page.dart b/example/lib/routes/manage_connections_page.dart index 581e913f..74bf268e 100644 --- a/example/lib/routes/manage_connections_page.dart +++ b/example/lib/routes/manage_connections_page.dart @@ -79,9 +79,9 @@ class _ManageConnectionsPageState extends State { case ConnectionState.none: case ConnectionState.active: case ConnectionState.waiting: - return Column( + return const Column( mainAxisAlignment: MainAxisAlignment.center, - children: const [ + children: [ CircularProgressIndicator(), Text('Fetching account...'), ], @@ -109,19 +109,23 @@ class _ManageConnectionsPageState extends State { } final Account account = snapshot.data!; - final String socialProviders = account.socialProviders ?? ''; return Column( children: [ const Text('Social connections for this account'), Expanded( - child: socialProviders.isEmpty + child: account.socialProviders.isEmpty ? const Center( child: Text( 'No social connections for this account', ), ) - : Text(socialProviders), + : ListView.builder( + itemBuilder: (_, int index) => Text( + account.socialProviders[index], + ), + itemCount: account.socialProviders.length, + ), ), Padding( padding: const EdgeInsets.symmetric(vertical: 16), diff --git a/example/pubspec.lock b/example/pubspec.lock index 1e32caed..768b558e 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -1,6 +1,30 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a + url: "https://pub.dev" + source: hosted + version: "61.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 + url: "https://pub.dev" + source: hosted + version: "5.13.0" + args: + dependency: transitive + description: + name: args + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + url: "https://pub.dev" + source: hosted + version: "2.4.2" async: dependency: transitive description: @@ -37,10 +61,26 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "3.0.3" fake_async: dependency: transitive description: @@ -49,6 +89,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" flutter: dependency: "direct main" description: flutter @@ -82,10 +130,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.3" flutter_test: dependency: "direct dev" description: flutter @@ -102,7 +150,15 @@ packages: path: ".." relative: true source: path - version: "1.0.0" + version: "1.0.1" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" google_sign_in: dependency: "direct main" description: @@ -115,26 +171,26 @@ packages: dependency: transitive description: name: google_sign_in_android - sha256: "2a8b90b766ce00b03e7543f4ffeec97b6eb51fb6c3f31ce2a364bd1f1b9dd7fc" + sha256: "6031f59074a337fdd81be821aba84cee3a41338c6e958499a5cd34d3e1db80ef" url: "https://pub.dev" source: hosted - version: "6.1.14" + version: "6.1.20" google_sign_in_ios: dependency: transitive description: name: google_sign_in_ios - sha256: "6ec0e13a4c5c646471b9f6a25ceb3ae76d339889d4c0f79b729bf0714215a63e" + sha256: "974944859f9cd40eb8a15b3fe8efb2d47fb7e99438f763f61a1ccd28d74ff4ce" url: "https://pub.dev" source: hosted - version: "5.6.2" + version: "5.6.4" google_sign_in_platform_interface: dependency: transitive description: name: google_sign_in_platform_interface - sha256: e69553c0fc6a76216e9d06a8c3767e291ad9be42171f879aab7ab708569d4393 + sha256: "35ceee5f0eadc1c07b0b4af7553246e315c901facbb7d3dadf734ba2693ceec4" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" google_sign_in_web: dependency: transitive description: @@ -155,34 +211,42 @@ packages: dependency: transitive description: name: lints - sha256: "6b0206b0bf4f04961fc5438198ccb3a885685cd67d4d4a32cc20ad7f8adbe015" + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" matcher: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" path: dependency: transitive description: @@ -191,11 +255,27 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.3" + pigeon: + dependency: transitive + description: + name: pigeon + sha256: "5a79fd0b10423f6b5705525e32015597f861c31220b522a67d1e6b580da96719" + url: "https://pub.dev" + source: hosted + version: "11.0.1" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc" + sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d + url: "https://pub.dev" + source: hosted + version: "2.1.6" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" url: "https://pub.dev" source: hosted version: "2.1.4" @@ -216,26 +296,26 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" string_scanner: dependency: transitive description: @@ -256,10 +336,18 @@ packages: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + url: "https://pub.dev" + source: hosted + version: "0.6.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "1.3.2" vector_math: dependency: transitive description: @@ -268,6 +356,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: "14f1f70c51119012600c5f1f60ca68efda5a9b6077748163c6af2893ec5df8fc" + url: "https://pub.dev" + source: hosted + version: "0.2.1-beta" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" sdks: - dart: ">=3.0.0-417 <4.0.0" - flutter: ">=3.3.0" + dart: ">=3.2.0-157.0.dev <4.0.0" + flutter: ">=3.10.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 9f4dd951..51cc89b2 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -5,7 +5,8 @@ homepage: https://www.sap.com publish_to: none environment: - sdk: ">=2.18.0 <4.0.0" + sdk: ">=3.0.0 <4.0.0" + flutter: ">=3.10.0" dependencies: flutter: diff --git a/example/web/favicon.png b/example/web/favicon.png new file mode 100644 index 00000000..8aaa46ac Binary files /dev/null and b/example/web/favicon.png differ diff --git a/example/web/icons/Icon-192.png b/example/web/icons/Icon-192.png new file mode 100644 index 00000000..b749bfef Binary files /dev/null and b/example/web/icons/Icon-192.png differ diff --git a/example/web/icons/Icon-512.png b/example/web/icons/Icon-512.png new file mode 100644 index 00000000..88cfd48d Binary files /dev/null and b/example/web/icons/Icon-512.png differ diff --git a/example/web/icons/Icon-maskable-192.png b/example/web/icons/Icon-maskable-192.png new file mode 100644 index 00000000..eb9b4d76 Binary files /dev/null and b/example/web/icons/Icon-maskable-192.png differ diff --git a/example/web/icons/Icon-maskable-512.png b/example/web/icons/Icon-maskable-512.png new file mode 100644 index 00000000..d69c5669 Binary files /dev/null and b/example/web/icons/Icon-maskable-512.png differ diff --git a/example/web/index.html b/example/web/index.html new file mode 100644 index 00000000..8ccf713b --- /dev/null +++ b/example/web/index.html @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + gigya_flutter_plugin_example + + + + + + + + + + diff --git a/example/web/manifest.json b/example/web/manifest.json new file mode 100644 index 00000000..7db4a594 --- /dev/null +++ b/example/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "gigya_flutter_plugin_example", + "short_name": "gigya_flutter_plugin_example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "Demonstrates how to use the gigya_flutter_plugin plugin.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/lib/gigya_flutter_plugin.dart b/lib/gigya_flutter_plugin.dart index 074f9205..1dfc6dc1 100644 --- a/lib/gigya_flutter_plugin.dart +++ b/lib/gigya_flutter_plugin.dart @@ -1,14 +1,14 @@ import 'src/models/enums/social_provider.dart'; import 'src/models/screenset_event.dart'; import 'src/platform_interface/gigya_flutter_plugin_platform_interface.dart'; -import 'src/services/interruption_resolver/interruption_resolver.dart'; -import 'src/services/otp_service/otp_service.dart'; -import 'src/services/web_authentication_service/web_authentication_service.dart'; +import 'src/services/interruption_resolver.dart'; +import 'src/services/otp_service.dart'; +import 'src/services/web_authentication_service.dart'; export 'src/models/account.dart'; export 'src/models/address.dart'; export 'src/models/certification.dart'; -export 'src/models/conflicting_accounts.dart'; +export 'src/models/conflicting_account.dart'; export 'src/models/education.dart'; export 'src/models/emails.dart'; export 'src/models/enums/screen_set_event_type.dart'; @@ -26,9 +26,9 @@ export 'src/models/screenset_event.dart'; export 'src/models/session_info.dart'; export 'src/models/skill.dart'; export 'src/models/work.dart'; -export 'src/services/interruption_resolver/interruption_resolver.dart'; -export 'src/services/otp_service/otp_service.dart'; -export 'src/services/web_authentication_service/web_authentication_service.dart'; +export 'src/services/interruption_resolver.dart'; +export 'src/services/otp_service.dart'; +export 'src/services/web_authentication_service.dart'; /// This class represents the Gigya SDK plugin. class GigyaSdk { diff --git a/lib/src/platform_interface/gigya_flutter_plugin_method_channel.dart b/lib/src/method_channel/gigya_flutter_plugin_method_channel.dart similarity index 89% rename from lib/src/platform_interface/gigya_flutter_plugin_method_channel.dart rename to lib/src/method_channel/gigya_flutter_plugin_method_channel.dart index 278f481b..280489ea 100644 --- a/lib/src/platform_interface/gigya_flutter_plugin_method_channel.dart +++ b/lib/src/method_channel/gigya_flutter_plugin_method_channel.dart @@ -7,13 +7,13 @@ import '../models/enums/methods.dart'; import '../models/enums/social_provider.dart'; import '../models/gigya_error.dart'; import '../models/screenset_event.dart'; -import '../services/interruption_resolver/interruption_resolver.dart'; -import '../services/interruption_resolver/method_channel_interruption_resolver.dart'; -import '../services/otp_service/method_channel_otp_service.dart'; -import '../services/otp_service/otp_service.dart'; -import '../services/web_authentication_service/method_channel_web_authentication_service.dart'; -import '../services/web_authentication_service/web_authentication_service.dart'; -import 'gigya_flutter_plugin_platform_interface.dart'; +import '../platform_interface/gigya_flutter_plugin_platform_interface.dart'; +import '../services/interruption_resolver.dart'; +import '../services/otp_service.dart'; +import '../services/web_authentication_service.dart'; +import 'method_channel_interruption_resolver.dart'; +import 'method_channel_otp_service.dart'; +import 'method_channel_web_authentication_service.dart'; /// An implementation of [GigyaFlutterPluginPlatform] that uses method channels. class MethodChannelGigyaFlutterPlugin extends GigyaFlutterPluginPlatform { @@ -122,7 +122,7 @@ class MethodChannelGigyaFlutterPlugin extends GigyaFlutterPluginPlatform { Future initSdk({ required String apiDomain, required String apiKey, - bool forceLogout = true, + bool forceLogout = false, }) async { // First, initialize the Gigya SDK. try { @@ -350,26 +350,12 @@ class MethodChannelGigyaFlutterPlugin extends GigyaFlutterPluginPlatform { throw GigyaError.fromPlatformException(exception); } - yield* screenSetEvents.receiveBroadcastStream().map((dynamic event) { - // The binary messenger sends things back as `dynamic`. - // If the event is a `Map`, - // it does not have type information and comes back as `Map`. - // Cast it using `Map.cast()` to at least recover the type of the key. - // The values are still `Object?`, though. - final Map typedEvent = - (event as Map).cast(); - - // Now grab the data of the event, - // using `Map.cast()` to recover the type of the keys. - final Map? data = - (typedEvent['data'] as Map?) - ?.cast(); - - return ScreensetEvent( - typedEvent['event'] as String, - data ?? {}, - ); - }); + // The binary messenger sends things back as `dynamic`, + // but the events are actually a `Map`. + yield* screenSetEvents + .receiveBroadcastStream() + .cast>() + .map(ScreensetEvent.fromMap); } @override diff --git a/lib/src/services/interruption_resolver/method_channel_interruption_resolver.dart b/lib/src/method_channel/method_channel_interruption_resolver.dart similarity index 83% rename from lib/src/services/interruption_resolver/method_channel_interruption_resolver.dart rename to lib/src/method_channel/method_channel_interruption_resolver.dart index ed50eb9b..8af6d252 100644 --- a/lib/src/services/interruption_resolver/method_channel_interruption_resolver.dart +++ b/lib/src/method_channel/method_channel_interruption_resolver.dart @@ -1,10 +1,10 @@ import 'package:flutter/services.dart' show MethodChannel, PlatformException; -import '../../models/conflicting_accounts.dart'; -import '../../models/enums/methods.dart'; -import '../../models/enums/social_provider.dart'; -import '../../models/gigya_error.dart'; -import 'interruption_resolver.dart'; +import '../models/conflicting_account.dart'; +import '../models/enums/methods.dart'; +import '../models/enums/social_provider.dart'; +import '../models/gigya_error.dart'; +import '../services/interruption_resolver.dart'; /// This class represents an [InterruptionResolver] that uses a [MethodChannel] /// for its implementation. @@ -32,25 +32,25 @@ class MethodChannelInterruptionResolverFactory class _MethodChannelLinkAccountResolver extends LinkAccountResolver { _MethodChannelLinkAccountResolver(this._channel) { - _conflictingAccounts = _getConflictingAccounts(); + _conflictingAccount = _getConflictingAccount(); } final MethodChannel _channel; - late final Future? _conflictingAccounts; + late final Future? _conflictingAccount; @override - Future? get conflictingAccounts => _conflictingAccounts; + Future? get conflictingAccount => _conflictingAccount; - /// Get the conflicting accounts for the user. - Future _getConflictingAccounts() async { + /// Get the conflicting account for the user. + Future _getConflictingAccount() async { try { final Map? result = await _channel.invokeMapMethod( Methods.getConflictingAccounts.methodName, ); - return ConflictingAccounts.fromJson(result ?? const {}); + return ConflictingAccount.fromJson(result ?? const {}); } on PlatformException catch (exception) { throw GigyaError.fromPlatformException(exception); } diff --git a/lib/src/services/otp_service/method_channel_otp_service.dart b/lib/src/method_channel/method_channel_otp_service.dart similarity index 95% rename from lib/src/services/otp_service/method_channel_otp_service.dart rename to lib/src/method_channel/method_channel_otp_service.dart index 1bc03122..e57155b4 100644 --- a/lib/src/services/otp_service/method_channel_otp_service.dart +++ b/lib/src/method_channel/method_channel_otp_service.dart @@ -1,8 +1,8 @@ import 'package:flutter/services.dart' show MethodChannel, PlatformException; -import '../../models/enums/methods.dart'; -import '../../models/gigya_error.dart'; -import 'otp_service.dart'; +import '../models/enums/methods.dart'; +import '../models/gigya_error.dart'; +import '../services/otp_service.dart'; /// This class represents an [OtpService] that uses a [MethodChannel] /// for its implementation. diff --git a/lib/src/services/web_authentication_service/method_channel_web_authentication_service.dart b/lib/src/method_channel/method_channel_web_authentication_service.dart similarity index 90% rename from lib/src/services/web_authentication_service/method_channel_web_authentication_service.dart rename to lib/src/method_channel/method_channel_web_authentication_service.dart index 4b905a0d..5f0f40ad 100644 --- a/lib/src/services/web_authentication_service/method_channel_web_authentication_service.dart +++ b/lib/src/method_channel/method_channel_web_authentication_service.dart @@ -1,9 +1,9 @@ import 'dart:convert'; import 'package:flutter/services.dart' show MethodChannel, PlatformException; -import 'package:gigya_flutter_plugin/gigya_flutter_plugin.dart'; -import '../../models/enums/methods.dart'; +import '../../gigya_flutter_plugin.dart'; +import '../models/enums/methods.dart'; /// This class represents a [WebAuthenticationService] that uses a [MethodChannel] /// for its implementation. @@ -16,8 +16,7 @@ class MethodChannelWebAuthenticationService extends WebAuthenticationService { @override Future> login() async { try { - final Map? result = - await _channel.invokeMapMethod( + final Map? result = await _channel.invokeMapMethod( WebAuthnMethods.login.methodName, {}, ).timeout( diff --git a/lib/src/models/account.dart b/lib/src/models/account.dart index 159a196d..406bc386 100644 --- a/lib/src/models/account.dart +++ b/lib/src/models/account.dart @@ -6,60 +6,84 @@ import 'session_info.dart'; class Account { /// The default constructor. Account({ - required this.emails, this.created, - this.createdTimestamp, + this.data = const {}, + this.emails = const Emails(), this.isActive, this.isRegistered, this.isVerified, - this.lastLoginTimestamp, - this.lastUpdatedTimestamp, + this.lastLogin, + this.lastUpdated, this.loginProvider, - this.oldestUpdateTimestamp, + this.oldestDataUpdated, this.profile, this.registered, - this.registeredTimestamp, + this.sessionInfo, this.signatureTimestamp, - this.socialProviders, + this.socialProviders = const [], this.uid, this.uidSignature, this.verified, - this.verifiedTimestamp, }); /// Construct an account from the given [json]. factory Account.fromJson(Map json) { - final Map? emails = json['emails'] == null ? null : json['emails'].cast() as Map; - final Map? profile = json['profile'] == null ? null : json['profile'].cast() as Map; + final String? created = json['created'] as String?; + final Map? emails = + (json['emails'] as Map?)?.cast(); + final Map? profile = + (json['profile'] as Map?)?.cast(); + final String? lastLogin = json['lastLogin'] as String?; + final String? lastUpdated = json['lastUpdated'] as String?; + final String? oldestDataUpdated = json['oldestDataUpdated'] as String?; + final String? registered = json['registered'] as String?; + final String? verified = json['verified'] as String?; + final Map? session = + (json['sessionInfo'] as Map?) + ?.cast(); + final String socialProviders = json['socialProviders'] as String? ?? ''; + + // The signature timestamp can be either an ISO 8601 date, + // or the number of seconds since the UNIX epoch (1 Jan 1970). + final DateTime? signatureTimestamp = switch (json['signatureTimestamp']) { + final String timestamp => DateTime.parse(timestamp), + final int timestamp => + DateTime.fromMillisecondsSinceEpoch(timestamp * 1000), + final double timestamp => + DateTime.fromMillisecondsSinceEpoch(timestamp.toInt() * 1000), + _ => null, + }; return Account( + created: DateTime.tryParse(created ?? ''), + data: (json['data'] as Map?)?.cast(), emails: emails == null ? const Emails() : Emails.fromJson(emails), - created: json['created'] as String?, - createdTimestamp: json['createdTimestamp'] as Object?, isActive: json['isActive'] as bool?, isRegistered: json['isRegistered'] as bool?, isVerified: json['isVerified'] as bool?, - lastLoginTimestamp: json['lastLoginTimestamp'] as Object?, - lastUpdatedTimestamp: json['lastUpdatedTimestamp'] as Object?, + lastLogin: DateTime.tryParse(lastLogin ?? ''), + lastUpdated: DateTime.tryParse(lastUpdated ?? ''), loginProvider: json['loginProvider'] as String?, - oldestUpdateTimestamp: json['oldestDataUpdatedTimestamp'] as Object?, + oldestDataUpdated: DateTime.tryParse(oldestDataUpdated ?? ''), profile: profile == null ? null : Profile.fromJson(profile), - registered: json['registered'] as String?, - registeredTimestamp: json['registeredTimestamp'] as Object?, - signatureTimestamp: json['signatureTimestamp'] as Object?, - socialProviders: json['socialProviders'] as String?, + registered: DateTime.tryParse(registered ?? ''), + sessionInfo: session == null ? null : SessionInfo.fromJson(session), + signatureTimestamp: signatureTimestamp, + socialProviders: socialProviders.split(','), uid: json['UID'] as String?, uidSignature: json['UIDSignature'] as String?, - verified: json['verified'] as String?, - verifiedTimestamp: json['verifiedTimestamp'] as Object?, + verified: DateTime.tryParse(verified ?? ''), ); } - /// The created status of the account. - final String? created; + /// The timestamp, in UTC, on which the account was created. + final DateTime? created; + + /// The custom data for the account, which is not part of the [profile]. + final Map? data; - /// The creation timestamp of the account. - final Object? createdTimestamp; // TODO: this should be a `DateTime?`. + /// The list of email addresses for the account. + final Emails emails; /// Whether this account is active. final bool? isActive; @@ -70,14 +94,12 @@ class Account { /// Whether this account is verified. final bool? isVerified; - /// The list of email addresses for the account. - final Emails emails; + /// The timestamp of the last login of the user. + final DateTime? lastLogin; - /// The timestamp of the last login. - final Object? lastLoginTimestamp; // TODO: this should be a `DateTime?`. - - /// The timestamp of the last update. - final Object? lastUpdatedTimestamp; // TODO: this should be a `DateTime?`. + /// The timestamp, in UTC, when the user's profile, preferences, + /// or subscriptions data was last updated. + final DateTime? lastUpdated; /// The name of current login provider for this account. /// @@ -85,23 +107,24 @@ class Account { /// then the value of this attribute will be `site`. final String? loginProvider; - /// The timestamp of the oldest data update. - final Object? oldestUpdateTimestamp; // TODO: this should be a `DateTime?`. + /// The timestamp, in UTC, of the oldest update to the user's account data. + final DateTime? oldestDataUpdated; /// The profile linked to this account. final Profile? profile; - /// The registered status of the account. - final String? registered; + /// The timestamp, in UTC, when the user was registered. + final DateTime? registered; - /// The timestamp of the account registration. - final Object? registeredTimestamp; // TODO: this should be a `DateTime?`. + /// The session info for the account. + final SessionInfo? sessionInfo; - /// The timestamp of the [uidSignature]. - final Object? signatureTimestamp; // TODO: this should be a `DateTime?`. + /// The timestamp of the [uidSignature], in UTC. + /// This signature should be used for login validation. + final DateTime? signatureTimestamp; /// The social providers linked to this account. - final String? socialProviders; // TODO: This should be a `List` + final List socialProviders; /// The UID of the account. final String? uid; @@ -109,36 +132,32 @@ class Account { /// The UID signature of the account. final String? uidSignature; - /// The verified status of this account. - final String? verified; - - /// The timestamp of the [verified] status. - final Object? verifiedTimestamp; // TODO: this should be a `DateTime?`. + /// The timestamp, in UTC, when the user was verified. + final DateTime? verified; /// Convert this object into a JSON object. Map toJson() { final Profile? accountProfile = profile; return { - 'created': created, - 'createdTimestamp': createdTimestamp?.toString(), + 'created': created?.toIso8601String(), + 'data': data, 'emails': emails.toJson(), 'isActive': isActive, 'isRegistered': isRegistered, 'isVerified': isVerified, - 'lastLoginTimestamp': lastLoginTimestamp?.toString(), - 'lastUpdatedTimestamp': lastUpdatedTimestamp?.toString(), + 'lastLogin': lastLogin?.toIso8601String(), + 'lastUpdated': lastUpdated?.toIso8601String(), 'loginProvider': loginProvider, - 'oldestDataUpdatedTimestamp': oldestUpdateTimestamp?.toString(), + 'oldestDataUpdated': oldestDataUpdated?.toIso8601String(), if (accountProfile != null) 'profile': accountProfile.toJson(), - 'registered': registered, - 'registeredTimestamp': registeredTimestamp?.toString(), - 'signatureTimestamp': signatureTimestamp?.toString(), + 'registered': registered?.toIso8601String(), + if (sessionInfo != null) 'sessionInfo': sessionInfo!.toJson(), + 'signatureTimestamp': signatureTimestamp?.toIso8601String(), 'socialProviders': socialProviders, 'UID': uid, 'UIDSignature': uidSignature, - 'verified': verified, - 'verifiedTimestamp': verifiedTimestamp?.toString(), + 'verified': verified?.toIso8601String(), }; } } diff --git a/lib/src/models/certification.dart b/lib/src/models/certification.dart index 03d4c3bc..c5ebe554 100644 --- a/lib/src/models/certification.dart +++ b/lib/src/models/certification.dart @@ -13,10 +13,10 @@ class Certification { factory Certification.fromJson(Map json) { return Certification._( authority: json['authority'] as String?, - endDate: json['endDate'] as String?, + endDate: DateTime.tryParse(json['endDate'] as String? ?? ''), name: json['name'] as String?, number: json['number'] as String?, - startDate: json['startDate'] as String?, + startDate: DateTime.tryParse(json['startDate'] as String? ?? ''), ); } @@ -24,7 +24,7 @@ class Certification { final String? authority; /// The end date of the validity of the certification. - final String? endDate; // TODO: this should be a DateTime? + final DateTime? endDate; /// The name of the certification. final String? name; @@ -33,16 +33,16 @@ class Certification { final String? number; /// The start date of the validity of the certification. - final String? startDate; // TODO: this should be a DateTime? + final DateTime? startDate; /// Convert this object to a JSON object. Map toJson() { return { 'authority': authority, - 'endDate': endDate, + 'endDate': endDate?.toIso8601String(), 'name': name, 'number': number, - 'startDate': startDate, + 'startDate': startDate?.toIso8601String(), }; } } diff --git a/lib/src/models/conflicting_accounts.dart b/lib/src/models/conflicting_account.dart similarity index 56% rename from lib/src/models/conflicting_accounts.dart rename to lib/src/models/conflicting_account.dart index add986fa..1244d3d7 100644 --- a/lib/src/models/conflicting_accounts.dart +++ b/lib/src/models/conflicting_account.dart @@ -1,16 +1,19 @@ /// This class represents a model that is used to resolve an account conflict. -class ConflictingAccounts { - /// The private constructor. - const ConflictingAccounts._(this.loginID, this.loginProviders); +class ConflictingAccount { + /// The default constructor. + const ConflictingAccount({ + this.loginID, + this.loginProviders = const [], + }); /// The default constructor. - factory ConflictingAccounts.fromJson(Map json) { + factory ConflictingAccount.fromJson(Map json) { // Lists coming from `jsonDecode` always have dynamic as type. final List? providers = json['loginProviders'] as List?; - return ConflictingAccounts._( - json['loginID'] as String?, - providers?.cast() ?? [], + return ConflictingAccount( + loginID: json['loginID'] as String?, + loginProviders: providers?.cast() ?? [], ); } diff --git a/lib/src/models/favorite.dart b/lib/src/models/favorite.dart index ef7ec437..5a954015 100644 --- a/lib/src/models/favorite.dart +++ b/lib/src/models/favorite.dart @@ -103,6 +103,7 @@ class Favorites { 'books': books.map((Favorite b) => b.toJson()).toList(), 'interests': interests.map((Favorite i) => i.toJson()).toList(), 'movies': movies.map((Favorite m) => m.toJson()).toList(), + 'music': music.map((Favorite m) => m.toJson()).toList(), 'television': television.map((Favorite t) => t.toJson()).toList(), }; } diff --git a/lib/src/models/like.dart b/lib/src/models/like.dart index ca9df63c..dc54c11c 100644 --- a/lib/src/models/like.dart +++ b/lib/src/models/like.dart @@ -6,7 +6,6 @@ class Like { this.id, this.name, this.time, - this.timestamp, }); /// The default constructor. @@ -15,8 +14,7 @@ class Like { category: json['category'] as String?, id: json['id'] as String?, name: json['name'] as String?, - time: json['time'] as String?, - timestamp: json['timestamp'] as double?, + time: DateTime.tryParse(json['time'] as String? ?? ''), ); } @@ -29,11 +27,8 @@ class Like { /// The name of the like. final String? name; - /// The formatted time of the like. - final String? time; // TODO: this parameter is redundant, timestamp is enough? - /// The timestamp of the like. - final double? timestamp; // TODO: this should be a `DateTime?` + final DateTime? time; /// Convert this object into a JSON object. Map toJson() { @@ -41,8 +36,7 @@ class Like { 'category': category, 'id': id, 'name': name, - 'time': time, - 'timestamp': timestamp, + 'time': time?.toIso8601String(), }; } } diff --git a/lib/src/models/oidc_data.dart b/lib/src/models/oidc_data.dart index 22c5a451..d7787627 100644 --- a/lib/src/models/oidc_data.dart +++ b/lib/src/models/oidc_data.dart @@ -76,7 +76,7 @@ class OidcData { 'name': name, 'phone_number': phoneNumber, 'phone_number_verified': phoneNumberVerified, - 'updated_at': updatedAt?.toString(), + 'updated_at': updatedAt?.toIso8601String(), 'website': website, 'zoneinfo': zoneInfo, }; diff --git a/lib/src/models/patent.dart b/lib/src/models/patent.dart index d912a626..ef79ec63 100644 --- a/lib/src/models/patent.dart +++ b/lib/src/models/patent.dart @@ -50,7 +50,7 @@ class Patent { /// Convert this object into a JSON object. Map toJson() { return { - 'date': date?.toString(), + 'date': date?.toIso8601String(), 'number': number, 'office': office, 'status': status, diff --git a/lib/src/models/profile.dart b/lib/src/models/profile.dart index 6e273c67..57210f03 100644 --- a/lib/src/models/profile.dart +++ b/lib/src/models/profile.dart @@ -36,11 +36,15 @@ class Profile { this.honors, this.industry, this.interests, + this.isConnected, + this.isSiteUser, this.languages, this.lastLoginLocation, this.lastName, this.likes = const [], this.locale, + this.loginProvider, + this.loginProviderUID, this.name, this.nickname, this.oidcData, @@ -50,6 +54,7 @@ class Profile { this.politicalView, this.professionalHeadline, this.profileUrl, + this.providers = const [], this.proxyEmail, this.publications = const [], this.relationshipStatus, @@ -77,6 +82,7 @@ class Profile { json['oidcData'] as Map?; final List? patents = json['patents'] as List?; final List? phones = json['phones'] as List?; + final List? providers = json['providers'] as List?; final List? publications = json['publications'] as List?; final List? skills = json['skills'] as List?; final List? work = json['work'] as List?; @@ -98,13 +104,15 @@ class Profile { email: json['email'] as String?, favorites: _listFromJson(favorites, Favorite.fromJson), firstName: json['firstName'] as String?, - followers: json['followersCounts'] as int?, + followers: json['followersCount'] as int?, following: json['followingCount'] as int?, gender: json['gender'] as String?, hometown: json['hometown'] as String?, honors: json['honors'] as String?, industry: json['industry'] as String?, interests: json['interests'] as String?, + isConnected: json['isConnected'] as bool?, + isSiteUser: json['isSiteUser'] as bool?, languages: json['languages'] as String?, lastLoginLocation: lastLoginLocation == null ? null @@ -112,6 +120,8 @@ class Profile { lastName: json['lastName'] as String?, likes: _listFromJson(likes, Like.fromJson), locale: json['locale'] as String?, + loginProvider: json['loginProvider'] as String?, + loginProviderUID: json['loginProviderUID'] as String?, name: json['name'] as String?, nickname: json['nickname'] as String?, oidcData: oidcStruct == null ? null : OidcData.fromJson(oidcStruct), @@ -121,6 +131,7 @@ class Profile { politicalView: json['politicalView'] as String?, professionalHeadline: json['professionalHeadline'] as String?, profileUrl: json['profileURL'] as String?, + providers: providers?.cast() ?? const [], proxyEmail: json['proxyEmail'] as String?, publications: _listFromJson(publications, Publication.fromJson), @@ -204,6 +215,12 @@ class Profile { /// The person's interests. final String? interests; + /// Whether the user is connected to any available provider. + final bool? isConnected; + + /// Whether the current user is a user of the site. + final bool? isSiteUser; + /// The different languages that the person is proficient in. final String? languages; @@ -219,6 +236,12 @@ class Profile { /// The language locale of the person's primary language. final String? locale; + /// The name of the provider that the user used in order to log in. + final String? loginProvider; + + /// The user's ID from the login provider. + final String? loginProviderUID; + /// The person's full name. final String? name; @@ -246,6 +269,9 @@ class Profile { /// The url to the person's profile page. final String? profileUrl; + /// The names of the providers to which the user is connected/logged in. + final List providers; + /// The person's proxy email address. final String? proxyEmail; @@ -323,18 +349,22 @@ class Profile { if (favorites.isNotEmpty) 'favorites': favorites.map((Favorite f) => f.toJson()).toList(), 'firstName': firstName, - 'followersCounts': followers, + 'followersCount': followers, 'followingCount': following, 'gender': gender, 'hometown': hometown, 'honors': honors, 'industry': industry, 'interests': interests, + 'isConnected': isConnected, + 'isSiteUser': isSiteUser, 'languages': languages, if (lastLogin != null) 'lastLoginLocation': lastLogin.toJson(), 'lastName': lastName, if (likes.isNotEmpty) 'likes': likes.map((Like l) => l.toJson()).toList(), 'locale': locale, + 'loginProvider': loginProvider, + 'loginProviderUID': loginProviderUID, 'name': name, 'nickname': nickname, if (oidcStruct != null) 'oidcData': oidcStruct.toJson(), @@ -346,6 +376,7 @@ class Profile { 'politicalView': politicalView, 'professionalHeadline': professionalHeadline, 'profileURL': profileUrl, + 'providers': providers, 'proxyEmail': proxyEmail, if (publications.isNotEmpty) 'publications': diff --git a/lib/src/models/publication.dart b/lib/src/models/publication.dart index 06d37062..729e8a29 100644 --- a/lib/src/models/publication.dart +++ b/lib/src/models/publication.dart @@ -40,7 +40,7 @@ class Publication { /// Convert this object to a JSON object. Map toJson() { return { - 'date': date?.toString(), + 'date': date?.toIso8601String(), 'publisher': publisher, 'summary': summary, 'title': title, diff --git a/lib/src/models/screenset_event.dart b/lib/src/models/screenset_event.dart index f4d26951..8929c6ad 100644 --- a/lib/src/models/screenset_event.dart +++ b/lib/src/models/screenset_event.dart @@ -13,6 +13,19 @@ class ScreensetEvent { return ScreensetEvent._(resolvedType, data); } + /// Construct a new [ScreensetEvent] from the given, loosely-typed [map]. + /// + /// The map is expected to have an `event` key, denoting the name of the event. + /// The map can have a `data` key, which is a [Map] that contains any data for the event. + factory ScreensetEvent.fromMap(Map map) { + final Map? data = map['data'] as Map?; + + return ScreensetEvent( + map['event'] as String, + data?.cast() ?? const {}, + ); + } + /// The private constructor. const ScreensetEvent._(this.type, this.data); diff --git a/lib/src/models/session_info.dart b/lib/src/models/session_info.dart index 4dce0f42..7a140a6d 100644 --- a/lib/src/models/session_info.dart +++ b/lib/src/models/session_info.dart @@ -1,21 +1,44 @@ /// This class represents a session info object. +/// +/// When running on native platforms, this session info contains a session token and secret. +/// When running on the web, this session info contains a session cookie. class SessionInfo { /// The default constructor. SessionInfo.fromJson(Map json) - : expiresIn = json['expires_in'] as int, - sessionSecret = json['sessionSecret'] as String, - sessionToken = json['sessionToken'] as String; + : cookieName = json['cookieName'] as String?, + cookieValue = json['cookieValue'] as String?, + expiresIn = json['expires_in'] as int?, + sessionSecret = json['sessionSecret'] as String?, + sessionToken = json['sessionToken'] as String?; + + /// The name of the session cookie. + /// + /// This is null when not running on the web. + final String? cookieName; + + /// The value of the session cookie. + /// + /// This is null when not running on the web. + final String? cookieValue; /// The expiration time of the session, in seconds. - final int expiresIn; + /// + /// This is null when running on the web. + final int? expiresIn; /// The session secret. - final String sessionSecret; + /// + /// This is null when running on the web. + final String? sessionSecret; /// The session token. - final String sessionToken; + /// + /// This is null when running on the web. + final String? sessionToken; /// Convert this object into a JSON object. + /// + /// The session cookie is not serialized when calling this method. Map toJson() { return { 'sessionToken': sessionToken, diff --git a/lib/src/models/work.dart b/lib/src/models/work.dart index 7f48f020..db4728c1 100644 --- a/lib/src/models/work.dart +++ b/lib/src/models/work.dart @@ -16,22 +16,22 @@ class Work { /// The default constructor. factory Work.fromJson(Map json) { - final String? endDate = json['endDate'] as String?; - final String? startDate = json['startDate'] as String?; - - // TODO: company size should be an int, not a double. - final double? companySize = json['companySize'] as double?; + final int? companySize = switch (json['companySize']) { + final int size => size, + final double size => size.toInt(), + _ => null, + }; return Work._( company: json['company'] as String?, companyID: json['companyID'] as String?, - companySize: companySize?.toInt(), + companySize: companySize, description: json['description'] as String?, - endDate: endDate == null ? null : DateTime.tryParse(endDate), + endDate: DateTime.tryParse(json['endDate'] as String? ?? ''), industry: json['industry'] as String?, isCurrent: json['isCurrent'] as bool?, location: json['location'] as String?, - startDate: startDate == null ? null : DateTime.tryParse(startDate), + startDate: DateTime.tryParse(json['startDate'] as String? ?? ''), title: json['title'] as String?, ); } @@ -71,14 +71,13 @@ class Work { return { 'company': company, 'companyID': companyID, - // TODO: company size should be an int, not a double. - 'companySize': companySize?.toDouble(), + 'companySize': companySize, 'description': description, - 'endDate': endDate?.toString(), + 'endDate': endDate?.toIso8601String(), 'industry': industry, 'isCurrent': isCurrent, 'location': location, - 'startDate': startDate?.toString(), + 'startDate': startDate?.toIso8601String(), 'title': title, }; } diff --git a/lib/src/platform_interface/gigya_flutter_plugin_platform_interface.dart b/lib/src/platform_interface/gigya_flutter_plugin_platform_interface.dart index d32096b2..d4f9e53c 100644 --- a/lib/src/platform_interface/gigya_flutter_plugin_platform_interface.dart +++ b/lib/src/platform_interface/gigya_flutter_plugin_platform_interface.dart @@ -1,11 +1,11 @@ import 'package:plugin_platform_interface/plugin_platform_interface.dart'; +import '../method_channel/gigya_flutter_plugin_method_channel.dart'; import '../models/enums/social_provider.dart'; import '../models/screenset_event.dart'; -import '../services/interruption_resolver/interruption_resolver.dart'; -import '../services/otp_service/otp_service.dart'; -import '../services/web_authentication_service/web_authentication_service.dart'; -import 'gigya_flutter_plugin_method_channel.dart'; +import '../services/interruption_resolver.dart'; +import '../services/otp_service.dart'; +import '../services/web_authentication_service.dart'; /// The platform interface for the Gigya Flutter Plugin. abstract class GigyaFlutterPluginPlatform extends PlatformInterface { @@ -30,6 +30,9 @@ abstract class GigyaFlutterPluginPlatform extends PlatformInterface { _instance = instance; } + // TODO(navaronbracke): Move the Biometrics service to `src/method_channel/method_channel_biometrics_service.dart` + // during the next rebase. See https://github.com/SAP/gigya-flutter-plugin/pull/77 + /// Get the interruption resolver factory provided by the Gigya SDK. InterruptionResolverFactory get interruptionResolverFactory { throw UnimplementedError( @@ -84,7 +87,7 @@ abstract class GigyaFlutterPluginPlatform extends PlatformInterface { Future initSdk({ required String apiDomain, required String apiKey, - bool forceLogout = true, + bool forceLogout = false, }) { throw UnimplementedError('initSdk() is not implemented.'); } diff --git a/lib/src/services/interruption_resolver/interruption_resolver.dart b/lib/src/services/interruption_resolver.dart similarity index 91% rename from lib/src/services/interruption_resolver/interruption_resolver.dart rename to lib/src/services/interruption_resolver.dart index 4dba769d..7888fd14 100644 --- a/lib/src/services/interruption_resolver/interruption_resolver.dart +++ b/lib/src/services/interruption_resolver.dart @@ -1,6 +1,6 @@ -import '../../models/conflicting_accounts.dart'; -import '../../models/enums/social_provider.dart'; -import '../../models/gigya_error.dart'; +import '../models/conflicting_account.dart'; +import '../models/enums/social_provider.dart'; +import '../models/gigya_error.dart'; /// This interface represents the base interruption resolver. abstract class InterruptionResolver { @@ -22,7 +22,7 @@ abstract class InterruptionResolverFactory { /// The resolver for a link account flow interruption. abstract class LinkAccountResolver extends InterruptionResolver { /// Get the conflicting accounts of the user. - Future? get conflictingAccounts { + Future? get conflictingAccount { throw UnimplementedError('conflictingAccounts is not implemented.'); } diff --git a/lib/src/services/otp_service/otp_service.dart b/lib/src/services/otp_service.dart similarity index 100% rename from lib/src/services/otp_service/otp_service.dart rename to lib/src/services/otp_service.dart diff --git a/lib/src/services/web_authentication_service/web_authentication_service.dart b/lib/src/services/web_authentication_service.dart similarity index 100% rename from lib/src/services/web_authentication_service/web_authentication_service.dart rename to lib/src/services/web_authentication_service.dart diff --git a/lib/src/web/enums/web_error_code.dart b/lib/src/web/enums/web_error_code.dart new file mode 100644 index 00000000..d74df1cd --- /dev/null +++ b/lib/src/web/enums/web_error_code.dart @@ -0,0 +1,31 @@ +/// This enum defines the error codes for the Gigya Web SDK. +/// +/// See also: https://help.sap.com/docs/SAP_CUSTOMER_DATA_CLOUD/8b8d6fffe113457094a17701f63e3d6a/416d41b170b21014bbc5a10ce4041860.html#error-code-definitions-table +enum WebErrorCode { + /// This value indicates that an unspecified error occurred. + genericError(-1), + + /// This value indicates a success result. + success(0), + + /// This value indicates that a user has no valid session. + unauthorizedUser(403005); + + /// The default constructor. + const WebErrorCode(this.errorCode); + + /// Create a [WebErrorCode] from the given [errorCode]. + factory WebErrorCode.fromErrorCode(int errorCode) { + switch (errorCode) { + case 0: + return success; + case 403005: + return unauthorizedUser; + default: + return genericError; + } + } + + /// The error code for this enum value. + final int errorCode; +} diff --git a/lib/src/web/gigya_flutter_plugin_web.dart b/lib/src/web/gigya_flutter_plugin_web.dart new file mode 100644 index 00000000..4fe68fdd --- /dev/null +++ b/lib/src/web/gigya_flutter_plugin_web.dart @@ -0,0 +1,224 @@ +import 'dart:async'; +import 'dart:js_interop'; + +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; +import 'package:js/js_util.dart' show allowInterop; +import 'package:web/web.dart' as web; + +import '../models/gigya_error.dart'; +import '../models/screenset_event.dart'; +import '../platform_interface/gigya_flutter_plugin_platform_interface.dart'; +import '../services/interruption_resolver.dart'; +import 'enums/web_error_code.dart'; +import 'static_interop/gigya_web_sdk.dart'; +import 'static_interop/parameters/basic.dart'; +import 'static_interop/parameters/login.dart'; +import 'static_interop/response/response.dart'; +import 'static_interop/window.dart'; +import 'web_interruption_resolver.dart'; +import 'web_screenset_delegate.dart'; + +/// An implementation of [GigyaFlutterPluginPlatform] that uses JavaScript static interop. +class GigyaFlutterPluginWeb extends GigyaFlutterPluginPlatform { + /// Register [GigyaFlutterPluginWeb] as the default implementation for the web plugin. + /// + /// This method is used by the `GeneratedPluginRegistrant` class. + static void registerWith(Registrar registrar) { + GigyaFlutterPluginPlatform.instance = GigyaFlutterPluginWeb(); + } + + @override + InterruptionResolverFactory get interruptionResolverFactory { + return const WebInterruptionResolverFactory(); + } + + final WebScreensetDelegate _screensetDelegate = const WebScreensetDelegate(); + + @override + Future initSdk({ + required String apiDomain, + required String apiKey, + bool forceLogout = false, + }) async { + final Completer onGigyaServiceReadyCompleter = Completer(); + final GigyaWindow domWindow = GigyaWindow(web.window); + + // Set `window.onGigyaServiceReady` before creating the script. + // That function is called when the SDK has been initialized. + domWindow.onGigyaServiceReady = allowInterop((JSString? _) { + if (!onGigyaServiceReadyCompleter.isCompleted) { + onGigyaServiceReadyCompleter.complete(); + } + }).toJS; + + // If the Gigya SDK is ready beforehand, complete directly. + // This is the case when doing a Hot Reload, where the application starts from scratch, + // even though the Gigya SDK script is still attached to the DOM and ready. + // See https://docs.flutter.dev/tools/hot-reload#how-to-perform-a-hot-reload + final bool sdkIsReady = domWindow.gigya != null && GigyaWebSdk.instance.isReady; + + if (sdkIsReady) { + if (!onGigyaServiceReadyCompleter.isCompleted) { + onGigyaServiceReadyCompleter.complete(); + } + } else { + final Completer scriptLoadCompleter = Completer(); + + final web.HTMLScriptElement script = (web.document.createElement('script') as web.HTMLScriptElement) + ..async = true + ..defer = false + ..type = 'text/javascript' + ..lang = 'javascript' + ..crossOrigin = 'anonymous' + ..src = 'https://cdns.$apiDomain/js/gigya.js?apikey=$apiKey' + ..onload = allowInterop((JSAny _) { + if (!scriptLoadCompleter.isCompleted) { + scriptLoadCompleter.complete(); + } + }).toJS; + + web.document.head!.append(script); + + await scriptLoadCompleter.future; + } + + // If `onGigyaServiceReady` takes too long to be called + // (for instance if the network is unavailable, or Gigya does not initialize properly), + // exit with a timeout error. + await onGigyaServiceReadyCompleter.future.timeout( + const Duration(seconds: 5), + onTimeout: () => throw const GigyaTimeoutError(), + ); + + if (forceLogout) { + await logout(); + } + } + + @override + Future isLoggedIn() { + final Completer completer = Completer(); + final BasicParameters parameters = BasicParameters( + callback: allowInterop((Response response) { + if (completer.isCompleted) { + return; + } + + switch (WebErrorCode.fromErrorCode(response.errorCode)) { + case WebErrorCode.success: + completer.complete(true); + break; + case WebErrorCode.unauthorizedUser: + completer.complete(false); + break; + default: + completer.completeError( + GigyaError( + apiVersion: response.apiVersion, + callId: response.callId, + details: response.details, + errorCode: response.errorCode, + ), + ); + break; + } + }).toJS, + ); + + GigyaWebSdk.instance.accounts.session.verify.callAsFunction( + null, + parameters, + ); + + return completer.future; + } + + @override + Future> login({ + required String loginId, + required String password, + Map parameters = const {}, + }) { + final Completer> completer = Completer>(); + + final LoginParameters loginParameters = LoginParameters( + loginID: loginId, + password: password, + captchaToken: parameters['captchaToken'] as String?, + include: parameters['include'] as String?, + loginMode: parameters['loginMode'] as String?, + redirectURL: parameters['redirectURL'] as String?, + regToken: parameters['regToken'] as String?, + sessionExpiration: parameters['sessionExpiration'] as int?, + callback: allowInterop((LoginResponse response) { + if (completer.isCompleted) { + return; + } + + if (response.baseResponse.errorCode == 0) { + completer.complete(response.toMap()); + } else { + completer.completeError( + GigyaError( + apiVersion: response.baseResponse.apiVersion, + callId: response.baseResponse.callId, + details: response.baseResponse.details, + errorCode: response.baseResponse.errorCode, + ), + ); + } + }).toJS, + ); + + GigyaWebSdk.instance.accounts.login.callAsFunction( + null, + loginParameters, + ); + + return completer.future; + } + + @override + Future logout() async { + if (!await isLoggedIn()) { + return; + } + + final Completer completer = Completer(); + final BasicParameters parameters = BasicParameters( + callback: allowInterop((Response response) { + if (completer.isCompleted) { + return; + } + + if (response.errorCode == 0) { + completer.complete(); + } else { + completer.completeError( + GigyaError( + apiVersion: response.apiVersion, + callId: response.callId, + details: response.details, + errorCode: response.errorCode, + ), + ); + } + }).toJS, + ); + + GigyaWebSdk.instance.accounts.logout.callAsFunction( + null, + parameters, + ); + + return completer.future; + } + + @override + Stream showScreenSet( + String name, { + Map parameters = const {}, + }) { + return _screensetDelegate.showScreenSet(name, parameters: parameters); + } +} diff --git a/lib/src/web/static_interop/account.dart b/lib/src/web/static_interop/account.dart new file mode 100644 index 00000000..1d315b46 --- /dev/null +++ b/lib/src/web/static_interop/account.dart @@ -0,0 +1,54 @@ +import 'dart:js_interop'; + +import './session.dart'; +import 'parameters/add_event_handlers_parameters.dart'; +import 'parameters/basic.dart'; +import 'parameters/conflicting_account.dart'; +import 'parameters/login.dart'; +import 'parameters/screenset_parameters.dart'; + +/// The extension type for the `gigya.accounts` JavaScript object. +/// +/// See also: https://help.sap.com/docs/SAP_CUSTOMER_DATA_CLOUD/8b8d6fffe113457094a17701f63e3d6a/4130da0f70b21014bbc5a10ce4041860.html +@JS() +@staticInterop +extension type Accounts(JSObject _) { + /// Register event handlers for the global events that are emitted by the Gigya SDK. + /// + /// This function receives an [AddEventHandlersParameters] instance as argument, + /// and has [JSVoid] as return type. + external JSFunction addEventHandlers; + + /// Get the conflicting accounts of the user. + /// + /// This function receives a [ConflictingAccountParameters] instance as argument, + /// and has [JSVoid] as return type. + external JSFunction getConflictingAccount; + + /// Hide a screen set. + /// + /// This function receives a [HideScreensetParameters] instance as argument, + /// and has [JSVoid] as return type. + external JSFunction hideScreenSet; + + /// Log the user in. + /// + /// This function receives a [LoginParameters] instance as argument, + /// and has [JSVoid] as return type. + external JSFunction login; + + /// Log out of the current session. + /// + /// This function receives a [BasicParameters] instance as argument, + /// and has [JSVoid] as return type. + external JSFunction logout; + + /// Get the `gigya.accounts.session` namespace. + external Session get session; + + /// Show a screen set. + /// + /// This function receives a [ShowScreensetParameters] instance as argument, + /// and has [JSVoid] as return type. + external JSFunction showScreenSet; +} diff --git a/lib/src/web/static_interop/gigya_web_sdk.dart b/lib/src/web/static_interop/gigya_web_sdk.dart new file mode 100644 index 00000000..f9cedc47 --- /dev/null +++ b/lib/src/web/static_interop/gigya_web_sdk.dart @@ -0,0 +1,32 @@ +import 'dart:js_interop'; + +import 'package:web/web.dart' as web; + +import 'account.dart'; +import 'window.dart'; + +/// The static interop extension type for the `gigya` JavaScript object. +/// +/// See also: https://help.sap.com/docs/SAP_CUSTOMER_DATA_CLOUD/8b8d6fffe113457094a17701f63e3d6a/417f6b5e70b21014bbc5a10ce4041860.html +@JS() +@staticInterop +extension type GigyaWebSdk(JSObject _) implements JSObject { + /// Get the Gigya Web SDK instance from the window. + /// + /// Throws a [StateError] if the Gigya Web SDK instance is null. + static GigyaWebSdk get instance { + final GigyaWebSdk? gigya = GigyaWindow(web.window).gigya; + + if (gigya == null) { + throw StateError('The Gigya Web SDK is null.'); + } + + return gigya; + } + + /// Get the accounts namespace in the Gigya Web SDK. + external Accounts get accounts; + + /// Whether the Gigya Web SDK is ready. + external bool get isReady; +} diff --git a/lib/src/web/static_interop/global_events/login_event.dart b/lib/src/web/static_interop/global_events/login_event.dart new file mode 100644 index 00000000..63b905b8 --- /dev/null +++ b/lib/src/web/static_interop/global_events/login_event.dart @@ -0,0 +1,42 @@ +import 'dart:js_interop'; + +import '../models/profile.dart'; + +/// The extension type for the global login event emitted by the Gigya SDK. +/// +/// See: https://help.sap.com/docs/SAP_CUSTOMER_DATA_CLOUD/8b8d6fffe113457094a17701f63e3d6a/41532ab870b21014bbc5a10ce4041860.html#onlogin-event-data +@JS() +@anonymous +@staticInterop +extension type LoginEvent(JSObject _) { + /// The type of login, which is either 'standard' or 'reAuth'. + external String get loginMode; + + /// The name of the provider that the user used in order to login. + external String get provider; + + /// The GMT time of the response in UNIX time format. + /// This timestamp should be used for login verification. + external String get signatureTimestamp; + + /// A [Profile] object with updated information for the current user. + external Profile get user; + + /// The User ID that should be used for login verification. + external String get UID; // ignore: non_constant_identifier_names + + /// The signature that should be used for login verification. + external String get UIDSignature; // ignore: non_constant_identifier_names + + /// Convert this object to a map. + Map toMap() { + return { + 'loginMode': loginMode, + 'provider': provider, + 'signatureTimestamp': signatureTimestamp, + 'user': user.toMap(), + 'UID': UID, + 'UIDSignature': UIDSignature, + }; + } +} \ No newline at end of file diff --git a/lib/src/web/static_interop/models/certification.dart b/lib/src/web/static_interop/models/certification.dart new file mode 100644 index 00000000..756dc229 --- /dev/null +++ b/lib/src/web/static_interop/models/certification.dart @@ -0,0 +1,33 @@ +import 'dart:js_interop'; + +/// The extension type for the Certification object. +@JS() +@anonymous +@staticInterop +extension type Certification(JSObject _) { + /// The certification authority. + external String? get authority; + + /// The date that the certification expired. + external String? get endDate; + + /// The name of the certification. + external String? get name; + + /// The certification number. + external String? get number; + + /// The date that the certification was issued. + external String? get startDate; + + /// Convert this certification to a [Map]. + Map toMap() { + return { + 'authority': authority, + 'endDate': endDate, + 'name': name, + 'number': number, + 'startDate': startDate, + }; + } +} diff --git a/lib/src/web/static_interop/models/conflicting_account.dart b/lib/src/web/static_interop/models/conflicting_account.dart new file mode 100644 index 00000000..1168db08 --- /dev/null +++ b/lib/src/web/static_interop/models/conflicting_account.dart @@ -0,0 +1,20 @@ +import 'dart:js_interop'; + +/// The extension type for the `Conflicting Account` object. +/// +/// The name of this extension type is `WebConflictingAccount`, +/// to prevent collisions with the existing `ConflictingAccount` class. +@JS() +@anonymous +@staticInterop +extension type WebConflictingAccount(JSObject _) { + /// The username or email address + /// of the user that has a conflicting account. + external String? get loginID; + + @JS('loginProviders') + external JSArray? get _loginProviders; + + /// The login providers for the [loginID] that has a conflicting account. + List? get loginProviders => _loginProviders?.toDart.cast(); +} diff --git a/lib/src/web/static_interop/models/education.dart b/lib/src/web/static_interop/models/education.dart new file mode 100644 index 00000000..343434d7 --- /dev/null +++ b/lib/src/web/static_interop/models/education.dart @@ -0,0 +1,37 @@ +import 'dart:js_interop'; + +/// The extension type for the Education object. +@JS() +@anonymous +@staticInterop +extension type Education(JSObject _) { + /// The degree for the education. + external String? get degree; + + /// The year in which the education was finished. + external String? get endYear; + + /// The field of study of the education. + external String? get fieldOfStudy; + + /// The name of the school at which the education took place. + external String? get school; + + /// The type of school at which the education took place. + external String? get schoolType; + + /// The year in which the education was started. + external String? get startYear; + + /// Convert this education to a [Map]. + Map toMap() { + return { + 'degree': degree, + 'endYear': endYear, + 'fieldOfStudy': fieldOfStudy, + 'school': school, + 'schoolType': schoolType, + 'startYear': startYear, + }; + } +} diff --git a/lib/src/web/static_interop/models/emails.dart b/lib/src/web/static_interop/models/emails.dart new file mode 100644 index 00000000..828bd313 --- /dev/null +++ b/lib/src/web/static_interop/models/emails.dart @@ -0,0 +1,31 @@ +import 'dart:js_interop'; + +/// The extension type for the emails object. +@JS() +@anonymous +@staticInterop +extension type Emails(JSObject _) { + @JS('unverified') + external JSArray? get _unverified; + + @JS('verified') + external JSArray? get _verified; + + /// The list of unverified email addresses. + List get unverified { + return _unverified?.toDart.cast() ?? const []; + } + + /// The list of verified email addresses. + List get verified { + return _verified?.toDart.cast() ?? const []; + } + + /// Convert this object to a [Map]. + Map toMap() { + return { + 'unverified': unverified, + 'verified': verified, + }; + } +} diff --git a/lib/src/web/static_interop/models/favorites.dart b/lib/src/web/static_interop/models/favorites.dart new file mode 100644 index 00000000..5eb9008a --- /dev/null +++ b/lib/src/web/static_interop/models/favorites.dart @@ -0,0 +1,91 @@ +import 'dart:js_interop'; + +/// The extension type for the `Favorite` object. +@JS() +@anonymous +@staticInterop +extension type Favorite(JSObject _) { + /// The category of the favorite. + external String? get category; + + /// The id of the favorite. + external String? get id; + + /// The name of the favorite. + external String? get name; + + /// Convert this favorite to a [Map]. + Map toMap() { + return { + 'id': id, + 'name': name, + 'category': category, + }; + } +} + +/// The extension type for the `Favorites` object. +@JS() +@anonymous +@staticInterop +extension type Favorites(JSObject _) { + @JS('activities') + external JSArray? get _activities; + + @JS('books') + external JSArray? get _books; + + @JS('interests') + external JSArray? get _interests; + + @JS('movies') + external JSArray? get _movies; + + @JS('music') + external JSArray? get _music; + + @JS('television') + external JSArray? get _television; + + /// The user's favorite activities. + List get activities { + return _activities?.toDart.cast() ?? const []; + } + + /// The user's favorite books. + List get books { + return _books?.toDart.cast() ?? const []; + } + + /// The user's interests. + List get interests { + return _interests?.toDart.cast() ?? const []; + } + + /// The user's favorite movies. + List get movies { + return _movies?.toDart.cast() ?? const []; + } + + /// The user's favorite music. + List get music { + return _music?.toDart.cast() ?? const []; + } + + /// The user's favorite television programmes. + List get television { + return _television?.toDart.cast() ?? const []; + } + + /// Convert this object to a [Map]. + Map toMap() { + return { + 'activities': activities.map((Favorite f) => f.toMap()).toList(), + 'books': books.map((Favorite f) => f.toMap()).toList(), + 'interests': interests.map((Favorite f) => f.toMap()).toList(), + 'movies': movies.map((Favorite f) => f.toMap()).toList(), + 'music': music.map((Favorite f) => f.toMap()).toList(), + 'television': television.map((Favorite f) => f.toMap()).toList(), + }; + } +} diff --git a/lib/src/web/static_interop/models/like.dart b/lib/src/web/static_interop/models/like.dart new file mode 100644 index 00000000..29f1f172 --- /dev/null +++ b/lib/src/web/static_interop/models/like.dart @@ -0,0 +1,29 @@ +import 'dart:js_interop'; + +/// The extension type for the `Like` object. +@JS() +@anonymous +@staticInterop +extension type Like(JSObject _) { + /// The category of the like. + external String? get category; + + /// The identifier of the like. + external String? get id; + + /// The name of the like. + external String? get name; + + /// The timestamp, in UTC, on which the like was created. + external String? get time; + + /// Convert this like to a [Map]. + Map toMap() { + return { + 'category': category, + 'id': id, + 'name': name, + 'time': time, + }; + } +} diff --git a/lib/src/web/static_interop/models/location.dart b/lib/src/web/static_interop/models/location.dart new file mode 100644 index 00000000..f977c9e9 --- /dev/null +++ b/lib/src/web/static_interop/models/location.dart @@ -0,0 +1,49 @@ +import 'dart:js_interop'; + +/// The extension type for the `Coordinates` object. +@JS() +@anonymous +@staticInterop +extension type Coordinates(JSObject _) { + /// The latitude of the coordinate. + external double get lat; + + /// The longitude of the coordinate. + external double get lon; + + /// Convert these coordinates to a [Map]. + Map toMap() { + return { + 'lat': lat, + 'lon': lon, + }; + } +} + +/// The extension type for the `Location` object. +@JS() +@anonymous +@staticInterop +extension type Location(JSObject _) { + /// The name of the city in which the location is located. + external String? get city; + + /// The coordinates of the location. + external Coordinates? get coordinates; + + /// The two character country code of the location. + external String? get country; + + /// The state in which the location is located. + external String? get state; + + /// Convert this location to a [Map]. + Map toMap() { + return { + 'city': city, + 'country': country, + if (coordinates != null) 'coordinates': coordinates!.toMap(), + 'state': state, + }; + } +} diff --git a/lib/src/web/static_interop/models/patent.dart b/lib/src/web/static_interop/models/patent.dart new file mode 100644 index 00000000..5413b2b3 --- /dev/null +++ b/lib/src/web/static_interop/models/patent.dart @@ -0,0 +1,41 @@ +import 'dart:js_interop'; + +/// The extension type for the `Patent` object. +@JS() +@anonymous +@staticInterop +extension type Patent(JSObject _) { + /// The issue date of the patent. + external String? get date; + + /// The patent number. + external String? get number; + + /// The name of the office that issued the patent. + external String? get office; + + /// The status of the patent. + external String? get status; + + /// The summary of the patent. + external String? get summary; + + /// The title of the patent. + external String? get title; + + /// The url to the patent document. + external String? get url; + + /// Convert this patent into a [Map]. + Map toMap() { + return { + 'date': date, + 'number': number, + 'office': office, + 'status': status, + 'summary': summary, + 'title': title, + 'url': url, + }; + } +} diff --git a/lib/src/web/static_interop/models/phone.dart b/lib/src/web/static_interop/models/phone.dart new file mode 100644 index 00000000..6da1810d --- /dev/null +++ b/lib/src/web/static_interop/models/phone.dart @@ -0,0 +1,21 @@ +import 'dart:js_interop'; + +/// The extension type for the `Phone` object. +@JS() +@anonymous +@staticInterop +extension type Phone(JSObject _) { + /// The value of the phone number. + external String? get number; + + /// The type of phone number. + external String? get type; + + /// Convert this phone into a [Map]. + Map toMap() { + return { + 'number': number, + 'type': type, + }; + } +} diff --git a/lib/src/web/static_interop/models/profile.dart b/lib/src/web/static_interop/models/profile.dart new file mode 100644 index 00000000..89e5aa1f --- /dev/null +++ b/lib/src/web/static_interop/models/profile.dart @@ -0,0 +1,269 @@ +import 'dart:js_interop'; + +import 'certification.dart'; +import 'education.dart'; +import 'favorites.dart'; +import 'like.dart'; +import 'patent.dart'; +import 'phone.dart'; +import 'publication.dart'; +import 'skill.dart'; +import 'work.dart'; + +/// The extension type for the `Profile` object. +@JS() +@anonymous +@staticInterop +extension type Profile(JSObject _) { + /// The person's activities. + external String? get activities; + + /// The person's address. + external String? get address; + + /// The person's age. + external int? get age; + + /// The person's biography. + external String? get bio; + + /// The person's birth day. + external int? get birthDay; + + /// The person's birth month. + external int? get birthMonth; + + /// The person's birth year. + external int? get birthYear; + + @JS('certifications') + external JSArray? get _certifications; + + /// The person's certifications. + List get certifications { + return _certifications?.toDart.cast() ?? const []; + } + + /// The city in which the person resides. + external String? get city; + + /// The country in which the person resides. + external String? get country; + + @JS('education') + external JSArray? get _education; + + /// The different educations of the person. + List get education { + return _education?.toDart.cast() ?? const []; + } + + /// The education level of the person. + external String? get educationLevel; + + /// The person's email address. + external String? get email; + + /// The person's favorites. + external Favorites? get favorites; + + /// The person's first name. + external String? get firstName; + + /// The amount of followers of this person. + external int? get followersCount; + + /// The amount of people that this person is following. + external int? get followingCount; + + /// The person's gender. + external String? get gender; + + /// The person's home town. + external String? get hometown; + + /// The person's honorary titles. + external String? get honors; + + /// The industry in which this person is employed. + external String? get industry; + + /// The person's interests. + external String? get interests; + + /// Indicates whether the user is connected to any available provider. + external bool? get isConnected; + + /// Indicates whether the user is a user of the site. + external bool? get isSiteUser; + + /// The different languages that the person is proficient in. + external String? get languages; + + /// The person's last name. + external String? get lastName; + + @JS('likes') + external JSArray? get _likes; + + /// The person's likes. + List get likes => _likes?.toDart.cast() ?? const []; + + /// The language locale of the person's primary language. + external String? get locale; + + /// The name of the provider that the user used in order to log in. + /// If the user logged in using your site login mechanism, then `site` is used. + external String? get loginProvider; + + /// The user's id from the given [loginProvider]. + external String? get loginProviderUID; + + /// The person's full name. + external String? get name; + + /// The person's nickname. + external String? get nickname; + + @JS('patents') + external JSArray? get _patents; + + /// The different patents that this person owns. + List get patents { + return _patents?.toDart.cast() ?? const []; + } + + @JS('phones') + external JSArray? get _phones; + + /// The different phone numbers belonging to this person. + List get phones => _phones?.toDart.cast() ?? const []; + + /// The url to the person's photo. + external String? get photoURL; + + /// The person's political view. + external String? get politicalView; + + /// The person's professional headline. + external String? get professionalHeadline; + + /// The url to the person's profile page. + external String? get profileURL; + + @JS('providers') + external JSArray? get _providers; + + /// The names of the providers to which the user is connected/logged in. + List get providers { + return _providers?.toDart.cast() ?? const []; + } + + /// The person's proxy email address. + external String? get proxyEmail; + + @JS('publications') + external JSArray? get _publications; + + /// The list of publications belonging to this person. + List get publications { + return _publications?.toDart.cast() ?? const []; + } + + /// The person's relationship status. + external String? get relationshipStatus; + + /// The person's religion. + external String? get religion; + + @JS('skills') + external JSArray? get _skills; + + /// The different skills of the person. + List get skills => _skills?.toDart.cast() ?? const []; + + /// The person's specialities. + external String? get specialities; + + /// The state in which the person resides. + external String? get state; + + /// The url to the person's thumbnail image. + external String? get thumbnailURL; + + /// The person's timezone. + external String? get timezone; + + /// The person's username. + external String? get username; + + /// The verified status of the person. + external String? get verified; + + @JS('work') + external JSArray? get _work; + + /// The person's career, divided into the different employments. + List get work => _work?.toDart.cast() ?? const []; + + /// The ZIP code of the person's address. + external String? get zip; + + /// Convert this profile into a [Map]. + Map toMap() { + return { + 'activities': activities, + 'address': address, + 'age': age, + 'bio': bio, + 'birthDay': birthDay, + 'birthMonth': birthMonth, + 'birthYear': birthYear, + 'certifications': certifications.map((Certification c) => c.toMap()).toList(), + 'city': city, + 'country': country, + 'education': education.map((Education e) => e.toMap()).toList(), + 'educationLevel': educationLevel, + 'email': email, + 'favorites': favorites?.toMap(), + 'firstName': firstName, + 'followersCount': followersCount, + 'followingCount': followingCount, + 'gender': gender, + 'hometown': hometown, + 'honors': honors, + 'industry': industry, + 'interests': interests, + 'isConnected': isConnected, + 'isSiteUser': isSiteUser, + 'languages': languages, + 'lastName': lastName, + 'likes': likes.map((Like l) => l.toMap()).toList(), + 'locale': locale, + 'loginProvider': loginProvider, + 'loginProviderUID': loginProviderUID, + 'name': name, + 'nickname': nickname, + 'patents': patents.map((Patent p) => p.toMap()).toList(), + 'phones': phones.map((Phone p) => p.toMap()).toList(), + 'photoURL': photoURL, + 'politicalView': politicalView, + 'professionalHeadline': professionalHeadline, + 'profileURL': profileURL, + 'providers': providers, + 'proxyEmail': proxyEmail, + 'publications': publications.map((Publication p) => p.toMap()).toList(), + 'relationshipStatus': relationshipStatus, + 'religion': religion, + 'skills': skills.map((Skill s) => s.toMap()).toList(), + 'specialities': specialities, + 'state': state, + 'thumbnailURL': thumbnailURL, + 'timezone': timezone, + 'username': username, + 'verified': verified, + 'work': work.map((Work w) => w.toMap()).toList(), + 'zip': zip, + }; + } +} diff --git a/lib/src/web/static_interop/models/publication.dart b/lib/src/web/static_interop/models/publication.dart new file mode 100644 index 00000000..4a4c2e08 --- /dev/null +++ b/lib/src/web/static_interop/models/publication.dart @@ -0,0 +1,33 @@ +import 'dart:js_interop'; + +/// The extension type for the `Publication` object. +@JS() +@anonymous +@staticInterop +extension type Publication(JSObject _) { + /// The publication date. + external String? get date; + + /// The name of the publisher that published the publication. + external String? get publisher; + + /// The summary of the publication. + external String? get summary; + + /// The title of the publication. + external String? get title; + + /// The url to the publication's document. + external String? get url; + + /// Convert this publication into a [Map]. + Map toMap() { + return { + 'date': date, + 'publisher': publisher, + 'summary': summary, + 'title': title, + 'url': url, + }; + } +} diff --git a/lib/src/web/static_interop/models/session_info.dart b/lib/src/web/static_interop/models/session_info.dart new file mode 100644 index 00000000..687f9cbb --- /dev/null +++ b/lib/src/web/static_interop/models/session_info.dart @@ -0,0 +1,21 @@ +import 'dart:js_interop'; + +/// The extension type for the session info object. +@JS() +@anonymous +@staticInterop +extension type SessionInfo(JSObject _) { + /// The name of the session cookie. + external String? get cookieName; + + /// The value of the session cookie. + external String? get cookieValue; + + /// Convert this session info into a [Map]. + Map toMap() { + return { + 'cookieName': cookieName, + 'cookieValue': cookieValue, + }; + } +} diff --git a/lib/src/web/static_interop/models/skill.dart b/lib/src/web/static_interop/models/skill.dart new file mode 100644 index 00000000..118716ac --- /dev/null +++ b/lib/src/web/static_interop/models/skill.dart @@ -0,0 +1,25 @@ +import 'dart:js_interop'; + +/// The extension type for the `Skill` object. +@JS() +@anonymous +@staticInterop +extension type Skill(JSObject _) { + /// The user's proficiency in the skill. + external String? get level; + + /// The user's skill. + external String? get skill; + + /// The amount of years the user is proficient in the given skill. + external int? get years; + + /// Convert this skill to a [Map]. + Map toMap() { + return { + 'level': level, + 'skill': skill, + 'years': years, + }; + } +} diff --git a/lib/src/web/static_interop/models/work.dart b/lib/src/web/static_interop/models/work.dart new file mode 100644 index 00000000..b367982d --- /dev/null +++ b/lib/src/web/static_interop/models/work.dart @@ -0,0 +1,45 @@ +import 'dart:js_interop'; + +/// The extension type for the `Work` object. +@JS() +@anonymous +@staticInterop +extension type Work(JSObject _) { + /// The name of the company. + external String? get company; + + /// The size of the [company] in number of employees. + external int? get companySize; + + /// The id of the company. + external String? get companyID; + + /// The date the user stopped working at the company. + external String? get endDate; + + /// The industry of the company. + external String? get industry; + + /// Whether the user currently works at the company. + external bool? get isCurrent; + + /// The date the user started working at the company. + external String? get startDate; + + /// The user's title in the [company]. + external String? get title; + + /// Convert this work object into a [Map]. + Map toMap() { + return { + 'company': company, + 'companyID': companyID, + 'companySize': companySize, + 'endDate': endDate, + 'industry': industry, + 'isCurrent': isCurrent, + 'startDate': startDate, + 'title': title, + }; + } +} diff --git a/lib/src/web/static_interop/parameters/add_event_handlers_parameters.dart b/lib/src/web/static_interop/parameters/add_event_handlers_parameters.dart new file mode 100644 index 00000000..3f9adb93 --- /dev/null +++ b/lib/src/web/static_interop/parameters/add_event_handlers_parameters.dart @@ -0,0 +1,31 @@ +import 'dart:js_interop'; + +import '../global_events/login_event.dart'; + +/// The extension type that defines the parameters for the `Accounts.addEventHandlers` function. +/// +/// The `onLogout`, `onConnectionAdded` and `onConnectionRemoved` handlers are specifically omitted from this class, +/// since the methods that emit these events also support a callback function, +/// which is used in specific static interop definitions for these events. +/// +/// The `onError` handler is specifically omitted from this class. +/// If the Gigya SDK fails to initialize, a timeout exception is thrown instead. +/// For screen sets, the error event is passed to the [Stream] directly. +/// +/// See also: https://help.sap.com/docs/SAP_CUSTOMER_DATA_CLOUD/8b8d6fffe113457094a17701f63e3d6a/41532ab870b21014bbc5a10ce4041860.html#global-application-events +@JS() +@anonymous +@staticInterop +extension type AddEventHandlersParameters._(JSObject _) implements JSObject { + /// Construct a new [AddEventHandlersParameters] instance. + /// + /// The [onAfterResponse] function receives a nullable [JSAny] argument, + /// and has [JSVoid] as return type. + /// + /// The [onLogin] function receives a [LoginEvent] argument, + /// and has [JSVoid] as return type. + external factory AddEventHandlersParameters({ + JSFunction? onAfterResponse, + JSFunction? onLogin, + }); +} diff --git a/lib/src/web/static_interop/parameters/basic.dart b/lib/src/web/static_interop/parameters/basic.dart new file mode 100644 index 00000000..d533b2d2 --- /dev/null +++ b/lib/src/web/static_interop/parameters/basic.dart @@ -0,0 +1,16 @@ +import 'dart:js_interop'; + +import '../response/response.dart'; + +/// This extension type represents the parameters for functions within the Gigya Web SDK, +/// that accept a `callback` function, which in turn receives a [Response] as argument. +@JS() +@anonymous +@staticInterop +extension type BasicParameters._(JSObject _) implements JSObject { + /// Create a [BasicParameters] instance using the given [callback]. + /// + /// The [callback] function will receive a [Response] as argument, + /// and has [JSVoid] as return type. + external factory BasicParameters({JSFunction callback}); +} diff --git a/lib/src/web/static_interop/parameters/conflicting_account.dart b/lib/src/web/static_interop/parameters/conflicting_account.dart new file mode 100644 index 00000000..5ba720d6 --- /dev/null +++ b/lib/src/web/static_interop/parameters/conflicting_account.dart @@ -0,0 +1,18 @@ +import 'dart:js_interop'; + +import '../response/conflicting_account_response.dart'; + +/// This extension type represents the parameters for the `Accounts.getConflictingAccount` method. +@JS() +@anonymous +@staticInterop +extension type ConflictingAccountParameters._(JSObject _) implements JSObject { + /// Create a [ConflictingAccountParameters] instance using the given [callback] and [regToken]. + /// + /// The [callback] receives a [ConflictingAccountResponse] as argument, + /// and has [JSVoid] as return type. + external factory ConflictingAccountParameters({ + JSFunction callback, + String regToken, + }); +} diff --git a/lib/src/web/static_interop/parameters/login.dart b/lib/src/web/static_interop/parameters/login.dart new file mode 100644 index 00000000..9fa089d6 --- /dev/null +++ b/lib/src/web/static_interop/parameters/login.dart @@ -0,0 +1,25 @@ +import 'dart:js_interop'; + +import '../response/login_response.dart'; + +/// This extension type represents the parameters for the `Accounts.login` method. +@JS() +@anonymous +@staticInterop +extension type LoginParameters._(JSObject _) implements JSObject { + /// Create a new [LoginParameters] instance. + /// + /// The [callback] receives a [LoginResponse] as argument, + /// and has [JSVoid] as return type. + external factory LoginParameters({ + JSFunction callback, + String? captchaToken, + String? include, + String loginID, + String? loginMode, + String password, + String? redirectURL, + String? regToken, + int? sessionExpiration, + }); +} diff --git a/lib/src/web/static_interop/parameters/screenset_parameters.dart b/lib/src/web/static_interop/parameters/screenset_parameters.dart new file mode 100644 index 00000000..4d427c15 --- /dev/null +++ b/lib/src/web/static_interop/parameters/screenset_parameters.dart @@ -0,0 +1,87 @@ +import 'dart:js_interop'; + +import '../../static_interop/screenset_event/screenset_events.dart'; + +/// The extension type that defines the parameters +/// for the `Accounts.hideScreenset` method. +@JS() +@anonymous +@staticInterop +extension type HideScreensetParameters._(JSObject _) implements JSObject { + /// Construct a new [HideScreensetParameters] instance. + /// + /// The given [screenSet] is the name of the screen set to hide. + external factory HideScreensetParameters({String screenSet}); +} + +// TODO: move DartDoc comments for the ShowScreensetParameters function arguments +// The function arguments have some documentation, which is only relevant for the constructor, +// since the type has no fields. However, DartDoc does not yet support documenting constructor arguments. +// +// See https://github.com/dart-lang/dartdoc/issues/1259 + +/// The extension type that defines the parameters +/// for the `Accounts.showScreenset` method. +@JS() +@anonymous +@staticInterop +extension type ShowScreensetParameters._(JSObject _) implements JSObject { + /// Construct a new [ShowScreensetParameters] instance. + /// + /// The [onAfterScreenLoad] function receives an [AfterScreenLoadEvent] as argument, + /// and has [JSVoid] as return type. + /// + /// The [onAfterSubmit] function receives an [AfterSubmitEvent] as argument, + /// and has [JSVoid] as return type. + /// + /// The [onAfterValidation] function receives an [AfterValidationEvent] as argument, + /// and has [JSVoid] as return type. + /// + /// The [onBeforeScreenLoad] function receives a [BeforeScreenLoadEvent] as argument, + /// and has a nullable [JSAny] as return type. + /// + /// The [onBeforeSubmit] function receives a [BeforeSubmitEvent] as argument, + /// and has a boolean as return type. + /// + /// The [onBeforeValidation] function receives a [BeforeValidationEvent] as argument, + /// and has a nullable [JSAny] as return type. + /// + /// The [onError] function receives an [ErrorEvent] as argument, + /// and has a nullable [JSAny] as return type. + /// + /// The [onFieldChanged] function receives a [FieldChangedEvent] as argument, + /// and has [JSVoid] as return type. + /// + /// The [onHide] function receives a [HideEvent] as argument, + /// and has [JSVoid] as return type. + /// + /// The [onSubmit] function receives a [SubmitEvent] as argument, + /// and has [JSVoid] as return type. + external factory ShowScreensetParameters({ + String? authFlow, + bool? communicationLangByScreenSet, + String? deviceType, + String? dialogStyle, + String? enabledProviders, + String? googlePlayAppID, + String? lang, + String? mobileScreenSet, + JSFunction onAfterScreenLoad, + JSFunction onAfterSubmit, + JSFunction onAfterValidation, + JSFunction onBeforeScreenLoad, + JSFunction onBeforeSubmit, + JSFunction onBeforeValidation, + JSFunction onError, + JSFunction onFieldChanged, + JSFunction onHide, + JSFunction onSubmit, + String? redirectMethod, + String? redirectURL, + String? regSource, + String? regToken, + String screenSet, + int? sessionExpiration, + String? startScreen, + }); +} diff --git a/lib/src/web/static_interop/response/conflicting_account_response.dart b/lib/src/web/static_interop/response/conflicting_account_response.dart new file mode 100644 index 00000000..592db403 --- /dev/null +++ b/lib/src/web/static_interop/response/conflicting_account_response.dart @@ -0,0 +1,15 @@ +import 'dart:js_interop'; + +import '../models/conflicting_account.dart'; +import 'response.dart'; + +/// The extension type for the Gigya Conflicting Account API response. +/// +/// See also: https://help.sap.com/docs/SAP_CUSTOMER_DATA_CLOUD/8b8d6fffe113457094a17701f63e3d6a/4134c49270b21014bbc5a10ce4041860.html +@JS() +@anonymous +@staticInterop +extension type ConflictingAccountResponse(Response baseResponse) { + /// Get the conflicting account that is included in the response. + external WebConflictingAccount? get conflictingAccount; +} diff --git a/lib/src/web/static_interop/response/login_response.dart b/lib/src/web/static_interop/response/login_response.dart new file mode 100644 index 00000000..f22477eb --- /dev/null +++ b/lib/src/web/static_interop/response/login_response.dart @@ -0,0 +1,117 @@ +import 'dart:js_interop'; + +import '../models/emails.dart'; +import '../models/location.dart'; +import '../models/profile.dart'; +import '../models/session_info.dart'; +import 'response.dart'; + +/// The extension type for the Gigya Login API response. +/// +/// See also: https://help.sap.com/docs/SAP_CUSTOMER_DATA_CLOUD/8b8d6fffe113457094a17701f63e3d6a/eb93d538b9ae45bfadd9a8aaa8806753.html#response-data +@JS() +@anonymous +@staticInterop +extension type LoginResponse(Response baseResponse) { + // TODO: preferences, subscriptions should be in the login response + + /// The timestamp of the creation of the user. + external String? get created; + + /// The verified and unverified email addresses of the user. + external Emails? get emails; + + /// Whether the user is active. + external bool? get isActive; + + /// Whether the user that is logging in is a new user. + external bool? get isNewUser; + + /// Whether the user is registered. + external bool? get isRegistered; + + /// Whether the user is verified. + external bool? get isVerified; + + /// The timestamp of the last login of the user. + external String? get lastLogin; + + /// The last login location of the user. + external Location? get lastLoginLocation; + + /// The timestamp of the last update to the user's profile, + /// preferences or subscriptions data. + external String? get lastUpdated; + + /// The login provider that was last used to log in. + external String? get loginProvider; + + /// The timestamp, when the oldest data of the user was refreshed. + external String? get oldestDataUpdated; + + /// The user's profile. + external Profile? get profile; + + /// The timestamp when the user was registered. + external String? get registered; + + /// The source of the registration. + external String? get regSource; + + /// The registration token. + external String? get regToken; + + /// The session info for the user's session. + external SessionInfo? get sessionInfo; + + /// The timestamp of the [UIDSignature]. + external String? get signatureTimestamp; + + /// The comma separated list of social providers linked to this user. + external String? get socialProviders; + + /// The unique id of this user. + external String? get UID; // ignore: non_constant_identifier_names + + /// The verification signature of the [UID]. + external String? get UIDSignature; // ignore: non_constant_identifier_names + + /// The timestamp when the user was verified. + external String? get verified; + + /// Convert this response to a [Map]. + Map toMap() { + final Map? profileAsMap = profile?.toMap(); + + if (profileAsMap != null && lastLoginLocation != null) { + // The lastLoginLocation field is not in the profile + // when using the Gigya Web SDK. + // Instead, it is located inside the login response. + // Add it to the profile map. + profileAsMap['lastLoginLocation'] = lastLoginLocation!.toMap(); + } + + return { + 'created': created, + if (emails != null) 'emails': emails!.toMap(), + 'isActive': isActive, + 'isNewUser': isNewUser, + 'isRegistered': isRegistered, + 'isVerified': isVerified, + 'lastLogin': lastLogin, + 'lastUpdated': lastUpdated, + 'loginProvider': loginProvider, + 'oldestDataUpdated': oldestDataUpdated, + if (profileAsMap != null) 'profile': profileAsMap, + 'registered': registered, + 'regSource': regSource, + 'regToken': regToken, + if (sessionInfo != null) 'sessionInfo': sessionInfo!.toMap(), + 'signatureTimestamp': signatureTimestamp, + 'socialProviders': socialProviders, + 'UID': UID, + 'UIDSignature': UIDSignature, + 'verified': verified, + }; + } +} diff --git a/lib/src/web/static_interop/response/response.dart b/lib/src/web/static_interop/response/response.dart new file mode 100644 index 00000000..625944b1 --- /dev/null +++ b/lib/src/web/static_interop/response/response.dart @@ -0,0 +1,43 @@ +import 'dart:js_interop'; + +export 'conflicting_account_response.dart'; +export 'login_response.dart'; + +/// The extension type for the Gigya `Response` class. +/// +/// See also: https://help.sap.com/docs/SAP_CUSTOMER_DATA_CLOUD/8b8d6fffe113457094a17701f63e3d6a/416d55f070b21014bbc5a10ce4041860.html +@JS() +@anonymous +@staticInterop +extension type Response(JSObject _) { + /// The version of the Gigya API that was used. + external int? get apiVersion; + + /// The unique identifier of the transaction. + external String get callId; + + /// Get the error details. + /// + /// This getter can be redefined (not overridden) by extension types, + /// that have [Response] as representation type, + /// to include additional details. + Map get details { + return { + 'errorDetails': errorDetails, + }; + } + + /// The response error code. + /// + /// A value of zero indicates success. + /// Any other number indicates a failure. + /// + /// See also: https://help.sap.com/docs/SAP_CUSTOMER_DATA_CLOUD/8b8d6fffe113457094a17701f63e3d6a/416d41b170b21014bbc5a10ce4041860.html + external int get errorCode; + + /// The additional details of the encountered error. + external String? get errorDetails; + + /// The error message for the given [errorCode]. + external String? get errorMessage; +} diff --git a/lib/src/web/static_interop/screenset_event/after_screen_load_event.dart b/lib/src/web/static_interop/screenset_event/after_screen_load_event.dart new file mode 100644 index 00000000..01f56bda --- /dev/null +++ b/lib/src/web/static_interop/screenset_event/after_screen_load_event.dart @@ -0,0 +1,51 @@ +import 'dart:js_interop'; + +import '../../../models/screenset_event.dart'; +import '../models/profile.dart'; + +/// The extension type for the after screen load event of the `Account.showScreenset` event stream. +/// +/// See: https://help.sap.com/docs/SAP_CUSTOMER_DATA_CLOUD/8b8d6fffe113457094a17701f63e3d6a/413a5b7170b21014bbc5a10ce4041860.html?locale=en-US#onafterscreenload-event-data +@JS() +@anonymous +@staticInterop +extension type AfterScreenLoadEvent(JSObject _) { + /// The name of the current screen. + external String get currentScreen; + + /// An object containing any custom data fields related to the user. + external JSAny? get data; + + /// The name of the event. + external String get eventName; + + /// The name of the form. + external String get form; + + /// The profile data of the user. + /// This is null if the user is not logged in. + external Profile? get profile; + + /// The response of the previous screen's submit operation. + external JSAny? get response; + + /// The source plugin that generated this event. + /// The value of this field is the name of the plugin's API method, + /// e.g., 'showLoginUI', 'showCommentsUI', etc. + external String get source; + + /// Serialize this event into a [ScreensetEvent]. + ScreensetEvent serialize() { + return ScreensetEvent( + eventName, + { + 'currentScreen': currentScreen, + 'data': data.dartify(), + 'form': form, + 'profile': profile?.toMap(), + 'response': response.dartify(), + 'source': source, + }, + ); + } +} diff --git a/lib/src/web/static_interop/screenset_event/after_submit_event.dart b/lib/src/web/static_interop/screenset_event/after_submit_event.dart new file mode 100644 index 00000000..76c1e0c7 --- /dev/null +++ b/lib/src/web/static_interop/screenset_event/after_submit_event.dart @@ -0,0 +1,52 @@ +import 'dart:js_interop'; + +import '../../../models/screenset_event.dart'; +import '../models/profile.dart'; + +/// The extension type for the after submit event of the `Account.showScreenset` event stream. +/// +/// See: https://help.sap.com/docs/SAP_CUSTOMER_DATA_CLOUD/8b8d6fffe113457094a17701f63e3d6a/413a5b7170b21014bbc5a10ce4041860.html?locale=en-US#onaftersubmit-event-data +@JS() +@anonymous +@staticInterop +extension type AfterSubmitEvent(JSObject _) { + /// An object containing any custom data fields related to the user, + /// as known after the form submission. + external JSAny? get data; + + /// The name of the event. + external String get eventName; + + /// The ID of the form. + external String get form; + + /// The profile data of the user, after the submission. + /// This is null if the user is not logged in. + external Profile? get profile; + + /// The response of the form's submit operation. + external JSAny? get response; + + /// The name of the screen. + external String get screen; + + /// The source plugin that generated this event. + /// The value of this field is the name of the plugin's API method, + /// e.g., 'showLoginUI', 'showCommentsUI', etc. + external String get source; + + /// Serialize this event into a [ScreensetEvent]. + ScreensetEvent serialize() { + return ScreensetEvent( + eventName, + { + 'data': data.dartify(), + 'form': form, + 'profile': profile?.toMap(), + 'response': response.dartify(), + 'screen': screen, + 'source': source, + }, + ); + } +} diff --git a/lib/src/web/static_interop/screenset_event/after_validation_event.dart b/lib/src/web/static_interop/screenset_event/after_validation_event.dart new file mode 100644 index 00000000..362ae507 --- /dev/null +++ b/lib/src/web/static_interop/screenset_event/after_validation_event.dart @@ -0,0 +1,57 @@ +import 'dart:js_interop'; + +import '../../../models/screenset_event.dart'; +import '../models/profile.dart'; + +/// The extension type for the after validation event of the `Account.showScreenset` event stream. +/// +/// See: https://help.sap.com/docs/SAP_CUSTOMER_DATA_CLOUD/8b8d6fffe113457094a17701f63e3d6a/413a5b7170b21014bbc5a10ce4041860.html?locale=en-US#onaftervalidation +@JS() +@anonymous +@staticInterop +extension type AfterValidationEvent(JSObject _) { + /// The data object for the user. This will be empty if the user is not logged in. + external JSAny? get data; + + /// The name of the event. + external String get eventName; + + /// The ID of the form. + external String get form; + + /// The profile object for the user. + /// This will be empty if the user is not logged in. + external Profile? get profile; + + /// The name of the screen. + external String get screen; + + /// The source plugin that generated this event. + /// The value of this field is the name of the plugin's API method, + /// e.g., 'showLoginUI', 'showCommentsUI', etc. + external String get source; + + // TODO: This should be the subscriptions static interop type. + + /// The subscriptions data for the user. This will be empty if the user is not logged in. + external JSAny? get subscriptions; + + /// An object containing the data for any fields that failed validation. + external JSAny? get validationErrors; + + /// Serialize this event into a [ScreensetEvent]. + ScreensetEvent serialize() { + return ScreensetEvent( + eventName, + { + 'data': data.dartify(), + 'form': form, + 'profile': profile?.toMap(), + 'screen': screen, + 'source': source, + 'subscriptions': subscriptions.dartify(), + 'validationErrors': validationErrors.dartify(), + }, + ); + } +} diff --git a/lib/src/web/static_interop/screenset_event/before_screen_load_event.dart b/lib/src/web/static_interop/screenset_event/before_screen_load_event.dart new file mode 100644 index 00000000..1c08c09d --- /dev/null +++ b/lib/src/web/static_interop/screenset_event/before_screen_load_event.dart @@ -0,0 +1,55 @@ +import 'dart:js_interop'; + +import '../../../models/screenset_event.dart'; +import '../models/profile.dart'; + +/// The extension type for the before screen load event of the `Account.showScreenset` event stream. +/// +/// See: https://help.sap.com/docs/SAP_CUSTOMER_DATA_CLOUD/8b8d6fffe113457094a17701f63e3d6a/413a5b7170b21014bbc5a10ce4041860.html?locale=en-US#onbeforescreenload-event-data +@JS() +@anonymous +@staticInterop +extension type BeforeScreenLoadEvent(JSObject _) { + /// The name of the current screen, before the screen switch. + external String get currentScreen; + + /// The site custom data fields related to the user, + /// as known before the form submission. + external JSAny? get data; + + /// The name of the event. + external String get eventName; + + /// The name of the form that triggered this screen switch. + external String get form; + + /// The name of the screen about to be loaded. + external String get nextScreen; + + /// The user's profile data. This is null if the user is not logged in. + external Profile? get profile; + + /// The response of the previous screen's submit operation. + external JSAny? get response; + + /// The source plugin that generated this event. + /// The value of this field is the name of the plugin's API method, + /// e.g., 'showLoginUI', 'showCommentsUI', etc. + external String get source; + + /// Serialize this event into a [ScreensetEvent]. + ScreensetEvent serialize() { + return ScreensetEvent( + eventName, + { + 'currentScreen': currentScreen, + 'data': data.dartify(), + 'form': form, + 'nextScreen': nextScreen, + 'profile': profile?.toMap(), + 'response': response.dartify(), + 'source': source, + }, + ); + } +} diff --git a/lib/src/web/static_interop/screenset_event/before_submit_event.dart b/lib/src/web/static_interop/screenset_event/before_submit_event.dart new file mode 100644 index 00000000..13f1398b --- /dev/null +++ b/lib/src/web/static_interop/screenset_event/before_submit_event.dart @@ -0,0 +1,50 @@ +import 'dart:js_interop'; + +import '../../../models/screenset_event.dart'; +import '../models/profile.dart'; + +/// The extension type for the before submit event of the `Account.showScreenset` event stream. +/// +/// See: https://help.sap.com/docs/SAP_CUSTOMER_DATA_CLOUD/8b8d6fffe113457094a17701f63e3d6a/413a5b7170b21014bbc5a10ce4041860.html?locale=en-US#onbeforesubmit-event-data +@JS() +@anonymous +@staticInterop +extension type BeforeSubmitEvent(JSObject _) { + /// The name of the event. + external String get eventName; + + /// The ID of the form. + external String get form; + + /// An object containing a copy of the form data that is about to be sent. + external JSAny? get formData; + + /// The ID of the screen that is about to be loaded. + external String? get nextScreen; + + /// The current profile data of the user. This is null if the user is not logged in. + external Profile? get profile; + + /// The name of the screen. + external String get screen; + + /// The source plugin that generated this event. + /// The value of this field is the name of the plugin's API method, + /// e.g., 'showLoginUI', 'showCommentsUI', etc. + external String get source; + + /// Serialize this event into a [ScreensetEvent]. + ScreensetEvent serialize() { + return ScreensetEvent( + eventName, + { + 'form': form, + 'formData': formData.dartify(), + 'nextScreen': nextScreen, + 'profile': profile?.toMap(), + 'screen': screen, + 'source': source, + }, + ); + } +} diff --git a/lib/src/web/static_interop/screenset_event/before_validation_event.dart b/lib/src/web/static_interop/screenset_event/before_validation_event.dart new file mode 100644 index 00000000..1b911a6a --- /dev/null +++ b/lib/src/web/static_interop/screenset_event/before_validation_event.dart @@ -0,0 +1,58 @@ +import 'dart:js_interop'; + +import '../../../models/screenset_event.dart'; +import '../models/profile.dart'; + +/// The extension type for the before validation event of the `Account.showScreenset` event stream. +/// +/// See: https://help.sap.com/docs/SAP_CUSTOMER_DATA_CLOUD/8b8d6fffe113457094a17701f63e3d6a/413a5b7170b21014bbc5a10ce4041860.html?locale=en-US#onbeforevalidation +@JS() +@anonymous +@staticInterop +extension type BeforeValidationEvent(JSObject _) { + /// The data object for the user. + /// This will be empty if the user is not logged in. + external JSAny? get data; + + /// The name of the event. + external String get eventName; + + /// The ID of the form. + external String get form; + + /// An object containing the properties of the form fields. + external JSAny? get formData; + + /// The profile object for the user. This will be empty if the user is not logged in. + external Profile? get profile; + + /// The name of the screen. + external String get screen; + + /// The source plugin that generated this event. + /// The value of this field is the name of the plugin's API method, + /// e.g., 'showLoginUI', 'showCommentsUI', etc. + external String get source; + + // TODO: This should be the subscriptions static interop type. + + /// The subscriptions data for the user. + /// This will be empty if the user is not logged in. + external JSAny? get subscriptions; + + /// Serialize this event into a [ScreensetEvent]. + ScreensetEvent serialize() { + return ScreensetEvent( + eventName, + { + 'data': data.dartify(), + 'form': form, + 'formData': formData.dartify(), + 'profile': profile?.toMap(), + 'screen': screen, + 'source': source, + 'subscriptions': subscriptions.dartify(), + }, + ); + } +} \ No newline at end of file diff --git a/lib/src/web/static_interop/screenset_event/error_event.dart b/lib/src/web/static_interop/screenset_event/error_event.dart new file mode 100644 index 00000000..fed98534 --- /dev/null +++ b/lib/src/web/static_interop/screenset_event/error_event.dart @@ -0,0 +1,41 @@ +import 'dart:js_interop'; + +import '../../../models/screenset_event.dart'; + +/// The extension type for the error event of the `Account.showScreenset` event stream. +/// +/// See: https://help.sap.com/docs/SAP_CUSTOMER_DATA_CLOUD/8b8d6fffe113457094a17701f63e3d6a/413a5b7170b21014bbc5a10ce4041860.html?locale=en-US#onerror-event-data +@JS() +@anonymous +@staticInterop +extension type ErrorEvent(JSObject _) { + /// The name of the event. + external String get eventName; + + /// The ID of the form. + external String get form; + + /// The response of the encapsulated API call that failed. + external JSAny? get response; + + /// The name of the screen. + external String get screen; + + /// The source plugin that generated this event. + /// The value of this field is the name of the plugin's API method, + /// e.g., 'showLoginUI', 'showCommentsUI', etc. + external String get source; + + /// Serialize this event into a [ScreensetEvent]. + ScreensetEvent serialize() { + return ScreensetEvent( + eventName, + { + 'form': form, + 'response': response.dartify(), + 'screen': screen, + 'source': source, + }, + ); + } +} diff --git a/lib/src/web/static_interop/screenset_event/field_changed_event.dart b/lib/src/web/static_interop/screenset_event/field_changed_event.dart new file mode 100644 index 00000000..1b8a3574 --- /dev/null +++ b/lib/src/web/static_interop/screenset_event/field_changed_event.dart @@ -0,0 +1,53 @@ +import 'dart:js_interop'; + +import '../../../models/screenset_event.dart'; + +/// The extension type for the field changed event of the `Account.showScreenset` event stream. +/// +/// See: https://help.sap.com/docs/SAP_CUSTOMER_DATA_CLOUD/8b8d6fffe113457094a17701f63e3d6a/413a5b7170b21014bbc5a10ce4041860.html?locale=en-US#onfieldchanged-event-data +@JS() +@anonymous +@staticInterop +extension type FieldChangedEvent(JSObject _) { + /// The error message for the [field]. + external String? get errMsg; + + /// The name of the event. + external String get eventName; + + /// Whether the [field] is currently valid. + external bool get isValid; + + /// The name of the field that changed. + external String get field; + + /// The name of the form that contains the changed field. + external String get form; + + /// The name of the screen that contains the changed field. + external String get screen; + + /// The source plugin that generated this event. + /// The value of this field is the name of the plugin's API method, + /// e.g., 'showLoginUI', 'showCommentsUI', etc. + external String get source; + + /// The current value of the [field]. + external String? get value; + + /// Serialize this event into a [ScreensetEvent]. + ScreensetEvent serialize() { + return ScreensetEvent( + eventName, + { + 'errMsg': errMsg, + 'isValid': isValid, + 'field': field, + 'form': form, + 'screen': screen, + 'source': source, + 'value': value, + }, + ); + } +} diff --git a/lib/src/web/static_interop/screenset_event/hide_event.dart b/lib/src/web/static_interop/screenset_event/hide_event.dart new file mode 100644 index 00000000..a7ff8c9b --- /dev/null +++ b/lib/src/web/static_interop/screenset_event/hide_event.dart @@ -0,0 +1,40 @@ +import 'dart:js_interop'; + +import '../../../models/enums/screen_set_event_type.dart'; +import '../../../models/screenset_event.dart'; + +/// The extension type for the hide event of the `Account.showScreenset` event stream. +/// +/// See: https://help.sap.com/docs/SAP_CUSTOMER_DATA_CLOUD/8b8d6fffe113457094a17701f63e3d6a/413a5b7170b21014bbc5a10ce4041860.html?locale=en-US#onhide-event-data +@JS() +@anonymous +@staticInterop +extension type HideEvent(JSObject _) { + /// The name of the event. + external String get eventName; + + /// The reason why the screen-set closed, which is either 'canceled' or 'finished'. + external String get reason; + + /// The source plugin that generated this event. + /// The value of this field is the name of the plugin's API method, + /// e.g., 'showLoginUI', 'showCommentsUI', etc. + external String get source; + + /// Serialize this event into a [ScreensetEvent]. + ScreensetEvent serialize() { + switch (reason) { + case 'canceled': + return ScreensetEvent( + ScreenSetEventType.onCancel.name, + {'source': source}, + ); + case 'finished': + default: + return ScreensetEvent( + ScreenSetEventType.onHide.name, + {'source': source}, + ); + } + } +} diff --git a/lib/src/web/static_interop/screenset_event/screenset_events.dart b/lib/src/web/static_interop/screenset_event/screenset_events.dart new file mode 100644 index 00000000..ddee5f64 --- /dev/null +++ b/lib/src/web/static_interop/screenset_event/screenset_events.dart @@ -0,0 +1,10 @@ +export './after_screen_load_event.dart'; +export './after_submit_event.dart'; +export './after_validation_event.dart'; +export './before_screen_load_event.dart'; +export './before_submit_event.dart'; +export './before_validation_event.dart'; +export './error_event.dart'; +export './field_changed_event.dart'; +export './hide_event.dart'; +export './submit_event.dart'; diff --git a/lib/src/web/static_interop/screenset_event/submit_event.dart b/lib/src/web/static_interop/screenset_event/submit_event.dart new file mode 100644 index 00000000..7c32b3a1 --- /dev/null +++ b/lib/src/web/static_interop/screenset_event/submit_event.dart @@ -0,0 +1,44 @@ +import 'dart:js_interop'; + +import '../../../models/screenset_event.dart'; + +/// The extension type for the submit event of the `Account.showScreenset` event stream. +/// +/// See: https://help.sap.com/docs/SAP_CUSTOMER_DATA_CLOUD/8b8d6fffe113457094a17701f63e3d6a/413a5b7170b21014bbc5a10ce4041860.html?locale=en-US#onsubmit-event-data +@JS() +@anonymous +@staticInterop +extension type SubmitEvent(JSObject _) { + /// An object that contains the user's account information. + /// Only the `data`, `profile` and `subscriptions` are included. + /// + /// This is null if the user is not logged in. + external JSAny? get accountInfo; + + /// The name of the event. + external String get eventName; + + /// The name of the form that is about to be submitted. + external String get form; + + /// The name of the screen that contains the form that is about to be submitted. + external String get screen; + + /// The source plugin that generated this event. + /// The value of this field is the name of the plugin's API method, + /// e.g., 'showLoginUI', 'showCommentsUI', etc. + external String get source; + + /// Serialize this event into a [ScreensetEvent]. + ScreensetEvent serialize() { + return ScreensetEvent( + eventName, + { + 'accountInfo': accountInfo.dartify(), + 'form': form, + 'screen': screen, + 'source': source, + }, + ); + } +} diff --git a/lib/src/web/static_interop/session.dart b/lib/src/web/static_interop/session.dart new file mode 100644 index 00000000..f84a7326 --- /dev/null +++ b/lib/src/web/static_interop/session.dart @@ -0,0 +1,16 @@ +import 'dart:js_interop'; + +import 'parameters/basic.dart'; + +/// This extension type represents the session namespace. +/// +/// See also: https://help.sap.com/docs/SAP_CUSTOMER_DATA_CLOUD/8b8d6fffe113457094a17701f63e3d6a/7fc882712f2e4011982171ea612466ca.html +@JS() +@staticInterop +extension type Session(JSObject _) { + /// Verify the current session. + /// + /// This function receives a [BasicParameters] instance as argument, + /// and has [JSVoid] as return type. + external JSFunction verify; +} diff --git a/lib/src/web/static_interop/window.dart b/lib/src/web/static_interop/window.dart new file mode 100644 index 00000000..a7b3428a --- /dev/null +++ b/lib/src/web/static_interop/window.dart @@ -0,0 +1,25 @@ +import 'dart:js_interop'; + +import 'package:web/web.dart' show Window; + +import 'gigya_web_sdk.dart'; + +/// The static interop extension type for the [Window]. +@JS() +@staticInterop +extension type GigyaWindow(Window window) { + /// Get the `gigya` JavaScript object on the [Window]. + /// + /// If `window.gigya` is null or undefined, this getter returns null. + external GigyaWebSdk? get gigya; + + /// Set the `onGigyaServiceReady` function on the [Window]. + /// + /// This function is called when the Gigya Web SDK has been initialized. + /// + /// The [onReady] function receives a nullable [JSString] as argument, + /// and has [JSVoid] as return type. + /// + /// See https://help.sap.com/docs/SAP_CUSTOMER_DATA_CLOUD/8b8d6fffe113457094a17701f63e3d6a/417f6b5e70b21014bbc5a10ce4041860.html#ongigyaserviceready + external set onGigyaServiceReady(JSFunction onReady); +} diff --git a/lib/src/web/web_interruption_resolver.dart b/lib/src/web/web_interruption_resolver.dart new file mode 100644 index 00000000..2519c097 --- /dev/null +++ b/lib/src/web/web_interruption_resolver.dart @@ -0,0 +1,94 @@ +import 'dart:async'; +import 'dart:js_interop'; +import 'package:js/js_util.dart' show allowInterop; + +import '../models/conflicting_account.dart'; +import '../models/gigya_error.dart'; +import '../services/interruption_resolver.dart'; +import 'static_interop/gigya_web_sdk.dart'; +import 'static_interop/models/conflicting_account.dart'; +import 'static_interop/parameters/conflicting_account.dart'; +import 'static_interop/response/response.dart'; + +// TODO: implement linkToSite & linkToSocial in _StaticInteropLinkAccountResolver +// TODO: return `_StaticInteropLinkAccountResolver` when required +// TODO: implement setAccount in _StaticInteropPendingRegistrationResolver, using `account.setAccountInfo` endpoint + +/// This class represents an [InterruptionResolver] +/// that uses static interop for its implementation. +/// +/// See also: https://help.sap.com/docs/SAP_CUSTOMER_DATA_CLOUD/8b8d6fffe113457094a17701f63e3d6a/416d41b170b21014bbc5a10ce4041860.html#error-code-definitions-table +class WebInterruptionResolverFactory extends InterruptionResolverFactory { + /// The default constructor. + const WebInterruptionResolverFactory(); + + @override + InterruptionResolver? fromErrorCode(GigyaError exception) { + // TODO: what is the error code for the `_StaticInteropLinkAccountResolver`? + switch (exception.errorCode) { + case 206001: + return const _PendingRegistrationResolver(); + case 206002: + return PendingVerificationResolver(exception.registrationToken ?? ''); + default: + return null; + } + } +} + +class _LinkAccountResolver extends LinkAccountResolver { + _LinkAccountResolver({required this.registrationToken}) { + _conflictingAccounts = _getConflictingAccounts(); + } + + final String registrationToken; + + late final Future? _conflictingAccounts; + + @override + Future? get conflictingAccount => _conflictingAccounts; + + /// Get the conflicting accounts for the user. + Future _getConflictingAccounts() { + final Completer completer = Completer(); + final ConflictingAccountParameters parameters = ConflictingAccountParameters( + regToken: registrationToken, + callback: allowInterop((ConflictingAccountResponse response) { + if (completer.isCompleted) { + return; + } + + if (response.baseResponse.errorCode == 0) { + final WebConflictingAccount? account = response.conflictingAccount; + + completer.complete( + ConflictingAccount( + loginID: account?.loginID, + loginProviders: account?.loginProviders ?? const [], + ), + ); + } else { + completer.completeError( + GigyaError( + apiVersion: response.baseResponse.apiVersion, + callId: response.baseResponse.callId, + details: response.baseResponse.details, + errorCode: response.baseResponse.errorCode, + ), + ); + } + }).toJS, + ); + + GigyaWebSdk.instance.accounts.getConflictingAccount.callAsFunction( + null, + parameters, + ); + + return completer.future; + } +} + +class _PendingRegistrationResolver extends PendingRegistrationResolver { + const _PendingRegistrationResolver(); +} diff --git a/lib/src/web/web_screenset_delegate.dart b/lib/src/web/web_screenset_delegate.dart new file mode 100644 index 00000000..8ccf2760 --- /dev/null +++ b/lib/src/web/web_screenset_delegate.dart @@ -0,0 +1,281 @@ +import 'dart:async'; +import 'dart:js_interop'; + +import 'package:js/js_util.dart' show allowInterop; + +import '../models/screenset_event.dart'; +import 'static_interop/gigya_web_sdk.dart'; +import 'static_interop/global_events/login_event.dart'; +import 'static_interop/parameters/add_event_handlers_parameters.dart'; +import 'static_interop/parameters/screenset_parameters.dart'; +import 'static_interop/screenset_event/screenset_events.dart'; + +/// This typedef defines the function signature for the handler +/// of the screenset before screen load event. +typedef _BeforeScreenLoadEventHandler = Object? Function(ScreensetEvent event); + +/// This typedef defines the function signature for the handler +/// of the screenset before submit event. +typedef _BeforeSubmitEventHandler = bool Function(ScreensetEvent event); + +/// This typedef defines the function signature for the handler +/// of the screenset before validation event. +typedef _BeforeValidationEventHandler = Future?> Function( + ScreensetEvent event, +); + +/// This typedef defines the function signature for the handler +/// of the screenset error event. +typedef _ErrorEventHandler = Map? Function(ScreensetEvent event); + +/// This class handles showing and hiding screensets for the web. +class WebScreensetDelegate { + /// Create an instance of [WebScreensetDelegate]. + const WebScreensetDelegate(); + + /// Clear any handlers for global events, + /// that were set by [_setupHandlersForGlobalEvents]. + void _clearGlobalEventHandlers() { + // Unset the handlers for the global events by setting them to null. + GigyaWebSdk.instance.accounts.addEventHandlers.callAsFunction( + null, + AddEventHandlersParameters(), + ); + } + + /// Create the parameters for showing a screenset. + /// + /// This method will also setup the handlers for the screenset events, + /// using the specified functions in the [parameters]. + ShowScreensetParameters _createShowScreensetParameters( + String screenSet, + StreamController controller, + Map parameters, + ) { + return ShowScreensetParameters( + authFlow: parameters['authFlow'] as String? ?? 'popup', + communicationLangByScreenSet: parameters['communicationLangByScreenSet'] as bool? ?? true, + deviceType: parameters['deviceType'] as String? ?? 'desktop', + dialogStyle: parameters['dialogStyle'] as String? ?? 'modern', + enabledProviders: parameters['enabledProviders'] as String?, + googlePlayAppID: parameters['googlePlayAppID'] as String?, + lang: parameters['lang'] as String?, + mobileScreenSet: parameters['mobileScreenSet'] as String?, + redirectMethod: parameters['redirectMethod'] as String? ?? 'get', + redirectURL: parameters['redirectURL'] as String?, + regSource: parameters['regSource'] as String?, + regToken: parameters['regToken'] as String?, + screenSet: screenSet, + sessionExpiration: parameters['sessionExpiration'] as int?, + startScreen: parameters['startScreen'] as String?, + onAfterScreenLoad: allowInterop((AfterScreenLoadEvent event) { + if (!controller.isClosed) { + controller.add(event.serialize()); + } + }).toJS, + onAfterSubmit: allowInterop((AfterSubmitEvent event) { + if (!controller.isClosed) { + controller.add(event.serialize()); + } + }).toJS, + onAfterValidation: allowInterop((AfterValidationEvent event) { + if (!controller.isClosed) { + controller.add(event.serialize()); + } + }).toJS, + onBeforeScreenLoad: allowInterop( + (BeforeScreenLoadEvent event) { + // Abort the screen load if the controller was closed. + if (controller.isClosed) { + return false; + } + + final ScreensetEvent screensetEvent = event.serialize(); + + controller.add(screensetEvent); + + final Object? handler = parameters['onBeforeScreenLoad']; + + // Continue loading the screen if no handler is set. + if (handler is! _BeforeScreenLoadEventHandler) { + return true; + } + + final Object? result = handler(screensetEvent); + + switch (result) { + // The handler can return a Map, which contains a `nextScreen` key. + case {'nextScreen': final String _}: + return result.jsify(); + // The handler decided that the screen should be loaded or not. + case true: + case false: + return result; + // The handler result is not valid, continue loading the screen. + default: + return true; + } + }, + ).toJS, + onBeforeSubmit: allowInterop( + (BeforeSubmitEvent event) { + // Abort the submission if the controller was closed. + if (controller.isClosed) { + return false; + } + + final ScreensetEvent screensetEvent = event.serialize(); + + controller.add(screensetEvent); + + final Object? handler = parameters['onBeforeSubmit']; + + if (handler is _BeforeSubmitEventHandler) { + return handler(screensetEvent); + } + + // If there is no handler, do not abort the submission. + return true; + }, + ).toJS, + onBeforeValidation: allowInterop( + (BeforeValidationEvent event) { + if (controller.isClosed) { + return Future?>.value().toJS; + } + + final ScreensetEvent screensetEvent = event.serialize(); + + controller.add(screensetEvent); + + final Object? handler = parameters['onBeforeValidation']; + + // Resolve with no value if there is no handler. + // Otherwise the screenset will fail. + if (handler is! _BeforeValidationEventHandler) { + return Future?>.value().toJS; + } + + // Resolve the Future with no value when the handler fails. + // Otherwise the screenset will fail. + return handler(screensetEvent).catchError( + (Object error, StackTrace stackTrace) { + return Future?>.value(); + }, + ).toJS; + }, + ).toJS, + onError: allowInterop( + (ErrorEvent event) { + if (controller.isClosed) { + return null; + } + + final ScreensetEvent screensetEvent = event.serialize(); + + controller.add(screensetEvent); + + final Object? handler = parameters['onError']; + + if (handler is _ErrorEventHandler) { + return handler(screensetEvent).jsify(); + } + + return null; + }, + ).toJS, + onFieldChanged: allowInterop((FieldChangedEvent event) { + if (!controller.isClosed) { + controller.add(event.serialize()); + } + }).toJS, + onHide: allowInterop((HideEvent event) { + if (!controller.isClosed) { + controller.add(event.serialize()); + } + }).toJS, + onSubmit: allowInterop((SubmitEvent event) { + if (!controller.isClosed) { + controller.add(event.serialize()); + } + }).toJS, + ); + } + + /// Setup the handlers for the global events. + void _setupHandlersForGlobalEvents( + StreamController controller, + ) { + GigyaWebSdk.instance.accounts.addEventHandlers.callAsFunction( + null, + AddEventHandlersParameters( + onAfterResponse: allowInterop( + (JSAny? event) { + if (event == null || controller.isClosed) { + return; + } + + final Map eventData = event.dartify() as Map; + + controller.add( + ScreensetEvent( + eventData['eventName'] as String? ?? 'onAfterResponse', + eventData.cast(), + ), + ); + }, + ).toJS, + onLogin: allowInterop( + (LoginEvent event) { + if (controller.isClosed) { + return; + } + + controller.add(ScreensetEvent('onLogin', event.toMap())); + }, + ).toJS, + ), + ); + } + + /// Hide the screenset with the given [name]. + void hideScreenset(String name) { + GigyaWebSdk.instance.accounts.hideScreenSet.callAsFunction( + null, + HideScreensetParameters(screenSet: name), + ); + } + + /// Show the screenset with the given [name]. + /// + /// The [parameters] are used to configure the screenset. + Stream showScreenSet( + String name, { + Map parameters = const {}, + }) { + late StreamController controller; + + controller = StreamController.broadcast(onListen: () { + // First setup the handlers for the global events. + _setupHandlersForGlobalEvents(controller); + + GigyaWebSdk.instance.accounts.showScreenSet.callAsFunction( + null, + _createShowScreensetParameters(name, controller, parameters), + ); + }, onCancel: () { + // When the screenset subscription is cancelled, + // clean up the global event handlers. + // At most one screenset can be displayed at once. + _clearGlobalEventHandlers(); + + // Automatically hide the screenset when the last listener + // of the stream cancels its subscription. + hideScreenset(name); + + unawaited(controller.close()); + }); + + return controller.stream; + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 59c0c0e3..6d24389e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,13 +4,18 @@ version: 1.0.1 homepage: https://github.com/SAP/gigya-flutter-plugin environment: - sdk: ">=2.18.0 <4.0.0" - flutter: ">=3.3.0" + sdk: ">=3.2.0-140.0.dev <4.0.0" + flutter: ">=3.10.0" dependencies: flutter: sdk: flutter + flutter_web_plugins: + sdk: flutter + + js: ">=0.6.6" plugin_platform_interface: ^2.0.2 + web: ^0.2.1-beta dev_dependencies: flutter_lints: ^2.0.0 @@ -19,7 +24,6 @@ dev_dependencies: sdk: flutter flutter: - plugin: platforms: android: @@ -27,6 +31,9 @@ flutter: pluginClass: GigyaFlutterPlugin ios: pluginClass: GigyaFlutterPlugin + web: + pluginClass: GigyaFlutterPluginWeb + fileName: src/web/gigya_flutter_plugin_web.dart false_secrets: - example/android/app/src/main/res/values/strings.xml