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..9d827d7d 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..8b174d1a 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: ccbbfbf29732ee1b391c4c6d13e7032d851f675f47365e81904e4c6fd669c854 + url: "https://pub.dev" + source: hosted + version: "0.2.2-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.3.0-1.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..a7d93b47 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 { @@ -60,6 +60,15 @@ class MethodChannelGigyaFlutterPlugin extends GigyaFlutterPluginPlatform { } } + @override + Future> finalizeRegistration( + String registrationToken, { + String? include, + bool allowAccountsLinking = false, + }) { + throw UnsupportedError('Calling finalizeRegistration() is only supported on the web.'); + } + @override Future> forgotPassword(String loginId) async { try { @@ -118,11 +127,16 @@ class MethodChannelGigyaFlutterPlugin extends GigyaFlutterPluginPlatform { } } + @override + Future initRegistration({required bool isLite}) { + throw UnsupportedError('Calling initRegistration() is only supported on the web.'); + } + @override Future initSdk({ required String apiDomain, required String apiKey, - bool forceLogout = true, + bool forceLogout = false, }) async { // First, initialize the Gigya SDK. try { @@ -350,26 +364,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..e568b35b 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 Map? session = + (json['sessionInfo'] 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 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 creation timestamp of the account. - final Object? createdTimestamp; // TODO: this should be a `DateTime?`. + /// The custom data for the account, which is not part of the [profile]. + final Map? data; + + /// 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. - final Object? lastLoginTimestamp; // TODO: this should be a `DateTime?`. + /// The timestamp of the last login of the user. + final DateTime? lastLogin; - /// 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,30 @@ 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(), - if (accountProfile != null) 'profile': accountProfile.toJson(), - 'registered': registered, - 'registeredTimestamp': registeredTimestamp?.toString(), - 'signatureTimestamp': signatureTimestamp?.toString(), + 'oldestDataUpdated': oldestDataUpdated?.toIso8601String(), + if (profile != null) 'profile': profile!.toJson(), + '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..5fc9e3b3 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( @@ -54,6 +57,15 @@ abstract class GigyaFlutterPluginPlatform extends PlatformInterface { throw UnimplementedError('addConnection() is not implemented.'); } + /// Finalize a pending registration. + Future> finalizeRegistration( + String registrationToken, { + String? include, + bool allowAccountsLinking = false, + }) { + throw UnimplementedError('finalizeRegistration() is not implemented.'); + } + /// Start the forgot password flow for the given [loginId]. Future> forgotPassword(String loginId) { throw UnimplementedError('forgotPassword() is not implemented.'); @@ -78,13 +90,26 @@ abstract class GigyaFlutterPluginPlatform extends PlatformInterface { throw UnimplementedError('getSession() is not implemented.'); } + /// Start a registration flow. + /// + /// The [isLite] parameter determines if a full + /// or a lite account registration should be started. + /// + /// This is typically only required for lite accounts, + /// as [register] automatically starts a registration. + Future initRegistration({ + required bool isLite, + }) { + throw UnimplementedError('initRegistration() is not implemented.'); + } + /// Initialize the Gigya SDK with the given [apiKey] and [apiDomain]. /// /// If [forceLogout] is true, the user will be logged out. 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..a592c469 --- /dev/null +++ b/lib/src/web/gigya_flutter_plugin_web.dart @@ -0,0 +1,376 @@ +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 '../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/models/profile.dart'; +import 'static_interop/parameters/basic.dart'; +import 'static_interop/parameters/login.dart'; +import 'static_interop/parameters/registration.dart'; +import 'static_interop/response/registration_response.dart'; +import 'static_interop/response/response.dart'; +import 'static_interop/window.dart'; +import 'web_account_delegate.dart'; +import 'web_interruption_resolver.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 WebAccountDelegate _accountDelegate = const WebAccountDelegate(); + + @override + Future> finalizeRegistration( + String registrationToken, { + String? include, + bool allowAccountsLinking = false, + }) { + final Completer> completer = Completer>(); + + GigyaWebSdk.instance.accounts.finalizeRegistration.callAsFunction( + null, + FinalizeRegistrationParameters( + allowAccountsLinking: allowAccountsLinking, + include: include, + regToken: registrationToken, + 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, + ), + ); + + return completer.future; + } + + @override + Future initRegistration({required bool isLite}) { + final Completer initRegistrationCompleter = Completer(); + + GigyaWebSdk.instance.accounts.initRegistration.callAsFunction( + null, + InitRegistrationParameters( + isLite: isLite, + callback: allowInterop( + (InitRegistrationResponse response) { + if (initRegistrationCompleter.isCompleted) { + return; + } + + if (response.baseResponse.errorCode == 0) { + initRegistrationCompleter.complete(response.regToken ?? ''); + } else { + initRegistrationCompleter.completeError( + GigyaError( + apiVersion: response.baseResponse.apiVersion, + callId: response.baseResponse.callId, + details: response.baseResponse.details, + errorCode: response.baseResponse.errorCode, + ), + ); + } + }, + ).toJS, + ), + ); + + return initRegistrationCompleter.future; + } + + @override + Future> getAccount({ + bool invalidate = false, + Map parameters = const {}, + }) async { + // Apparently, web does not support the invalidate argument. + return _accountDelegate.getAccount(parameters: parameters); + } + + @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.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 + Future> register({ + required String loginId, + required String password, + Map parameters = const {}, + }) async { + // Start registration by retrieving the regToken. + // If it was already provided, skip the initRegistration step. + String regToken = parameters['regToken'] as String? ?? ''; + + if (regToken.isEmpty) { + regToken = await initRegistration( + isLite: parameters['isLite'] as bool? ?? false, + ); + } + + final Map? profile = parameters['profile'] as Map?; + final Completer> registrationCompleter = Completer>(); + + GigyaWebSdk.instance.accounts.register.callAsFunction( + null, + RegistrationParameters( + // Explicitly finalize using the register endpoint. + // This way, calling `finalizeRegistration` is not needed. + // For lite accounts, the `accounts.register` endpoint is never used anyway. + // That flow uses `accounts.initRegistration` and `accounts.setAccountInfo` instead. + finalizeRegistration: true, + regToken: regToken, + email: loginId, + password: password, + captchaToken: parameters['captchaToken'] as String?, + include: parameters['include'] as String?, + lang: parameters['lang'] as String?, + profile: profile == null ? null : Profile.fromMap(profile), + regSource: parameters['regSource'] as String?, + secretAnswer: parameters['secretAnswer'] as String?, + secretQuestion: parameters['secretQuestion'] as String?, + sessionExpiration: parameters['sessionExpiration'] as int?, + siteUID: parameters['siteUID'] as String?, + callback: allowInterop( + (LoginResponse response) { + if (registrationCompleter.isCompleted) { + return; + } + + if (response.baseResponse.errorCode == 0) { + registrationCompleter.complete(response.toMap()); + } else { + // The error response does not seem to return the regToken. + // Forward it manually. + registrationCompleter.completeError( + GigyaError( + apiVersion: response.baseResponse.apiVersion, + callId: response.baseResponse.callId, + details: { + ...response.baseResponse.details, + 'regToken': regToken, + }, + errorCode: response.baseResponse.errorCode, + ), + ); + } + }, + ).toJS, + ), + ); + + return registrationCompleter.future; + } + + @override + Future> setAccount(Map account) { + return _accountDelegate.setAccount(account); + } +} diff --git a/lib/src/web/static_interop/account.dart b/lib/src/web/static_interop/account.dart new file mode 100644 index 00000000..4fc79b59 --- /dev/null +++ b/lib/src/web/static_interop/account.dart @@ -0,0 +1,65 @@ +import 'dart:js_interop'; + +import './session.dart'; +import 'parameters/account.dart'; +import 'parameters/basic.dart'; +import 'parameters/login.dart'; +import 'parameters/registration.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 _) { + /// Finalize a pending registration. + /// + /// This function receives a [FinalizeRegistrationParameters] instance as argument, + /// and has [JSVoid] as return type. + external JSFunction finalizeRegistration; + + /// Get the account info of a user. + /// + /// This function receives a [GetAccountParameters] instance as argument, + /// and has [JSVoid] as return type. + external JSFunction getAccountInfo; + + /// Get the conflicting accounts of the user. + /// + /// This function receives a [ConflictingAccountParameters] instance as argument, + /// and has [JSVoid] as return type. + external JSFunction getConflictingAccount; + + /// Start a registration. + /// + /// This function receives an [InitRegistrationParameters] instance as argument, + /// and has [JSVoid] as return type. + external JSFunction initRegistration; + + /// 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; + + /// Register a new user using the given parameters. + /// + /// This function receives a [RegistrationParameters] instance as argument, + /// and has [JSVoid] as return type. + external JSFunction register; + + /// Get the `gigya.accounts.session` namespace. + external Session get session; + + /// Update the user's account info. + /// + /// This function receives a [SetAccountParameters] instance as argument, + /// and has [JSVoid] as return type. + external JSFunction setAccountInfo; +} 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/models/account_login_id.dart b/lib/src/web/static_interop/models/account_login_id.dart new file mode 100644 index 00000000..3bdd652f --- /dev/null +++ b/lib/src/web/static_interop/models/account_login_id.dart @@ -0,0 +1,31 @@ +import 'dart:js_interop'; + +/// The extension type for the login id of a user. +@JS() +@anonymous +@staticInterop +extension type AccountLoginId(JSObject _) { + @JS('emails') + external JSArray get _emails; + + @JS('unverifiedEmails') + external JSArray get _unverifiedEmails; + + /// The list of verified email addresses of the user. + List get emails => _emails.toDart.cast(); + + /// The list of unverified email addresses of the user. + List get unverifiedEmails => _unverifiedEmails.toDart.cast(); + + /// The username of the user. + external String? get username; + + /// Convert this object into a map. + Map toMap() { + return { + 'emails': emails, + 'unverifiedEmails': unverifiedEmails, + 'username': username, + }; + } +} 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..a8cab328 --- /dev/null +++ b/lib/src/web/static_interop/models/certification.dart @@ -0,0 +1,53 @@ +import 'dart:js_interop'; + +/// The extension type for the Certification object. +@JS() +@anonymous +@staticInterop +extension type Certification._(JSObject _) implements JSObject { + /// Create a new [Certification] instance. + external factory Certification({ + String? authority, + String? endDate, + String? name, + String? number, + String? startDate, + }); + + /// Create a new [Certification] instance from the given [map]. + factory Certification.fromMap(Map map) { + return Certification( + authority: map['authority'] as String?, + endDate: map['endDate'] as String?, + name: map['name'] as String?, + number: map['number'] as String?, + startDate: map['startDate'] as String?, + ); + } + + /// 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..f858d988 --- /dev/null +++ b/lib/src/web/static_interop/models/education.dart @@ -0,0 +1,59 @@ +import 'dart:js_interop'; + +/// The extension type for the Education object. +@JS() +@anonymous +@staticInterop +extension type Education._(JSObject _) implements JSObject { + /// Construct a new [Education] instance. + external factory Education({ + String? degree, + String? endYear, + String? fieldOfStudy, + String? school, + String? schoolType, + String? startYear, + }); + + /// Create a new [Education] instance from the given [map]. + factory Education.fromMap(Map map) { + return Education( + degree: map['degree'] as String?, + endYear: map['endYear'] as String?, + fieldOfStudy: map['fieldOfStudy'] as String?, + school: map['school'] as String?, + schoolType: map['schoolType'] as String?, + startYear: map['startYear'] as String?, + ); + } + + /// 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..6d928d13 --- /dev/null +++ b/lib/src/web/static_interop/models/emails.dart @@ -0,0 +1,37 @@ +import 'dart:js_interop'; + +/// The extension type for the emails object. +@JS() +@anonymous +@staticInterop +extension type Emails._(JSObject _) { + /// Create a new [Emails] instance. + external factory Emails({ + JSArray unverified, + JSArray verified, + }); + + @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..a0a41874 --- /dev/null +++ b/lib/src/web/static_interop/models/favorites.dart @@ -0,0 +1,142 @@ +import 'dart:js_interop'; + +/// The extension type for the `Favorite` object. +@JS() +@anonymous +@staticInterop +extension type Favorite._(JSObject _) implements JSObject { + /// Construct a new [Favorite] instance. + external factory Favorite({ + String? category, + String? id, + String? name, + }); + + /// Create a new [Favorite] instance from the given [map]. + factory Favorite.fromMap(Map map) { + return Favorite( + category: map['category'] as String?, + id: map['id'] as String?, + name: map['name'] as String?, + ); + } + + /// 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 _) { + /// Construct a new [Favorites] instance. + external factory Favorites({ + JSArray activities, + JSArray books, + JSArray interests, + JSArray movies, + JSArray music, + JSArray television, + }); + + /// Create a new [Favorites] instance from the given [map]. + factory Favorites.fromMap(Map map) { + final List> activities = + map['activities'] as List>? ?? const >[]; + final List> books = + map['books'] as List>? ?? const >[]; + final List> interests = + map['interests'] as List>? ?? const >[]; + final List> movies = + map['movies'] as List>? ?? const >[]; + final List> music = + map['music'] as List>? ?? const >[]; + final List> television = + map['television'] as List>? ?? const >[]; + + return Favorites( + activities: activities.map(Favorite.fromMap).toList().toJS, + books: books.map(Favorite.fromMap).toList().toJS, + interests: interests.map(Favorite.fromMap).toList().toJS, + movies: movies.map(Favorite.fromMap).toList().toJS, + music: music.map(Favorite.fromMap).toList().toJS, + television: television.map(Favorite.fromMap).toList().toJS, + ); + } + + @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..ebbe4261 --- /dev/null +++ b/lib/src/web/static_interop/models/like.dart @@ -0,0 +1,47 @@ +import 'dart:js_interop'; + +/// The extension type for the `Like` object. +@JS() +@anonymous +@staticInterop +extension type Like._(JSObject _) implements JSObject { + /// Create a new [Like] instance. + external factory Like({ + String? category, + String? id, + String? name, + String? time, + }); + + /// Create a new [Like] instance from the given [map]. + factory Like.fromMap(Map map) { + return Like( + category: map['category'] as String?, + id: map['id'] as String?, + name: map['name'] as String?, + time: map['time'] as String?, + ); + } + + /// 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..c579ba03 --- /dev/null +++ b/lib/src/web/static_interop/models/patent.dart @@ -0,0 +1,65 @@ +import 'dart:js_interop'; + +/// The extension type for the `Patent` object. +@JS() +@anonymous +@staticInterop +extension type Patent._(JSObject _) implements JSObject { + /// Construct a new [Patent] instance. + external factory Patent({ + String? date, + String? number, + String? office, + String? status, + String? summary, + String? title, + String? url, + }); + + /// Create a new [Patent] instance from the given [map]. + factory Patent.fromMap(Map map) { + return Patent( + date: map['date'] as String?, + number: map['number'] as String?, + office: map['office'] as String?, + status: map['status'] as String?, + summary: map['summary'] as String?, + title: map['title'] as String?, + url: map['url'] as String?, + ); + } + + /// 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..d3c82878 --- /dev/null +++ b/lib/src/web/static_interop/models/phone.dart @@ -0,0 +1,35 @@ +import 'dart:js_interop'; + +/// The extension type for the `Phone` object. +@JS() +@anonymous +@staticInterop +extension type Phone._(JSObject _) implements JSObject { + /// Construct a new [Phone] instance. + external factory Phone({ + String? number, + String? type, + }); + + /// Create a new [Phone] instance from the given [map]. + factory Phone.fromMap(Map map) { + return Phone( + number: map['number'] as String?, + type: map['type'] as String?, + ); + } + + /// 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..71de7ac6 --- /dev/null +++ b/lib/src/web/static_interop/models/profile.dart @@ -0,0 +1,403 @@ +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 _) { + /// Construct a new [Profile] instance. + external factory Profile({ + String? activities, + String? address, + int? age, + String? bio, + int? birthDay, + int? birthMonth, + int? birthYear, + JSArray certifications, + String? city, + String? country, + JSArray education, + String? educationLevel, + String? email, + Favorites favorites, + String? firstName, + int? followersCount, + int? followingCount, + String? gender, + String? hometown, + String? honors, + String? industry, + String? interests, + bool? isConnected, + bool? isSiteUser, + String? languages, + String? lastName, + JSArray likes, + String? locale, + String? loginProvider, + String? loginProviderUID, + String? name, + String? nickname, + JSArray patents, + JSArray phones, + String? photoURL, + String? politicalView, + String? professionalHeadline, + String? profileURL, + JSArray providers, + String? proxyEmail, + JSArray publications, + String? relationshipStatus, + String? religion, + JSArray skills, + String? specialities, + String? state, + String? thumbnailURL, + String? timezone, + String? username, + String? verified, + JSArray work, + String? zip, + }); + + /// Create a new [Profile] instance from the given [map]. + factory Profile.fromMap(Map map) { + final List> certifications = + map['certifications'] as List>? ?? const >[]; + final List> education = + map['education'] as List>? ?? const >[]; + final List> likes = + map['likes'] as List>? ?? const >[]; + final List> patents = + map['patents'] as List>? ?? const >[]; + final List> phones = + map['phones'] as List>? ?? const >[]; + final List> publications = + map['publications'] as List>? ?? const >[]; + final List> skills = + map['skills'] as List>? ?? const >[]; + final List> work = + map['work'] as List>? ?? const >[]; + final List providers = map['providers'] as List? ?? []; + + return Profile( + activities: map['activities'] as String?, + address: map['address'] as String?, + age: map['age'] as int?, + bio: map['bio'] as String?, + birthDay: map['birthDay'] as int?, + birthMonth: map['birthMonth'] as int?, + birthYear: map['birthYear'] as int?, + certifications: certifications.map(Certification.fromMap).toList().toJS, + city: map['city'] as String?, + country: map['country'] as String?, + education: education.map(Education.fromMap).toList().toJS, + educationLevel: map['educationLevel'] as String?, + email: map['email'] as String?, + favorites: Favorites.fromMap( + map['favorites'] as Map? ?? const {}, + ), + firstName: map['firstName'] as String?, + followersCount: map['followersCount'] as int?, + followingCount: map['followingCount'] as int?, + gender: map['gender'] as String?, + hometown: map['hometown'] as String?, + honors: map['honors'] as String?, + industry: map['industry'] as String?, + interests: map['interests'] as String?, + isConnected: map['isConnected'] as bool?, + isSiteUser: map['isSiteUser'] as bool?, + languages: map['languages'] as String?, + lastName: map['lastName'] as String?, + likes: likes.map(Like.fromMap).toList().toJS, + locale: map['locale'] as String?, + loginProvider: map['loginProvider'] as String?, + loginProviderUID: map['loginProviderUID'] as String?, + name: map['name'] as String?, + nickname: map['nickname'] as String?, + patents: patents.map(Patent.fromMap).toList().toJS, + phones: phones.map(Phone.fromMap).toList().toJS, + photoURL: map['photoURL'] as String?, + politicalView: map['politicalView'] as String?, + professionalHeadline: map['professionalHeadline'] as String?, + profileURL: map['profileURL'] as String?, + providers: providers.map((String p) => p.toJS).toList().toJS, + proxyEmail: map['proxyEmail'] as String?, + publications: publications.map(Publication.fromMap).toList().toJS, + relationshipStatus: map['relationshipStatus'] as String?, + religion: map['religion'] as String?, + skills: skills.map(Skill.fromMap).toList().toJS, + specialities: map['specialities'] as String?, + state: map['state'] as String?, + thumbnailURL: map['thumbnailURL'] as String?, + timezone: map['timezone'] as String?, + username: map['username'] as String?, + verified: map['verified'] as String?, + work: work.map(Work.fromMap).toList().toJS, + zip: map['zip'] as String?, + ); + } + + /// 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..17e2442d --- /dev/null +++ b/lib/src/web/static_interop/models/publication.dart @@ -0,0 +1,53 @@ +import 'dart:js_interop'; + +/// The extension type for the `Publication` object. +@JS() +@anonymous +@staticInterop +extension type Publication._(JSObject _) implements JSObject { + /// Construct a new [Publication] instance. + external factory Publication({ + String? date, + String? publisher, + String? summary, + String? title, + String? url, + }); + + /// Create a new [Publication] instance from the given [map]. + factory Publication.fromMap(Map map) { + return Publication( + date: map['date'] as String?, + publisher: map['publisher'] as String?, + summary: map['summary'] as String?, + title: map['title'] as String?, + url: map['url'] as String?, + ); + } + + /// 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..ff59d8e5 --- /dev/null +++ b/lib/src/web/static_interop/models/skill.dart @@ -0,0 +1,41 @@ +import 'dart:js_interop'; + +/// The extension type for the `Skill` object. +@JS() +@anonymous +@staticInterop +extension type Skill._(JSObject _) implements JSObject { + /// Construct a new [Skill] instance. + external factory Skill({ + String? level, + String? skill, + int? years, + }); + + /// Create a new [Skill] instance from the given [map]. + factory Skill.fromMap(Map map) { + return Skill( + level: map['level'] as String?, + skill: map['skill'] as String?, + years: map['years'] as int?, + ); + } + + /// 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/validation_error.dart b/lib/src/web/static_interop/models/validation_error.dart new file mode 100644 index 00000000..c59b93ca --- /dev/null +++ b/lib/src/web/static_interop/models/validation_error.dart @@ -0,0 +1,27 @@ +import 'dart:js_interop'; + +/// The extension type for the validation error object. +@JS() +@anonymous +@staticInterop +extension type ValidationError(JSObject _) { + /// The validation error code. + external int get errorCode; + + /// The name of the field that caused the validation error. + /// + /// This field name uses dot notation, i.e. `profile.name`. + external String get fieldName; + + /// The validation error message. + external String get message; + + /// COnvert this validation error message to a map. + Map toMap() { + return { + 'errorCode': errorCode, + 'fieldName': fieldName, + 'message': message, + }; + } +} 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..192adca1 --- /dev/null +++ b/lib/src/web/static_interop/models/work.dart @@ -0,0 +1,71 @@ +import 'dart:js_interop'; + +/// The extension type for the `Work` object. +@JS() +@anonymous +@staticInterop +extension type Work._(JSObject _) implements JSObject { + /// Construct a new [Work] instance. + external factory Work({ + String? company, + int? companySize, + String? companyID, + String? endDate, + String? industry, + bool? isCurrent, + String? startDate, + String? title, + }); + + /// Create a new [Work] instance from the given [map]. + factory Work.fromMap(Map map) { + return Work( + company: map['company'] as String?, + companyID: map['companyID'] as String?, + companySize: map['companySize'] as int?, + endDate: map['endDate'] as String?, + industry: map['industry'] as String?, + isCurrent: map['isCurrent'] as bool?, + startDate: map['startDate'] as String?, + title: map['title'] as String?, + ); + } + + /// 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/account.dart b/lib/src/web/static_interop/parameters/account.dart new file mode 100644 index 00000000..ff8d4eb2 --- /dev/null +++ b/lib/src/web/static_interop/parameters/account.dart @@ -0,0 +1,62 @@ +import 'dart:js_interop'; + +import '../models/profile.dart'; +import '../response/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, + }); +} + +/// This extension type represents the parameters for the `Accounts.getAccountInfo` method. +@JS() +@anonymous +@staticInterop +extension type GetAccountParameters._(JSObject _) implements JSObject { + /// Create a [GetAccountParameters] instance. + /// + /// The [callback] receives a [GetAccountResponse] as argument, + /// and has [JSVoid] as return type. + external factory GetAccountParameters({ + JSFunction callback, + String? extraProfileFields, + String? include, + }); +} + +/// This extension type represents the parameters for the `Accounts.setAccountInfo` method. +@JS() +@anonymous +@staticInterop +extension type SetAccountParameters._(JSObject _) implements JSObject { + // TODO: add preferences and subscriptions object once the static interop definition is ready + + /// Create a [SetAccountParameters] instance. + /// + /// The [callback] receives a [SetAccountResponse] as argument, + /// and has [JSVoid] as return type. + external factory SetAccountParameters({ + String? addLoginEmails, + JSFunction callback, + String? conflictHandling, + JSAny? data, + String? newPassword, + String? password, + Profile? profile, + String? removeLoginEmails, + bool? requirePasswordChange, + String? secretAnswer, + String? secretQuestion, + String? username, + }); +} 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/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/registration.dart b/lib/src/web/static_interop/parameters/registration.dart new file mode 100644 index 00000000..d03959bb --- /dev/null +++ b/lib/src/web/static_interop/parameters/registration.dart @@ -0,0 +1,64 @@ +import 'dart:js_interop'; + +import '../models/profile.dart'; +import '../response/login_response.dart'; +import '../response/registration_response.dart'; + +/// The extension type for the parameters for the `Accounts.finalizeRegistration` method. +@JS() +@anonymous +@staticInterop +extension type FinalizeRegistrationParameters._(JSObject _) implements JSObject { + /// Create a new [FinalizeRegistrationParameters] instance. + /// + /// The [callback] function receives a [LoginResponse] as argument + /// and has [JSVoid] as return type. + external factory FinalizeRegistrationParameters({ + JSFunction callback, + bool allowAccountsLinking, + String? include, + String regToken, + }); +} + +/// The extension type for the parameters for the `Accounts.initRegistration` method. +@JS() +@anonymous +@staticInterop +extension type InitRegistrationParameters._(JSObject _) implements JSObject { + /// Create a new [InitRegistrationParameters] instance. + /// + /// The [callback] function receives a [InitRegistrationResponse] as argument + /// and has [JSVoid] as return type. + external factory InitRegistrationParameters({ + JSFunction callback, + bool? isLite, + }); +} + +/// The extension type for the parameters for the `Accounts.registration` method. +@JS() +@anonymous +@staticInterop +extension type RegistrationParameters._(JSObject _) implements JSObject { + /// Create a new [RegistrationParameters] instance. + /// + /// The [callback] function receives a [LoginResponse] as argument + /// and has [JSVoid] as return type. + external factory RegistrationParameters({ + JSFunction callback, + String? captchaToken, + String email, + bool finalizeRegistration, + String? include, + String? lang, + String password, + Profile? profile, + String? regSource, + String regToken, + String? secretQuestion, + String? secretAnswer, + int? sessionExpiration, + String? siteUID, + }); +} diff --git a/lib/src/web/static_interop/response/account_response.dart b/lib/src/web/static_interop/response/account_response.dart new file mode 100644 index 00000000..a1d7240c --- /dev/null +++ b/lib/src/web/static_interop/response/account_response.dart @@ -0,0 +1,145 @@ +import 'dart:js_interop'; + +import '../models/account_login_id.dart'; +import '../models/emails.dart'; +import '../models/location.dart'; +import '../models/profile.dart'; +import '../models/validation_error.dart'; +import 'response.dart'; + +// TODO: preferences, subscriptions should be in `GetAccountResponse` + +/// The extension type for the get account response. +@JS() +@anonymous +@staticInterop +extension type GetAccountResponse(Response baseResponse) { + /// The timestamp of the creation of the user. + external String? get created; + + /// The custom user data that is not part of the [profile]. + external JSAny? get data; + + /// The verified and unverified email addresses of the user. + external Emails? get emails; + + /// Whether this account is currently in transition. + /// + /// An account that is in transition cannot be modified. + external bool? get inTransition; + + /// Whether the user is active. + external bool? get isActive; + + /// 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; + + @JS('loginIDs') + external JSArray? get _loginIds; + + /// The login IDs for the user. + List? get loginIds { + return _loginIds?.toDart.cast(); + } + + /// 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 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; + + // TODO: add lockedUntil when DateTime static interop is fixed. + // Currently it is not supported, so add a static interop type for the date class + // See: https://github.com/dart-lang/sdk/issues/52021 + + /// Convert this response to a [Map]. + Map toMap() { + return { + 'created': created, + 'data': data.dartify(), + 'emails': emails?.toMap(), + 'inTransition': inTransition, + 'isActive': isActive, + 'isRegistered': isRegistered, + 'isVerified': isVerified, + 'lastLogin': lastLogin, + 'lastLoginLocation': lastLoginLocation?.toMap(), + 'lastUpdated': lastUpdated, + if (loginIds != null) + 'loginIds': loginIds!.map((AccountLoginId id) => id.toMap()).toList(), + 'loginProvider': loginProvider, + 'oldestDataUpdated': oldestDataUpdated, + 'profile': profile?.toMap(), + 'registered': registered, + 'regSource': regSource, + 'signatureTimestamp': signatureTimestamp, + 'socialProviders': socialProviders, + 'UID': UID, + 'UIDSignature': UIDSignature, + 'verified': verified, + }; + } +} + +/// The extension type for the Gigya Set Account API response. +/// +/// See also: https://help.sap.com/docs/SAP_CUSTOMER_DATA_CLOUD/8b8d6fffe113457094a17701f63e3d6a/4139777d70b21014bbc5a10ce4041860.html?locale=en-US#response-object-data-members +@JS() +@anonymous +@staticInterop +extension type SetAccountResponse(Response baseResponse) { + /// Get the error details. + /// + /// The error details will include the [validationErrors]. + Map get details { + return { + ...baseResponse.details, + 'validationErrors': validationErrors.map((ValidationError e) => e.toMap()).toList(), + }; + } + + @JS('validationErrors') + external JSArray? get _validationErrors; + + /// The validation errors for the set account payload. + List get validationErrors { + return _validationErrors?.toDart.cast() ?? const []; + } +} 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..4813b09b --- /dev/null +++ b/lib/src/web/static_interop/response/login_response.dart @@ -0,0 +1,127 @@ +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; + + /// Get the error details for this response. + /// + /// The [regToken] is included in the details. + Map get details { + return { + ...baseResponse.details, + 'regToken': regToken, + }; + } + + /// 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/registration_response.dart b/lib/src/web/static_interop/response/registration_response.dart new file mode 100644 index 00000000..3d3de1cb --- /dev/null +++ b/lib/src/web/static_interop/response/registration_response.dart @@ -0,0 +1,14 @@ +import 'dart:js_interop'; + +import 'response.dart'; + +/// The extension type for the Gigya InitRegistration API response. +/// +/// See also: https://help.sap.com/docs/SAP_CUSTOMER_DATA_CLOUD/8b8d6fffe113457094a17701f63e3d6a/4136cef070b21014bbc5a10ce4041860.html?locale=en-US#response-example +@JS() +@anonymous +@staticInterop +extension type InitRegistrationResponse(Response baseResponse) { + /// The registration token for the registration. + external String? get regToken; +} \ No newline at end of file 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..5208920f --- /dev/null +++ b/lib/src/web/static_interop/response/response.dart @@ -0,0 +1,44 @@ +import 'dart:js_interop'; + +export 'account_response.dart'; +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/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_account_delegate.dart b/lib/src/web/web_account_delegate.dart new file mode 100644 index 00000000..3136e77b --- /dev/null +++ b/lib/src/web/web_account_delegate.dart @@ -0,0 +1,101 @@ +import 'dart:async'; +import 'dart:js_interop'; +import 'package:js/js_util.dart' show allowInterop; + +import '../models/gigya_error.dart'; +import 'static_interop/gigya_web_sdk.dart'; +import 'static_interop/models/profile.dart'; +import 'static_interop/parameters/account.dart'; +import 'static_interop/response/account_response.dart'; + +/// This class handles updating and retrieving account information for the web. +class WebAccountDelegate { + /// Create an instance of [WebAccountDelegate]. + const WebAccountDelegate(); + + /// Get the account information for the current user. + Future> getAccount({ + Map parameters = const {}, + }) async { + final Completer> completer = Completer>(); + + GigyaWebSdk.instance.accounts.getAccountInfo.callAsFunction( + null, + GetAccountParameters( + extraProfileFields: parameters['extraProfileFields'] as String?, + include: parameters['include'] as String?, + callback: allowInterop( + (GetAccountResponse 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, + ), + ); + + return completer.future; + } + + /// Set the account information using the given [account]. + Future> setAccount(Map account) { + final Completer> completer = Completer>(); + + final Map? profile = account['profile'] as Map?; + final Map? data = account['data'] as Map?; + + GigyaWebSdk.instance.accounts.setAccountInfo.callAsFunction( + null, + SetAccountParameters( + addLoginEmails: account['addLoginEmails'] as String?, + conflictHandling: account['conflictHandling'] as String?, + data: data.jsify(), + newPassword: account['newPassword'] as String?, + password: account['password'] as String?, + profile: profile == null ? null : Profile.fromMap(profile), + removeLoginEmails: account['removeLoginEmails'] as String?, + requirePasswordChange: account['requirePasswordChange'] as bool?, + secretAnswer: account['secretAnswer'] as String?, + secretQuestion: account['secretQuestion'] as String?, + username: account['username'] as String?, + callback: allowInterop( + (SetAccountResponse response) { + if (completer.isCompleted) { + return; + } + + // If the call succeeded, there is no data of value to send back. + // However, if the call failed, the error details will include the validation errors. + if (response.baseResponse.errorCode == 0) { + completer.complete(const {}); + } else { + completer.completeError( + GigyaError( + apiVersion: response.baseResponse.apiVersion, + callId: response.baseResponse.callId, + details: response.details, + errorCode: response.baseResponse.errorCode, + ), + ); + } + }, + ).toJS, + ), + ); + + return completer.future; + } +} diff --git a/lib/src/web/web_interruption_resolver.dart b/lib/src/web/web_interruption_resolver.dart new file mode 100644 index 00000000..f17c4b6f --- /dev/null +++ b/lib/src/web/web_interruption_resolver.dart @@ -0,0 +1,102 @@ +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/account.dart'; +import 'static_interop/response/response.dart'; +import 'web_account_delegate.dart'; + +// TODO: implement linkToSite & linkToSocial in _LinkAccountResolver +// TODO: return `_LinkAccountResolver` when required + +/// 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(); + + final WebAccountDelegate _accountDelegate = const WebAccountDelegate(); + + @override + Future> setAccount(Map parameters) { + return _accountDelegate.setAccount(parameters); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 59c0c0e3..b6ae8309 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.3.0-1.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.2-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