From e81fd5ff27bdb00b277c934b2632c3e34ee65fc8 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 14 Aug 2025 15:45:03 -0300 Subject: [PATCH 01/55] Update platform_interface changelog --- splitio_platform_interface/CHANGELOG.md | 2 ++ splitio_platform_interface/lib/split_view.dart | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/splitio_platform_interface/CHANGELOG.md b/splitio_platform_interface/CHANGELOG.md index 3e5e7e1..50572dd 100644 --- a/splitio_platform_interface/CHANGELOG.md +++ b/splitio_platform_interface/CHANGELOG.md @@ -1,3 +1,5 @@ +# 2.0.0-rc.1 (Aug 14, 2025) + # 1.5.0 (Oct 18, 2024) * Added certificate pinning functionality. This feature allows you to pin a certificate to the SDK, ensuring that the SDK only communicates with servers that present this certificate. Read more in our documentation. diff --git a/splitio_platform_interface/lib/split_view.dart b/splitio_platform_interface/lib/split_view.dart index 9cc2916..8908cf6 100644 --- a/splitio_platform_interface/lib/split_view.dart +++ b/splitio_platform_interface/lib/split_view.dart @@ -32,7 +32,7 @@ class SplitView { this.impressionsDisabled = false, this.prerequisites = const {}]); - static SplitView? fromEntry(Map? entry) { + static SplitView? fromEntry(Map? entry) { if (entry == null || entry.isEmpty) { return null; } From d06b5af053e665ea6f88357e085b7db30dc34a99 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 14 Aug 2025 15:46:32 -0300 Subject: [PATCH 02/55] Update splitio_android --- splitio_android/CHANGELOG.md | 2 ++ splitio_android/pubspec.yaml | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/splitio_android/CHANGELOG.md b/splitio_android/CHANGELOG.md index 4093fab..66affa3 100644 --- a/splitio_android/CHANGELOG.md +++ b/splitio_android/CHANGELOG.md @@ -1,3 +1,5 @@ +# 1.0.0-rc.1 (Aug 14, 2025) + # 0.2.0 (Nov 6, 2024) * Updated Android SDK to `5.0.0` diff --git a/splitio_android/pubspec.yaml b/splitio_android/pubspec.yaml index 05d4e96..5cf2ecd 100644 --- a/splitio_android/pubspec.yaml +++ b/splitio_android/pubspec.yaml @@ -19,8 +19,7 @@ flutter: dependencies: flutter: sdk: flutter - splitio_platform_interface: # ^2.0.0-rc.1 - path: ../splitio_platform_interface + splitio_platform_interface: ^2.0.0-rc.1 dev_dependencies: flutter_test: From df2eba976713084472f7928792a71e6fcbd99ad3 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 14 Aug 2025 16:25:16 -0300 Subject: [PATCH 03/55] Update splitio_ios --- splitio_ios/CHANGELOG.md | 2 ++ splitio_ios/pubspec.yaml | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/splitio_ios/CHANGELOG.md b/splitio_ios/CHANGELOG.md index d8e08de..0b46839 100644 --- a/splitio_ios/CHANGELOG.md +++ b/splitio_ios/CHANGELOG.md @@ -1,3 +1,5 @@ +# 1.0.0-rc.1 (Aug 14, 2025) + # 0.2.0 (Nov 6, 2024) * Updated iOS SDK to `3.0.0` diff --git a/splitio_ios/pubspec.yaml b/splitio_ios/pubspec.yaml index ae0269e..e177e0e 100644 --- a/splitio_ios/pubspec.yaml +++ b/splitio_ios/pubspec.yaml @@ -18,8 +18,7 @@ flutter: dependencies: flutter: sdk: flutter - splitio_platform_interface: # ^2.0.0-rc.1 - path: ../splitio_platform_interface + splitio_platform_interface: ^2.0.0-rc.1 dev_dependencies: flutter_test: From 2a1cf24512cb986590db530257e32f74c59fe7c3 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 14 Aug 2025 16:27:44 -0300 Subject: [PATCH 04/55] Update splitio --- splitio/CHANGELOG.md | 3 +++ splitio/pubspec.yaml | 9 +++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/splitio/CHANGELOG.md b/splitio/CHANGELOG.md index 3ed9258..c23bf23 100644 --- a/splitio/CHANGELOG.md +++ b/splitio/CHANGELOG.md @@ -1,3 +1,6 @@ +# 1.0.0-rc.1 (Aug 14, 2025) + + # 0.2.0 (Nov 6, 2024) * Added support for targeting rules based on large segments. * BREAKING CHANGE (for Split Proxy users): diff --git a/splitio/pubspec.yaml b/splitio/pubspec.yaml index 634f85e..ccf475c 100644 --- a/splitio/pubspec.yaml +++ b/splitio/pubspec.yaml @@ -20,12 +20,9 @@ flutter: dependencies: flutter: sdk: flutter - splitio_android: # ^1.0.0-rc.1 - path: ../splitio_android - splitio_ios: # ^1.0.0-rc.1 - path: ../splitio_ios - splitio_platform_interface: # ^2.0.0-rc.1 - path: ../splitio_platform_interface + splitio_android: ^1.0.0-rc.1 + splitio_ios: ^1.0.0-rc.1 + splitio_platform_interface: ^2.0.0-rc.1 dev_dependencies: flutter_test: sdk: flutter From f411e28dc884b822d5a14672b6f57acdebf37f23 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 14 Aug 2025 16:31:40 -0300 Subject: [PATCH 05/55] Remove publish_to: none --- splitio/pubspec.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/splitio/pubspec.yaml b/splitio/pubspec.yaml index ccf475c..b535c24 100644 --- a/splitio/pubspec.yaml +++ b/splitio/pubspec.yaml @@ -1,4 +1,3 @@ -publish_to: none # TODO name: splitio description: Official plugin for split.io, the platform for controlled rollouts, which serves features to your users via feature flags to manage your complete customer experience. version: 1.0.0-rc.1 From ec1a001a52918f56febdee12fbf3a8b20c11506d Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 14 Aug 2025 17:01:40 -0300 Subject: [PATCH 06/55] Platform to 2.0.0 --- splitio_platform_interface/CHANGELOG.md | 2 ++ splitio_platform_interface/pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/splitio_platform_interface/CHANGELOG.md b/splitio_platform_interface/CHANGELOG.md index 50572dd..cf67b5e 100644 --- a/splitio_platform_interface/CHANGELOG.md +++ b/splitio_platform_interface/CHANGELOG.md @@ -1,3 +1,5 @@ +# 2.0.0 (Aug 14, 2025) + # 2.0.0-rc.1 (Aug 14, 2025) # 1.5.0 (Oct 18, 2024) diff --git a/splitio_platform_interface/pubspec.yaml b/splitio_platform_interface/pubspec.yaml index 3465a95..6b2a0e1 100644 --- a/splitio_platform_interface/pubspec.yaml +++ b/splitio_platform_interface/pubspec.yaml @@ -2,7 +2,7 @@ name: splitio_platform_interface description: A common platform interface for the splitio plugin. # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 2.0.0-rc.1 +version: 2.0.0 repository: https://github.com/splitio/flutter-sdk-plugin/tree/main/splitio_platform_interface environment: From e4c7d99bafd388935b6b5eb2db8772993c1e0901 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 14 Aug 2025 17:04:25 -0300 Subject: [PATCH 07/55] Update splitio_ios to 1.0.0 --- splitio_android/CHANGELOG.md | 3 +++ splitio_android/pubspec.yaml | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/splitio_android/CHANGELOG.md b/splitio_android/CHANGELOG.md index 66affa3..6f47087 100644 --- a/splitio_android/CHANGELOG.md +++ b/splitio_android/CHANGELOG.md @@ -1,3 +1,6 @@ +# 1.0.0 (Aug 14, 2025) +- Updated Android SDK to `5.3.1`. + # 1.0.0-rc.1 (Aug 14, 2025) # 0.2.0 (Nov 6, 2024) diff --git a/splitio_android/pubspec.yaml b/splitio_android/pubspec.yaml index 5cf2ecd..fc2bbbe 100644 --- a/splitio_android/pubspec.yaml +++ b/splitio_android/pubspec.yaml @@ -1,7 +1,7 @@ name: splitio_android description: The official Android implementation of splitio Flutter plugin. repository: https://github.com/splitio/flutter-sdk-plugin/tree/main/splitio_android -version: 1.0.0-rc.1 +version: 1.0.0 environment: sdk: ">=2.16.2 <4.0.0" @@ -19,7 +19,7 @@ flutter: dependencies: flutter: sdk: flutter - splitio_platform_interface: ^2.0.0-rc.1 + splitio_platform_interface: ^2.0.0 dev_dependencies: flutter_test: From 330ba621890a33b53e7eac5f3677d555a01147c4 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 14 Aug 2025 17:08:42 -0300 Subject: [PATCH 08/55] Update splitio_ios to 1.0.0 --- splitio_ios/CHANGELOG.md | 3 +++ splitio_ios/pubspec.yaml | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/splitio_ios/CHANGELOG.md b/splitio_ios/CHANGELOG.md index 0b46839..d0935b9 100644 --- a/splitio_ios/CHANGELOG.md +++ b/splitio_ios/CHANGELOG.md @@ -1,3 +1,6 @@ +# 1.0.0 (Aug 14, 2025) +- iOS SDK to `3.3.2` + # 1.0.0-rc.1 (Aug 14, 2025) # 0.2.0 (Nov 6, 2024) diff --git a/splitio_ios/pubspec.yaml b/splitio_ios/pubspec.yaml index e177e0e..8c6637b 100644 --- a/splitio_ios/pubspec.yaml +++ b/splitio_ios/pubspec.yaml @@ -1,7 +1,7 @@ name: splitio_ios description: The official iOS implementation of splitio Flutter plugin. repository: https://github.com/splitio/flutter-sdk-plugin/tree/main/splitio_ios -version: 1.0.0-rc.1 +version: 1.0.0 environment: sdk: ">=2.16.2 <4.0.0" @@ -18,7 +18,7 @@ flutter: dependencies: flutter: sdk: flutter - splitio_platform_interface: ^2.0.0-rc.1 + splitio_platform_interface: ^2.0.0 dev_dependencies: flutter_test: From 175d5f3a15697da5573ccdfc1c6ec10f2ac05aed Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 14 Aug 2025 17:10:11 -0300 Subject: [PATCH 09/55] splitio to 1.0.0 --- splitio/CHANGELOG.md | 9 ++++++++- splitio/pubspec.yaml | 8 ++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/splitio/CHANGELOG.md b/splitio/CHANGELOG.md index c23bf23..057ee4c 100644 --- a/splitio/CHANGELOG.md +++ b/splitio/CHANGELOG.md @@ -1,5 +1,12 @@ -# 1.0.0-rc.1 (Aug 14, 2025) +# 1.0.0 (Aug 14, 2025) +- Updated Android SDK to `5.3.1` & iOS SDK to `3.3.2` +- Added support for rule-based segments. These segments determine membership at runtime by evaluating their configured rules against the user attributes provided to the SDK. +- Added support for feature flag prerequisites. This allows customers to define dependency conditions between flags, which are evaluated before any allowlists or targeting rules. +- Added two new configuration options to control the behavior of the persisted rollout plan cache. Use `rolloutCacheConfiguration` in the config. +- Added a new optional argument to the client `getTreatment` methods to allow passing additional evaluation options, such as a map of properties to append to the generated impressions sent to Split backend. Read more in our docs. +- Added support for the new impressions tracking toggle available on feature flags, both respecting the setting and including the new field being returned on SplitView type objects. Read more in our docs. +# 1.0.0-rc.1 (Aug 14, 2025) # 0.2.0 (Nov 6, 2024) * Added support for targeting rules based on large segments. diff --git a/splitio/pubspec.yaml b/splitio/pubspec.yaml index b535c24..05216c1 100644 --- a/splitio/pubspec.yaml +++ b/splitio/pubspec.yaml @@ -1,6 +1,6 @@ name: splitio description: Official plugin for split.io, the platform for controlled rollouts, which serves features to your users via feature flags to manage your complete customer experience. -version: 1.0.0-rc.1 +version: 1.0.0 homepage: https://split.io/ repository: https://github.com/splitio/flutter-sdk-plugin/tree/main/splitio/ @@ -19,9 +19,9 @@ flutter: dependencies: flutter: sdk: flutter - splitio_android: ^1.0.0-rc.1 - splitio_ios: ^1.0.0-rc.1 - splitio_platform_interface: ^2.0.0-rc.1 + splitio_android: ^1.0.0 + splitio_ios: ^1.0.0 + splitio_platform_interface: ^2.0.0 dev_dependencies: flutter_test: sdk: flutter From 7a57c2a673d7251673c600b1a15b60ffbf3e88d0 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 17 Dec 2025 14:59:09 -0300 Subject: [PATCH 10/55] Add splitio_web folder with basic scaffolding --- splitio/pubspec.yaml | 3 + splitio_web/.gitignore | 29 +++ splitio_web/LICENSE | 191 ++++++++++++++++++ splitio_web/lib/splitio_web.dart | 11 + splitio_web/pubspec.yaml | 31 +++ .../web/split-browser-1.6.0.full.min.js | 9 + 6 files changed, 274 insertions(+) create mode 100644 splitio_web/.gitignore create mode 100644 splitio_web/LICENSE create mode 100644 splitio_web/lib/splitio_web.dart create mode 100644 splitio_web/pubspec.yaml create mode 100644 splitio_web/web/split-browser-1.6.0.full.min.js diff --git a/splitio/pubspec.yaml b/splitio/pubspec.yaml index 05216c1..0e0dc54 100644 --- a/splitio/pubspec.yaml +++ b/splitio/pubspec.yaml @@ -15,12 +15,15 @@ flutter: default_package: splitio_android ios: default_package: splitio_ios + web: + default_package: splitio_web dependencies: flutter: sdk: flutter splitio_android: ^1.0.0 splitio_ios: ^1.0.0 + splitio_web: ^1.0.0 splitio_platform_interface: ^2.0.0 dev_dependencies: flutter_test: diff --git a/splitio_web/.gitignore b/splitio_web/.gitignore new file mode 100644 index 0000000..9be145f --- /dev/null +++ b/splitio_web/.gitignore @@ -0,0 +1,29 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ diff --git a/splitio_web/LICENSE b/splitio_web/LICENSE new file mode 100644 index 0000000..af74bff --- /dev/null +++ b/splitio_web/LICENSE @@ -0,0 +1,191 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright © 2025 Split Software, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart new file mode 100644 index 0000000..4fd62fb --- /dev/null +++ b/splitio_web/lib/splitio_web.dart @@ -0,0 +1,11 @@ +import 'package:flutter_web_plugins/flutter_web_plugins.dart' show Registrar; +import 'package:splitio_platform_interface/splitio_platform_interface.dart'; + +/// Web implementation of [SplitioPlatform]. +class SplitioWeb extends SplitioPlatform { + /// Registers this class as the default platform implementation. + static void registerWith(Registrar registrar) { + SplitioPlatform.instance = SplitioWeb(); + } + +} diff --git a/splitio_web/pubspec.yaml b/splitio_web/pubspec.yaml new file mode 100644 index 0000000..d5abf73 --- /dev/null +++ b/splitio_web/pubspec.yaml @@ -0,0 +1,31 @@ +name: splitio_web +description: The official Web implementation of splitio Flutter plugin. +repository: https://github.com/splitio/flutter-sdk-plugin/tree/main/splitio_web +version: 1.0.0 + +environment: + sdk: ">=3.3.0 <4.0.0" + flutter: ">=3.16.0" + +flutter: + plugin: + implements: splitio + platforms: + web: + pluginClass: SplitioWeb + fileName: splitio_web.dart + assets: + - web/split-browser-1.6.0.full.min.js + +dependencies: + flutter: + sdk: flutter + splitio_platform_interface: ^2.0.0 + flutter_web_plugins: + sdk: flutter + web: ^0.5.0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^1.0.0 diff --git a/splitio_web/web/split-browser-1.6.0.full.min.js b/splitio_web/web/split-browser-1.6.0.full.min.js new file mode 100644 index 0000000..e8b2dae --- /dev/null +++ b/splitio_web/web/split-browser-1.6.0.full.min.js @@ -0,0 +1,9 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).splitio=t()}(this,(function(){"use strict";var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},e(t,n)};function t(t,n){if("function"!=typeof n&&null!==n)throw new TypeError("Class extends value "+String(n)+" is not a constructor or null");function r(){this.constructor=t}e(t,n),t.prototype=null===n?Object.create(n):(r.prototype=n.prototype,new r)}function n(e,t){var n;if(c(e))for(var r=Object.keys(e),i=0;i0)return t;e.error(vn,[n,r])}else e.error(mn,[n,r]);return!1}function Qn(e,t,n,r,i){if(void 0===r&&(r="feature flag names"),void 0===i&&(i="feature flag name"),Array.isArray(t)&&t.length>0){var s=[];if(t.forEach((function(t){var r=Hn(e,t,n,i);r&&s.push(r)})),s.length)return m(s)}return e.error(yn,[n,r]),!1}var Vn=Object.assign||function(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");e=Object(e);for(var t=1;t0}))}function er(e,t,n){var r,i,s={validFilters:[],queryString:null,groupedFilters:{bySet:[],byName:[],byPrefix:[]}};return t?Ie(n)?(e.warn(jt),s):Array.isArray(t)&&0!==t.length?(s.validFilters=t.filter((function(t,n){return t&&(r=t.type,Jn.some((function(e){return e.type===r})))&&Array.isArray(t.values)?(s.groupedFilters[t.type]=s.groupedFilters[t.type].concat(t.values),!0):(e.warn(qt,[n]),!1);var r})),Jn.forEach((function(t){var n=t.type,r=t.maxLength;s.groupedFilters[n].length>0&&(s.groupedFilters[n]=function(e,t,n,r){var i=Qn(e,n,wn,t+" filter",t+" filter value");if(i){if("bySet"===t&&(i=Zn(e,i,wn)),i.length>r)throw new Error(r+" unique values can be specified at most for '"+t+"' filter. You passed "+i.length+". Please consider reducing the amount or using other filter.");i.sort()}return i||[]}(e,n,s.groupedFilters[n],r))})),Xn(s.validFilters,"bySet")&&((Xn(s.validFilters,"byName")||Xn(s.validFilters,"byPrefix"))&&e.error(kn),Vn(s.groupedFilters,{byName:[],byPrefix:[]})),s.queryString=(r=s.groupedFilters,i=[],Jn.forEach((function(e){var t=e.type,n=e.queryParam,s=r[t];s.length>0&&i.push(n+s.map((function(e){return encodeURIComponent(e)})).join(","))})),i.length>0?"&"+i.join("&"):null),e.debug(Ve,[s.queryString]),s):(e.warn(zt),s):s}var tr,nr=250;function rr(e,t,n,r){if(null==t)return e.error(pn,[n,r]),!1;if(a(t))return e.warn(Mt,[n,r,t]),d(t);if(l(t)){if((t=t.trim()).length>0&&t.length<=nr)return t;0===t.length?e.error(vn,[n,r]):t.length>nr&&e.error(gn,[n,r])}else e.error(mn,[n,r]);return!1}function ir(e,t,n){if(c(t)){var r=rr(e,t.matchingKey,n,"matchingKey"),i=rr(e,t.bucketingKey,n,"bucketingKey");return r&&i?{matchingKey:r,bucketingKey:i}:(e.error(dn,[n]),!1)}return rr(e,t,n,"key")}function sr(e,t,n,r){var i=n.splits,s=n.rbSegments,o=n.segments,a=n.largeSegments,u=t.splitChanges,c=u.ff,l=u.rbs;e.debug("storage: set feature flags and segments"+(r?" for key "+r:"")),i&&c&&(i.clear(),i.update(c.d,[],c.t)),s&&l&&(s.clear(),s.update(l.d,[],l.t));var f=t.segmentChanges;if(r){var h=t.memberships&&t.memberships[r];!h&&f&&(h={ms:{k:f.filter((function(e){return e.added.indexOf(r)>-1})).map((function(e){return{n:e.name}}))}}),h&&(h.ms&&o.resetSegments(h.ms),h.ls&&a&&a.resetSegments(h.ls))}else f&&(o.clear(),f.forEach((function(e){o.update(e.name,e.added,e.removed,e.till)})))}!function(e){e.FlagName="Invalid flag name (max 100 chars, no spaces)",e.Treatment="Invalid treatment (max 100 chars and must match pattern)"}(tr||(tr={}));var or=/^[0-9]+[.a-zA-Z0-9_-]*$|^[a-zA-Z]+[a-zA-Z0-9_-]*$/;function ar(e){var t=c(e)?e.treatment:e;return!(!l(t)||t.length>100)&&or.test(t)}function ur(e,t){if(void 0!==t){if(ar(t))return t;e.error("Fallback treatments - Discarded fallback: "+tr.Treatment)}}function cr(e,t){var n={};return c(t)?(Object.keys(t).forEach((function(r){var i,s=t[r];(i=r).length<=100&&!i.includes(" ")?ar(s)?n[r]=s:e.error("Fallback treatments - Discarded treatment for flag '"+r+"': "+tr.Treatment):e.error("Fallback treatments - Discarded flag '"+r+"': "+tr.FlagName)})),n):n}var lr={mode:k,core:{authorizationKey:void 0,key:void 0,labelsEnabled:!0,IPAddressesEnabled:void 0},scheduler:{featuresRefreshRate:60,segmentsRefreshRate:60,telemetryRefreshRate:3600,impressionsRefreshRate:300,offlineRefreshRate:15,eventsPushRate:60,eventsQueueSize:500,impressionsQueueSize:3e4,pushRetryBackoffBase:1},urls:{sdk:"https://sdk.split.io/api",events:"https://events.split.io/api",auth:"https://auth.split.io/api",streaming:"https://streaming.split.io",telemetry:"https://telemetry.split.io/api"},storage:void 0,debug:void 0,impressionListener:void 0,version:void 0,integrations:void 0,streamingEnabled:!0,sync:{splitFilters:void 0,impressionsMode:E,enabled:!0,flagSpecVersion:Ce},log:void 0};function fr(e){return Math.round(1e3*e)}function hr(e,t){var n=t.defaults,r=t.runtime,i=t.storage,o=t.integrations,a=t.logger,u=t.consent,l=t.flagSpec,p=h({},lr,n,e);p.features=s(e,"features");var g=a(p);p.log=g;var d=p.sync;d.impressionsMode=function(e,t){return t=f(t),[b,E,T].indexOf(t)>-1?t:(e.error(Sn,["impressionsMode",[b,E,T],E]),E)}(g,d.impressionsMode);var m,v,y,S=p.scheduler,_=p.startup;S.featuresRefreshRate=fr(S.featuresRefreshRate),S.segmentsRefreshRate=fr(S.segmentsRefreshRate),S.offlineRefreshRate=fr(S.offlineRefreshRate),S.eventsPushRate=fr(S.eventsPushRate),S.telemetryRefreshRate=fr((m="telemetryRefreshRate",(v=S.telemetryRefreshRate)>=(y=60)?v:(g.error(Tn,[m,y]),y))),void 0===s(e,"scheduler.impressionsRefreshRate")&&d.impressionsMode===b&&(S.impressionsRefreshRate=60),S.impressionsRefreshRate=fr(S.impressionsRefreshRate),S.metricsRefreshRate&&g.warn("`metricsRefreshRate` will be deprecated soon. For configuring telemetry rates, update `telemetryRefreshRate` value in configs"),_.requestTimeoutBeforeReady=fr(_.requestTimeoutBeforeReady),_.readyTimeout=fr(_.readyTimeout),_.eventsFirstPushWindow=fr(_.eventsFirstPushWindow),p.mode=function(e,t){if("localhost"===e)return R;if(-1===[k,C,w,N].indexOf(t))throw Error("Invalid mode provided");return t}(p.core.authorizationKey,p.mode),i&&(p.storage=i(p)),p.initialRolloutPlan&&(p.initialRolloutPlan=function(e,t){var n=t.mode,r=t.initialRolloutPlan;if(!Ie(n))return c(r)&&c(r.splitChanges)?r:void e.error("storage: invalid rollout plan provided");e.warn("storage: initial rollout plan is ignored in consumer mode")}(g,p));var A=p.core.key;t.acceptKey?p.mode===R&&void 0===A?p.core.key="localhost_key":p.core.key=ir(g,A,_n):(void 0!==A&&g.warn("Provided `key` is ignored in server-side SDK."),p.core.key=void 0),p.runtime=r(p),o&&(p.integrations=o(p)),!1!==p.streamingEnabled&&(p.streamingEnabled=!0,S.pushRetryBackoffBase=fr(S.pushRetryBackoffBase)),!1!==d.enabled&&(d.enabled=!0);var I=er(g,d.splitFilters,p.mode);return d.splitFilters=I.validFilters,d.__splitFiltersValidation=I,d.flagSpecVersion=l?l(p):Ce,p.userConsent=u?u(p):void 0,p.fallbackTreatments=p.fallbackTreatments?function(e,t){if(c(t))return{global:ur(e,t.global),byFlag:cr(e,t.byFlag)};e.error("Fallback treatments - Discarded configuration: it must be an object with optional `global` and `byFlag` properties")}(g,p.fallbackTreatments):void 0,p}function pr(e){return null!==e&&"object"==typeof e&&"function"==typeof e.debug&&"function"==typeof e.info&&"function"==typeof e.warn&&"function"==typeof e.error}var gr={DEBUG:"DEBUG",INFO:"INFO",WARN:"WARN",ERROR:"ERROR",NONE:"NONE"},dr={DEBUG:1,INFO:2,WARN:3,ERROR:4,NONE:5},mr={debug:function(e){console.log("[DEBUG] "+e)},info:function(e){console.log("[INFO] "+e)},warn:function(e){console.log("[WARN] "+e)},error:function(e){console.log("[ERROR] "+e)}};function vr(e){return!!n(gr,(function(t){return e===t}))}function yr(e,t){void 0===e&&(e=""),void 0===t&&(t=[]);var n=0;return e.replace(/%s/g,(function(){var e=t[n++];if(c(e)||Array.isArray(e))try{e=JSON.stringify(e)}catch(e){}return e}))}var Sr,br={prefix:"splitio",logLevel:gr.NONE},Er=function(){function e(e,t){this.options=Vn({},br,e),this.codes=t||new Map,this.logLevel=dr[this.options.logLevel]}return e.prototype.setLogLevel=function(e){this.options.logLevel=e,this.logLevel=dr[e]},e.prototype.setLogger=function(e){if(e){if(pr(e))return void(this.logger=e);this.error("Invalid `logger` instance. It must be an object with `debug`, `info`, `warn` and `error` methods. Defaulting to `console.log`")}this.logger=void 0},e.prototype.debug=function(e,t){this._shouldLog(dr.DEBUG)&&this._log("debug",e,t)},e.prototype.info=function(e,t){this._shouldLog(dr.INFO)&&this._log("info",e,t)},e.prototype.warn=function(e,t){this._shouldLog(dr.WARN)&&this._log("warn",e,t)},e.prototype.error=function(e,t){this._shouldLog(dr.ERROR)&&this._log("error",e,t)},e.prototype._log=function(e,t,n){if("number"==typeof t){var r=this.codes.get(t);t=r?yr(r,n):"Message code "+t+(n?", with args: "+n.toString():"")}else n&&(t=yr(t,n));if(this.options.prefix&&(t=this.options.prefix+" => "+t),this.logger)try{return void this.logger[e](t)}catch(e){}mr[e](t)},e.prototype._shouldLog=function(e){return e>=this.logLevel},e}();try{var Tr=localStorage.getItem("splitio_debug")||"";Sr=/^(enabled?|on)/i.test(Tr)?gr.DEBUG:vr(Tr)?Tr:gr.NONE}catch(Ei){}var Rr={startup:{requestTimeoutBeforeReady:5,retriesOnFailureBeforeReady:1,readyTimeout:10,eventsFirstPushWindow:10},userConsent:O,version:"browserjs-1.6.0",debug:Sr};var kr=function(){function e(){}return e.prototype.update=function(e,t,n){var r=this;this.setChangeNumber(n);var i=e.map((function(e){return r.addSplit(e)})).some((function(e){return e}));return t.map((function(e){return r.removeSplit(e.name)})).some((function(e){return e}))||i},e.prototype.getSplits=function(e){var t=this,n={};return e.forEach((function(e){n[e]=t.getSplit(e)})),n},e.prototype.getAll=function(){var e=this;return this.getSplitNames().map((function(t){return e.getSplit(t)}))},e.prototype.killLocally=function(e,t,n){var r=this.getSplit(e);if(r&&(!r.changeNumber||r.changeNumber0)}function wr(e){return e.splits.usesSegments()||e.rbSegments.usesSegments()}var Nr=function(e){function n(t){var n=e.call(this)||this;return n.splitsCache={},n.ttCache={},n.changeNumber=-1,n.segmentsCount=0,n.flagSetsCache={},n.flagSetsFilter=t?t.groupedFilters.bySet:[],n}return t(n,e),n.prototype.clear=function(){this.splitsCache={},this.ttCache={},this.changeNumber=-1,this.segmentsCount=0,this.flagSetsCache={}},n.prototype.addSplit=function(e){var t=e.name,n=this.getSplit(t);if(n){var r=n.trafficTypeName;this.ttCache[r]--,this.ttCache[r]||delete this.ttCache[r],this.removeFromFlagSets(n.name,n.sets),Cr(n)&&this.segmentsCount--}this.splitsCache[t]=e;var i=e.trafficTypeName;return this.ttCache[i]=(this.ttCache[i]||0)+1,this.addToFlagSets(e),Cr(e)&&this.segmentsCount++,!0},n.prototype.removeSplit=function(e){var t=this.getSplit(e);if(!t)return!1;delete this.splitsCache[e];var n=t.trafficTypeName;return this.ttCache[n]--,this.ttCache[n]||delete this.ttCache[n],this.removeFromFlagSets(t.name,t.sets),Cr(t)&&this.segmentsCount--,!0},n.prototype.getSplit=function(e){return this.splitsCache[e]||null},n.prototype.setChangeNumber=function(e){return this.changeNumber=e,!0},n.prototype.getChangeNumber=function(){return this.changeNumber},n.prototype.getSplitNames=function(){return Object.keys(this.splitsCache)},n.prototype.trafficTypeExists=function(e){return a(this.ttCache[e])&&this.ttCache[e]>0},n.prototype.usesSegments=function(){return-1===this.getChangeNumber()||this.segmentsCount>0},n.prototype.getNamesByFlagSets=function(e){var t=this;return e.map((function(e){return t.flagSetsCache[e]||new Set}))},n.prototype.addToFlagSets=function(e){var t=this;e.sets&&e.sets.forEach((function(n){t.flagSetsFilter.length>0&&!t.flagSetsFilter.some((function(e){return e===n}))||(t.flagSetsCache[n]||(t.flagSetsCache[n]=new Set([])),t.flagSetsCache[n].add(e.name))}))},n.prototype.removeFromFlagSets=function(e,t){var n=this;t&&t.forEach((function(t){n.removeNames(t,e)}))},n.prototype.removeNames=function(e,t){this.flagSetsCache[e]&&(this.flagSetsCache[e].delete(t),0===this.flagSetsCache[e].size&&delete this.flagSetsCache[e])},n}(kr),_r=function(){function e(){}return e.prototype.clear=function(){this.resetSegments({})},e.prototype.registerSegments=function(){return!1},e.prototype.update=function(){return!1},e.prototype.resetSegments=function(e){var t=this;this.setChangeNumber(e.cn);var n=e,r=n.added,i=n.removed;if(r&&i){var s=!1;return r.forEach((function(e){s=t.addSegment(e)||s})),i.forEach((function(e){s=t.removeSegment(e)||s})),s}var o=(e.k||[]).map((function(e){return e.n})).sort(),a=this.getRegisteredSegments().sort();if(!o.length&&!a.length)return!1;for(var u=0;u0&&this.queue.length>=this.maxQueue&&this.onFullQueue&&this.onFullQueue()},e.prototype.clear=function(){this.queue=[]},e.prototype.pop=function(e){var t=this.queue;return this.clear(),e?e.concat(t):t},e.prototype.isEmpty=function(){return 0===this.queue.length},e}(),Or=function(){function e(e){void 0===e&&(e=0),this.name="events",this.maxQueue=e,this.queue=[],this.queueByteSize=0}return e.prototype.setOnFullQueueCb=function(e){this.onFullQueue=e},e.prototype.track=function(e,t){return void 0===t&&(t=0),this.queueByteSize+=t,this.queue.push(e),this._checkForFlush(),!0},e.prototype.clear=function(){this.queue=[],this.queueByteSize=0},e.prototype.pop=function(e){var t=this.queue;return this.clear(),e?e.concat(t):t},e.prototype.isEmpty=function(){return 0===this.queue.length},e.prototype._checkForFlush=function(){(this.queueByteSize>5242880||this.maxQueue>0&&this.queue.length>=this.maxQueue)&&this.onFullQueue&&this.onFullQueue()},e}(),Fr=36e5;function Lr(e){return e-e%Fr}var Dr=function(){function e(e){void 0===e&&(e=3e4),this.name="impression counts",this.cache={},this.cacheSize=0,this.maxStorage=e}return e.prototype._makeKey=function(e,t){return e+"::"+Lr(t)},e.prototype.track=function(e,t,n){var r=this._makeKey(e,t),i=this.cache[r];this.cache[r]=i?i+n:n,this.onFullQueue&&(this.cacheSize=this.cacheSize+n,this.cacheSize>=this.maxStorage&&this.onFullQueue())},e.prototype.pop=function(e){var t=this.cache;return this.clear(),e?(Object.keys(t).forEach((function(n){e[n]?e[n]+=t[n]:e[n]=t[n]})),e):t},e.prototype.clear=function(){this.cache={},this.cacheSize=0},e.prototype.isEmpty=function(){return 0===Object.keys(this.cache).length},e}();function Mr(e){return c(e)?e.matchingKey:e}function Kr(e){return c(e)?{matchingKey:e.matchingKey,bucketingKey:e.bucketingKey}:{matchingKey:e,bucketingKey:e}}function xr(e){return!e.core.key}function Pr(e){var t=Math.min(22,Math.max(0,Math.ceil(Math.log(e)/Math.log(1.5))));return u(t)?0:t}var Ur=.001;function Br(e){var t=e.settings;return t.mode!==R&&(xr(t)||Math.random()<=Ur)}var jr=function(){function e(e,t,n){this.splits=e,this.segments=t,this.largeSegments=n,this.name="telemetry stats",this.e=!0,this.notReadyUsage=0,this.impressionStats=[0,0,0],this.eventStats=[0,0],this.lastSync={},this.httpErrors={},this.httpLatencies={},this.authRejections=0,this.tokenRefreshes=0,this.streamingEvents=[],this.tags=[],this.exceptions={},this.latencies={},this.updatesFromSSE={}}return e.prototype.isEmpty=function(){return this.e},e.prototype.clear=function(){},e.prototype.pop=function(){return this.e=!0,{lS:this.getLastSynchronization(),mL:this.popLatencies(),mE:this.popExceptions(),hE:this.popHttpErrors(),hL:this.popHttpLatencies(),tR:this.popTokenRefreshes(),aR:this.popAuthRejections(),iQ:this.getImpressionStats(H),iDe:this.getImpressionStats(V),iDr:this.getImpressionStats(Q),spC:this.splits&&this.splits.getSplitNames().length,seC:this.segments&&this.segments.getRegisteredSegments().length,skC:this.segments&&this.segments.getKeysCount(),lsC:this.largeSegments&&this.largeSegments.getRegisteredSegments().length,lskC:this.largeSegments&&this.largeSegments.getKeysCount(),sL:this.getSessionLength(),eQ:this.getEventStats(H),eD:this.getEventStats(Q),sE:this.popStreamingEvents(),t:this.popTags(),ufs:this.popUpdatesFromSSE()}},e.prototype.getTimeUntilReady=function(){return this.timeUntilReady},e.prototype.recordTimeUntilReady=function(e){this.timeUntilReady=e},e.prototype.getTimeUntilReadyFromCache=function(){return this.timeUntilReadyFromCache},e.prototype.recordTimeUntilReadyFromCache=function(e){this.timeUntilReadyFromCache=e},e.prototype.getNonReadyUsage=function(){return this.notReadyUsage},e.prototype.recordNonReadyUsage=function(){this.notReadyUsage++},e.prototype.getImpressionStats=function(e){return this.impressionStats[e]},e.prototype.recordImpressionStats=function(e,t){this.impressionStats[e]+=t,this.e=!1},e.prototype.getEventStats=function(e){return this.eventStats[e]},e.prototype.recordEventStats=function(e,t){this.eventStats[e]+=t,this.e=!1},e.prototype.getLastSynchronization=function(){return this.lastSync},e.prototype.recordSuccessfulSync=function(e,t){this.lastSync[e]=t,this.e=!1},e.prototype.popHttpErrors=function(){var e=this.httpErrors;return this.httpErrors={},e},e.prototype.recordHttpError=function(e,t){var n=this.httpErrors[e]=this.httpErrors[e]||{};n[t]=(n[t]||0)+1,this.e=!1},e.prototype.popHttpLatencies=function(){var e=this.httpLatencies;return this.httpLatencies={},e},e.prototype.recordHttpLatency=function(e,t){(this.httpLatencies[e]=this.httpLatencies[e]||[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0])[Pr(t)]++,this.e=!1},e.prototype.popAuthRejections=function(){var e=this.authRejections;return this.authRejections=0,e},e.prototype.recordAuthRejections=function(){this.authRejections++,this.e=!1},e.prototype.popTokenRefreshes=function(){var e=this.tokenRefreshes;return this.tokenRefreshes=0,e},e.prototype.recordTokenRefreshes=function(){this.tokenRefreshes++,this.e=!1},e.prototype.popStreamingEvents=function(){return this.streamingEvents.splice(0)},e.prototype.recordStreamingEvents=function(e){this.streamingEvents.length<20&&this.streamingEvents.push(e),this.e=!1},e.prototype.popTags=function(){return this.tags.splice(0)},e.prototype.addTag=function(e){this.tags.length<10&&this.tags.push(e),this.e=!1},e.prototype.getSessionLength=function(){return this.sessionLength},e.prototype.recordSessionLength=function(e){this.sessionLength=e,this.e=!1},e.prototype.popExceptions=function(){var e=this.exceptions;return this.exceptions={},e},e.prototype.recordException=function(e){this.exceptions[e]=(this.exceptions[e]||0)+1,this.e=!1},e.prototype.popLatencies=function(){var e=this.latencies;return this.latencies={},e},e.prototype.recordLatency=function(e,t){(this.latencies[e]=this.latencies[e]||[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0])[Pr(t)]++,this.e=!1},e.prototype.popUpdatesFromSSE=function(){var e=this.updatesFromSSE;return this.updatesFromSSE={},e},e.prototype.recordUpdatesFromSSE=function(e){this.updatesFromSSE[e]=(this.updatesFromSSE[e]||0)+1,this.e=!1},e}();function qr(e){if(Array.from)return Array.from(e);var t=[];return e.forEach((function(e){t.push(e)})),t}var zr=function(){function e(e){void 0===e&&(e=3e4),this.name="unique keys",this.uniqueTrackerSize=0,this.uniqueKeysTracker={},this.maxStorage=e}return e.prototype.setOnFullQueueCb=function(e){this.onFullQueue=e},e.prototype.track=function(e,t){this.uniqueKeysTracker[e]||(this.uniqueKeysTracker[e]=new Set);var n=this.uniqueKeysTracker[e];n.has(t)||(n.add(t),this.uniqueTrackerSize++),this.uniqueTrackerSize>=this.maxStorage&&this.onFullQueue&&this.onFullQueue()},e.prototype.clear=function(){this.uniqueTrackerSize=0,this.uniqueKeysTracker={}},e.prototype.pop=function(){var e=this.uniqueKeysTracker;return this.clear(),this.fromUniqueKeysCollector(e)},e.prototype.isEmpty=function(){return 0===Object.keys(this.uniqueKeysTracker).length},e.prototype.fromUniqueKeysCollector=function(e){for(var t=[],n=Object.keys(e),r=0;r0},e}();function Gr(e){var t=e.settings,n=t.scheduler,r=n.impressionsQueueSize,i=n.eventsQueueSize,s=t.sync.__splitFiltersValidation,o=new Nr(s),a=new Wr,u=new Ar,c={splits:o,rbSegments:a,segments:u,largeSegments:new Ar,impressions:new Ir(r),impressionCounts:new Dr,events:new Or(i),telemetry:Br(e)?new jr(o,u):void 0,uniqueKeys:new zr,destroy:function(){},shared:function(){return{splits:this.splits,rbSegments:this.rbSegments,segments:new Ar,largeSegments:new Ar,impressions:this.impressions,impressionCounts:this.impressionCounts,events:this.events,telemetry:this.telemetry,uniqueKeys:this.uniqueKeys,destroy:function(){}}}};if(e.settings.mode===R){var l=function(){return!0};c.impressions.track=l,c.events.track=l,c.impressionCounts.track=l,c.uniqueKeys.track=l}return c}function Hr(e){var t=Gr(e);return t.validateCache=function(){return Promise.resolve(!0)},t}Gr.type=_,Hr.type=_;var Qr=gr.NONE;var Vr=[F,O,L];var Jr={defaults:Rr,acceptKey:!0,runtime:function(){return{ip:!1,hostname:!1}},storage:function(e){var t=e.storage,n=void 0===t?Gr:t,r=e.log,i=e.mode;if("function"==typeof n&&-1!==[_,A,I].indexOf(n.type)||(n=Gr,r.error(324)),i===R&&n.type===A)return Hr;if(-1===[R,k].indexOf(i)){if(n.type!==I)throw new Error("A PluggableStorage instance is required on consumer mode")}else n.type===I&&(n=Gr,r.error(324,[" It requires consumer mode."]));return n},integrations:function(e){return function(e,t,n){var r=e.integrations,i=e.log;if(!Array.isArray(r)||0===r.length)return[];var s=r.filter(t),o=r.length-s.length;return o&&i.warn(Bt,[o,n||""]),s}(e,(function(e){return"function"==typeof e}),"Integration items must be functions that initialize the integrations")},logger:function(e){var t,n=e.debug,r=e.logger,i=Qr;if(void 0!==n){if(function(e){return pr(e)&&"function"==typeof e.setLogLevel}(n))return n.setLogger(r),n;i="boolean"==typeof(t=e.debug)?t?gr.DEBUG:gr.NONE:"string"==typeof t&&vr(t)?t:void 0}var s=new Er({logLevel:i||Qr});return s.setLogger(r),i||s._log("error","Invalid `debug` value at config. Logs will be disabled."),s},consent:function(e){var t=e.userConsent,n=e.log;return t=f(t),Vr.indexOf(t)>-1?t:(n.error(Sn,["userConsent",Vr,O]),O)}};var Yr=new Set(["splitsdkclientkey","splitsdkversion","splitsdkmachineip","splitsdkmachinename","splitsdkimpressionsmode","host","referrer","content-type","content-length","content-encoding","accept","keep-alive","x-fastly-debug"]);function $r(e,t){var n;if(null===(n=e.sync.requestOptions)||void 0===n?void 0:n.getHeaderOverrides)try{var r=e.sync.requestOptions.getHeaderOverrides({headers:Vn({},t)});Object.keys(r).filter((function(e){return!Yr.has(e.toLowerCase())})).forEach((function(e){return t[e]=r[e]}))}catch(t){e.log.error("Problem adding custom headers to request decorator: "+t)}return t}function Zr(e,t){return e<1?t:new Promise((function(n,r){var i=setTimeout((function(){r(new Error("Operation timed out because it exceeded the configured time limit of "+e+" ms."))}),e);t.then((function(e){clearTimeout(i),n(e)}),(function(e){clearTimeout(i),r(e)}))}))}var Xr=100,ei="Global fetch API is not available.";var ti={headers:{"Cache-Control":"no-cache"}};function ni(e){return"users="+encodeURIComponent(e)}function ri(e,t,n){var r=e.urls,i=e.sync.__splitFiltersValidation&&e.sync.__splitFiltersValidation.queryString,s=e.sync.impressionsMode,o=function(e,t){var n=t.getOptions,r=t.getFetch,i=e.log,s=e.core.authorizationKey,o=e.version,a=e.runtime,u=a.ip,c=a.hostname,l=n&&n(e),f=r&&r(e);f||i.error(en,[ei]);var h={Accept:"application/json","Content-Type":"application/json",Authorization:"Bearer "+s,SplitSDKVersion:o};return u&&(h.SplitSDKMachineIP=u),c&&(h.SplitSDKMachineName=c.replace(/[^\x00-\xFF]/g,"")),function(t,n,r,s){void 0===n&&(n={}),void 0===r&&(r=function(){}),void 0===s&&(s=!1);var o=Vn({headers:$r(e,Vn({},h,n.headers||{})),method:n.method||"GET",body:n.body},l);return f?f(t,o).then((function(e){return e.ok?(r(),e):Zr(Xr,e.text()).then((function(t){return Promise.reject({response:e,message:t})}),(function(){return Promise.reject({response:e})}))})).catch((function(e){var n=e&&e.response,o="";o=n?404===n.status?"Invalid SDK key or resource not found.":e.message:e.message||"Network Error",n&&403===n.status||i[s?"info":"error"](bn,[n?"status code "+n.status:"no status code",t,o]);var a=new Error(o);throw a.statusCode=n&&n.status,r(a),a})):Promise.reject(new Error(ei))}}(e,t);return{getSdkAPIHealthCheck:function(){var e=r.sdk+"/version";return o(e).then((function(){return!0})).catch((function(){return!1}))},getEventsAPIHealthCheck:function(){var e=r.events+"/version";return o(e).then((function(){return!0})).catch((function(){return!1}))},fetchAuth:function(t){var i=r.auth+"/v2/auth?s="+e.sync.flagSpecVersion;if(t){var s=t.map(ni).join("&");s&&(i+="&"+s)}return o(i,void 0,n.trackHttp(ee))},fetchSplitChanges:function(t,s,a,u){var c=r.sdk+"/splitChanges?s="+e.sync.flagSpecVersion+"&since="+t+(u?"&rbSince="+u:"")+(i||"")+(a?"&till="+a:"");return o(c,s?ti:void 0,n.trackHttp(J)).catch((function(t){throw 414===t.statusCode&&e.log.error(Rn),t}))},fetchSegmentChanges:function(e,t,i,s){var a=r.sdk+"/segmentChanges/"+t+"?since="+e+(s?"&till="+s:"");return o(a,i?ti:void 0,n.trackHttp(te))},fetchMemberships:function(e,t,i){var s=r.sdk+"/memberships/"+encodeURIComponent(e)+(i?"?till="+i:"");return o(s,t?ti:void 0,n.trackHttp(ne))},postEventsBulk:function(e,t){var i=r.events+"/events/bulk";return o(i,{method:"POST",body:e,headers:t},n.trackHttp(Z))},postTestImpressionsBulk:function(e,t){var i=r.events+"/testImpressions/bulk";return o(i,{method:"POST",body:e,headers:Vn({SplitSDKImpressionsMode:s},t)},n.trackHttp(Y))},postTestImpressionsCount:function(e,t){var i=r.events+"/testImpressions/count";return o(i,{method:"POST",body:e,headers:t},n.trackHttp($))},postUniqueKeysBulkCs:function(e,t){var i=r.telemetry+"/v1/keys/cs";return o(i,{method:"POST",body:e,headers:t},n.trackHttp(X))},postUniqueKeysBulkSs:function(e,t){var i=r.telemetry+"/v1/keys/ss";return o(i,{method:"POST",body:e,headers:t},n.trackHttp(X))},postMetricsConfig:function(e,t){var i=r.telemetry+"/v1/metrics/config";return o(i,{method:"POST",body:e,headers:t},n.trackHttp(X),!0)},postMetricsUsage:function(e,t){var i=r.telemetry+"/v1/metrics/usage";return o(i,{method:"POST",body:e,headers:t},n.trackHttp(X),!0)}}}function ii(e,t,n,r){void 0===r&&(r="task");var i,s,o,a=0,u=!1,c=0;function l(){for(var n=[],s=0;s0},start:function(){for(var t=[],i=0;i0&&(a=oi(a,i)),o.setOnFullQueueCb((function(){a.isRunning()&&(n.info(ht,[o.name]),a.execute())})),a}function ui(e,t){var n=function(e,t){var n={};if(Array.isArray(e)&&l(t))for(var r=0;r0?n=t:e.error(vn,[Nn,bi]):e.error(mn,[Nn,bi]),n}(e,t);return n&&(ki[n]?(e.warn(Wt,[ki[n]+" factory/ies with this SDK Key"]),ki[n]++):(ki[n]=1,Object.keys(ki).length>1&&e.warn(Wt,["an instance of the Split factory"]))),n}function wi(e){var t=e();return function(){return Math.round(e()-t)}}var Ni=((Ei={})[k]=0,Ei[w]=1,Ei[N]=2,Ei),_i=((Ti={})[E]=0,Ti[b]=1,Ti[T]=2,Ti),Ai=((Ri={})[L]=1,Ri[O]=2,Ri[F]=3,Ri);function Ii(e,t){return{name:"telemetry config",isEmpty:function(){return!1},clear:function(){},pop:function(){var n,r,i=t.urls,s=t.scheduler,o=xr(t),a=function(e){var t=0;e.validFilters.forEach((function(e){"bySet"===e.type&&(t+=e.values.length)}));var n=e.groupedFilters.bySet.length;return{flagSetsTotal:t,flagSetsIgnored:t-n}}(t.sync.__splitFiltersValidation),u=a.flagSetsTotal,c=a.flagSetsIgnored;return Vn((n=t.mode,r=t.storage.type,{oM:Ni[n],st:r.toLowerCase(),aF:Object.keys(ki).length,rF:Object.keys(ki).reduce((function(e,t){return e+ki[t]-1}),0)}),{sE:t.streamingEnabled,rR:{sp:s.featuresRefreshRate/1e3,se:o?s.segmentsRefreshRate/1e3:void 0,ms:o?void 0:s.segmentsRefreshRate/1e3,im:s.impressionsRefreshRate/1e3,ev:s.eventsPushRate/1e3,te:s.telemetryRefreshRate/1e3},uO:{s:i.sdk!==lr.urls.sdk,e:i.events!==lr.urls.events,a:i.auth!==lr.urls.auth,st:i.streaming!==lr.urls.streaming,t:i.telemetry!==lr.urls.telemetry},iQ:s.impressionsQueueSize,eQ:s.eventsQueueSize,iM:_i[t.sync.impressionsMode],iL:!!t.impressionListener,hP:!1,tR:e.getTimeUntilReady(),tC:e.getTimeUntilReadyFromCache(),nR:e.getNonReadyUsage(),t:e.popTags(),i:t.integrations&&t.integrations.map((function(e){return e.type})),uC:t.userConsent?Ai[t.userConsent]:0,fsT:u,fsI:c})}}}var Oi=9e5;function Fi(e){var t=e.settings,n=t.log,r=t.core.key,i=e.splitApi,s=i.postUniqueKeysBulkCs,o=i.postUniqueKeysBulkSs,a=e.storage.uniqueKeys,u=si(n,void 0!==r?s:o,a,Oi);return a.setOnFullQueueCb((function(){u.isRunning()&&(n.info(ht,[a.name]),u.execute())})),u}function Li(e){var t=[ci(e),ai(e),hi(e),Fi(e)],n=function(e){var t=e.storage.telemetry,n=e.platform.now;if(t&&n){var r=e.settings,i=e.settings,s=i.log,o=i.scheduler.telemetryRefreshRate,a=e.splitApi,u=e.readiness,c=e.sdkReadinessManager,l=wi(n),f=oi(si(s,a.postMetricsUsage,t,o,void 0,0,!0),o);return u.gate.once(yi,(function(){t.recordTimeUntilReadyFromCache(l())})),c.incInternalReadyCbCount(),u.gate.once(vi,(function(){t.recordTimeUntilReady(l()),f.isRunning()&&si(s,a.postMetricsConfig,Ii(t,r),0,void 0,0,!0).execute()})),f}}(e);return{start:function(e){e||t.forEach((function(e){return e.start()})),n&&n.start()},stop:function(e){t.forEach((function(e){return e.stop()})),!e&&n&&n.stop()},isRunning:function(){return t.some((function(e){return e.isRunning()}))},execute:function(e){var r=e?[]:t.map((function(e){return e.execute()}));return n&&r.push(n.execute()),Promise.all(r)},isExecuting:function(){return t.some((function(e){return e.isExecuting()}))}}}var Di,Mi=600,Ki="PUSH_NONRETRYABLE_ERROR",xi="PUSH_RETRYABLE_ERROR",Pi="PUSH_SUBSYSTEM_UP",Ui="PUSH_SUBSYSTEM_DOWN",Bi="MEMBERSHIPS_MS_UPDATE",ji="MEMBERSHIPS_LS_UPDATE",qi="SEGMENT_UPDATE",zi="SPLIT_KILL",Wi="SPLIT_UPDATE",Gi="RB_SEGMENT_UPDATE",Hi="CONTROL",Qi="OCCUPANCY";function Vi(e){var t=e.userConsent;return!t||t===O}function Ji(e,t){return function(n){var r=n.settings,i=n.settings,s=i.log,o=i.streamingEnabled,a=i.sync.enabled,u=n.telemetryTracker,c=n.storage,l=n.readiness,f=e&&e(n),h=a&&o&&f&&t?t(n,f):void 0,p=Li(n);h&&(h.on(Pi,(function(){s.info(mt),f.isRunning()&&(f.stop(),u.streamingEvent(ve,Se)),f.syncAll()})),h.on(Ui,(function(){f.isRunning()?s.info(dt):(s.info(gt),f.start(),u.streamingEvent(ve,be))})));var g=!1,d=!0;return{pollingManager:f,pushManager:h,submitterManager:p,start:function(){return g=!0,p.start(!Vi(r)),Promise.resolve(!!c.validateCache&&c.validateCache()).then((function(e){g&&(d&&e&&l.splits.emit(gi),f&&(a?h?(d&&f.syncAll(),h.start()):f.start():d&&f.syncAll()),d=!1)}))},stop:function(){g=!1,h&&h.stop(),f&&f.isRunning()&&f.stop(),p.stop()},isRunning:function(){return g},flush:function(){return p.execute(!Vi(r))},shared:function(e,t,n){if(f){var r=f.add(e,t,n);return a&&h&&h.add(e,r),g&&(a?h?f.isRunning()?wr(n)&&r.start():r.execute():wr(n)&&r.start():t.isReady()||r.execute()),{stop:function(){var t=f.get(e);t&&(h&&h.remove(e),t.isRunning()&&t.stop(),f.remove(e))},flush:function(){return Promise.resolve()}}}}}}}!function(e){e.STREAMING_DISABLED="STREAMING_DISABLED",e.STREAMING_PAUSED="STREAMING_PAUSED",e.STREAMING_RESUMED="STREAMING_RESUMED",e.STREAMING_RESET="STREAMING_RESET"}(Di||(Di={}));var Yi=function(){function e(t,n,r){this.baseMillis=e.__TEST__BASE_MILLIS||n||e.DEFAULT_BASE_MILLIS,this.maxMillis=e.__TEST__MAX_MILLIS||r||e.DEFAULT_MAX_MILLIS,this.attempts=0,this.cb=t}return e.prototype.scheduleCall=function(){var e=this,t=Math.min(this.baseMillis*Math.pow(2,this.attempts),this.maxMillis);return this.timeoutID&&clearTimeout(this.timeoutID),this.timeoutID=setTimeout((function(){e.timeoutID=void 0,e.cb()}),t),this.attempts++,t},e.prototype.reset=function(){this.attempts=0,this.timeoutID&&(clearTimeout(this.timeoutID),this.timeoutID=void 0)},e.DEFAULT_BASE_MILLIS=1e3,e.DEFAULT_MAX_MILLIS=18e5,e}();var $i=[/control_pri$/,/control_sec$/],Zi=[10,20];function Xi(e,t,n){var r=function(e,t){var n=$i.map((function(e){return{regex:e,hasPublishers:!0,oTime:-1,cTime:-1}})),r=!0,i=!0;return{handleOpen:function(){t.streamingEvent(he),e.emit(Pi)},isStreamingUp:function(){return i&&r},handleOccupancyEvent:function(s,o,a){for(var u=0;uc.oTime){c.oTime=a,c.hasPublishers=0!==s;var l=n.some((function(e){return e.hasPublishers}));i&&(!l&&r?e.emit(Ui):l&&!r&&e.emit(Pi)),r=l}return}}},handleControlEvent:function(s,o,a){if(s!==Di.STREAMING_RESET)for(var u=0;uc.cTime&&(c.cTime=a,s===Di.STREAMING_DISABLED?(t.streamingEvent(pe,Te),e.emit(Ki)):r&&(s===Di.STREAMING_PAUSED&&i?(t.streamingEvent(pe,ke),e.emit(Ui)):s!==Di.STREAMING_RESUMED||i||(t.streamingEvent(pe,Re),e.emit(Pi))),i=s===Di.STREAMING_RESUMED))}else e.emit(s)}}}(t,n);return{handleOpen:function(){r.handleOpen()},handleError:function(r){var i=r;try{i=function(e){return l(e.data)&&(e.parsedData=JSON.parse(e.data)),e}(r)}catch(t){e.warn(_t,[t])}var s=i.parsedData&&i.parsedData.message||i.message;e.error(nn,[s]),!function(e){if(e.parsedData&&e.parsedData.code){var t=e.parsedData.code;if(n.streamingEvent(me,t),40140<=t&&t<=40149)return!0;if(4e4<=t&&t<=49999)return!1}else n.streamingEvent(ge,Ee);return!0}(i)?t.emit(Ki):t.emit(xi)},handleMessage:function(n){var i;try{if(i=function(e){if(e.data){var t=JSON.parse(e.data);return t.parsedData=JSON.parse(t.data),t.name&&"[meta]occupancy"===t.name&&(t.parsedData.type=Qi),t}}(n),!i)return}catch(t){return void e.warn(At,[t])}var s=i.parsedData,o=i.data,a=i.channel,u=i.timestamp;if(e.debug(We,[o]),r.isStreamingUp()||-1!==[Qi,Hi].indexOf(s.type))switch(s.type){case Wi:case qi:case Bi:case ji:case zi:case Gi:t.emit(s.type,s);break;case Qi:r.handleOccupancyEvent(s.metrics.publishers,a,u);break;case Hi:r.handleControlEvent(s.controlType,a,u)}}}}var es=1e4,ts=6e4,ns=10;function rs(e,t,n,r){var i,s,o;function a(t){var i,a,u,c=0,l=-1,f=!1,h=new Yi(p);function p(){if(i=!0,c>Math.max(l,t.getChangeNumber())){f=!1;var g=c;(s?new Promise((function(e){o=setTimeout((function(){s=void 0,n.execute(u,!0,a?c:void 0).then(e)}),s)})):n.execute(u,!0,a?c:void 0)).then((function(n){if(i){if(!1!==n){var s=t.getChangeNumber();l=s>-1?s:Math.max(l,g)}if(f)p();else{u&&r.trackUpdatesFromSSE(ne);var o=h.attempts+1;if(c<=l)return e.debug("Refresh completed"+(a?" bypassing the CDN":"")+" in "+o+" attempts."),void(i=!1);if(o(n.getChangeNumber(r)||-1)?(a=!1,t.execute(!1,r,!0,s?o:void 0).then((function(){if(i)if(a)c();else{var t=u.attempts+1;if(o<=(n.getChangeNumber(r)||-1))return e.debug("Refresh completed"+(s?" bypassing the CDN":"")+" in "+t+" attempts."),void(i=!1);if(t>>1|(21845&h)<<1;p=(61680&(p=(52428&p)>>>2|(13107&p)<<2))>>>4|(3855&p)<<4,f[h]=((65280&p)>>>8|(255&p)<<8)>>>1}var g=function(e,n,r){for(var i=e.length,s=0,o=new t(n);s>>c]=l}else for(a=new t(i),s=0;s>>15-e[s]);return a},d=new e(288);for(h=0;h<144;++h)d[h]=8;for(h=144;h<256;++h)d[h]=9;for(h=256;h<280;++h)d[h]=7;for(h=280;h<288;++h)d[h]=8;var m=new e(32);for(h=0;h<32;++h)m[h]=5;var v=g(d,9,1),y=g(m,5,1),S=function(e){for(var t=e[0],n=1;nt&&(t=e[n]);return t},b=function(e,t,n){var r=t/8|0;return(e[r]|e[r+1]<<8)>>(7&t)&n},E=function(e,t){var n=t/8|0;return(e[n]|e[n+1]<<8|e[n+2]<<16)>>(7&t)},T=["unexpected EOF","invalid block type","invalid length/literal","invalid distance","stream finished","no stream handler",,"no callback","invalid UTF-8 data","extra field too long","date not in range 1980-2099","filename too long","stream finishing","invalid zip data"],R=function(e,t,n){var r=new Error(t||T[e]);if(r.code=e,Error.captureStackTrace&&Error.captureStackTrace(r,R),!n)throw r;return r},k=function(o,a,c){var f=o.length;if(!f||c&&c.f&&!c.l)return a||new e(0);var h=!a||c,p=!c||c.i;c||(c={}),a||(a=new e(3*f));var d=function(t){var n=a.length;if(t>n){var r=new e(Math.max(2*n,t));r.set(a),a=r}},m=c.f||0,T=c.p||0,k=c.b||0,C=c.l,w=c.d,N=c.m,_=c.n,A=8*f;do{if(!C){m=b(o,T,1);var I=b(o,T+1,3);if(T+=3,!I){var O=o[(q=4+((T+7)/8|0))-4]|o[q-3]<<8,F=q+O;if(F>f){p&&R(0);break}h&&d(k+O),a.set(o.subarray(q,F),k),c.b=k+=O,c.p=T=8*F,c.f=m;continue}if(1==I)C=v,w=y,N=9,_=5;else if(2==I){var L=b(o,T,31)+257,D=b(o,T+10,15)+4,M=L+b(o,T+5,31)+1;T+=14;for(var K=new e(M),x=new e(19),P=0;P>>4)<16)K[P++]=q;else{var W=0,G=0;for(16==q?(G=3+b(o,T,3),T+=2,W=K[P-1]):17==q?(G=3+b(o,T,7),T+=3):18==q&&(G=11+b(o,T,127),T+=7);G--;)K[P++]=W}}var H=K.subarray(0,L),Q=K.subarray(L);N=S(H),_=S(Q),C=g(H,N,1),w=g(Q,_,1)}else R(1);if(T>A){p&&R(0);break}}h&&d(k+131072);for(var V=(1<>>4;if((T+=15&W)>A){p&&R(0);break}if(W||R(2),$<256)a[k++]=$;else{if(256==$){Y=T,C=null;break}var Z=$-254;if($>264){var X=r[P=$-257];Z=b(o,T,(1<>>4;ee||R(3),T+=15ⅇQ=l[te];if(te>3){X=i[te];Q+=E(o,T)&(1<A){p&&R(0);break}h&&d(k+131072);for(var ne=k+Z;kr.length)&&(s=r.length);var o=new(r instanceof t?t:r instanceof n?n:e)(s-i);return o.set(r.subarray(i,s)),o}(a,0,k)};return{gunzipSync:function(t,n){return k(t.subarray(function(e){31==e[0]&&139==e[1]&&8==e[2]||R(6,"invalid gzip data");var t=e[3],n=10;4&t&&(n+=e[10]|2+(e[11]<<8));for(var r=(t>>3&1)+(t>>4&1);r>0;r-=!e[n++]);return n+(2&t)}(t),-8),n||new e((i=(r=t).length,(r[i-4]|r[i-3]<<8|r[i-2]<<16|r[i-1]<<24)>>>0)));var r,i},unzlibSync:function(e,t){return k(((8!=(15&(n=e)[0])||n[0]>>>4>7||(n[0]<<8|n[1])%31)&&R(6,"invalid zlib data"),32&n[1]&&R(6,"invalid zlib data: preset dictionaries not supported"),e.subarray(2,-4)),t);var n}}}(),os="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";function as(e){var t=String(e).replace(/[=]+$/,"");if(t.length%4==1)throw new Error("'atob' failed: The string to be decoded is not correctly encoded.");for(var n="",r=0,i=void 0,s=void 0,o=0;s=t.charAt(o++);~s&&(i=r%4?64*i+s:s,r++%4)?n+=String.fromCharCode(255&i>>(-2*r&6)):0)s=os.indexOf(s);return n}var us=String.fromCharCode;function cs(e){var t,n,r,i,s;return n=function(e){if("string"!=typeof e)throw TypeError("Illegal argument: "+typeof e);var t=0;return function(){return t>=e.length?null:e.charCodeAt(t++)}}(e),i=[],s=[],r=t=function(){if(0===arguments.length)return s.join("")+us.apply(String,i);i.length+arguments.length>1024&&(s.push(us.apply(String,i)),i.length=0),Array.prototype.push.apply(i,arguments)},function(e,t){for(var n,r=null;null!==(n=null!==r?r:e());)n>=55296&&n<=57343&&null!==(r=e())&&r>=56320&&r<=57343?(t(1024*(n-55296)+r-56320+65536),r=null):t(n);null!==r&&t(r)}(n,(function(e){!function(e,t){var n=null;for("number"==typeof e&&(n=e,e=function(){return null});null!==n||null!==(n=e());)n<128?t(127&n):n<2048?(t(n>>6&31|192),t(63&n|128)):n<65536?(t(n>>12&15|224),t(n>>6&63|128),t(63&n|128)):(t(n>>18&7|240),t(n>>12&63|128),t(n>>6&63|128),t(63&n|128)),n=null}(e,r)})),t()} +/*! + * +----------------------------------------------------------------------------------+ + * | murmurHash3.js v3.0.0 (http://github.com/karanlyons/murmurHash3.js) | + * | A TypeScript/JavaScript implementation of MurmurHash3's hashing algorithms. | + * |----------------------------------------------------------------------------------| + * | Copyright (c) 2012-2020 Karan Lyons. Freely distributable under the MIT license. | + * +----------------------------------------------------------------------------------+ + */function ls(e,t){return(65535&e)*t+(((e>>>16)*t&65535)<<16)}function fs(e,t){return e<>>32-t}function hs(e,t){t=t||0;for(var n,r=(e=e||"").length%4,i=e.length-r,s=t,o=0,a=3432918353,u=461845907,c=0;c>>16,2246822507),(s=(n=ls(n^=n>>>13,3266489909))^n>>>16)>>>0}function ps(e,t){return hs(cs(e),t>>>0)}function gs(e,t){return Math.abs(ps(e,t)%100)+1}var ds=1,ms=2;function vs(e,t){var n,r=as(e),i=(n=r.split("").map((function(e){return e.charCodeAt(0)})),new Uint8Array(n));if("string"==typeof ss)throw new Error(ss);if(t===ds)return ss.gunzipSync(i);if(t===ms)return ss.unzlibSync(i);throw new Error("Invalid compression algorithm #"+t)}function ys(e,t,n){void 0===n&&(n=!0);var r,i=vs(e,t),s=(r=i,String.fromCharCode.apply(null,r));return n&&(s=s.replace(/\d+/g,'"$&"')),JSON.parse(s)}var Ss=6e4;function bs(e,t){if(0===e.h)return 0;var n=e.i||Ss;return ps(t,e.s||0)%n}function Es(e,t,n,r,i,s){var o=u(t.splits),a=u(t.rbSegments);function u(t){var r,u,c,l=-1,f=!1,h=new Yi(p,es,ts);function p(){r=!0,l>t.getChangeNumber()?(f=!1,n.execute(!0,u?l:void 0,c).then((function(){if(r)if(f)p();else{c&&i.trackUpdatesFromSSE(J),s&&s.execute(!0);var t=h.attempts+1;if(o.isSync()&&a.isSync())return e.debug("Refresh completed"+(u?" bypassing the CDN":"")+" in "+t+" attempts."),void(r=!1);if(t0?ys(i,r,!1):JSON.parse(as(i)));if(n)return void(t.type===Gi?a:o).put(t,n)}catch(n){e.warn(Ht,[t.type,n])}var r,i;(t.type===Gi?a:o).put(t)},killSplit:function(e){var n=e.changeNumber,i=e.splitName,s=e.defaultTreatment;t.splits.killLocally(i,s,n)&&r.emit(pi,!0),o.put({changeNumber:n})},stop:function(){o.stop(),a.stop()}}}function Ts(e){return function(t){return e(t).then((function(e){return e.json()})).then((function(e){if(e.token){var t=(r=e.token,i=r.split(".")[1].replace(/-/g,"+").replace(/_/g,"/"),s=decodeURIComponent(as(i).split("").map((function(e){return"%"+("00"+e.charCodeAt(0).toString(16)).slice(-2)})).join("")),JSON.parse(s));if("number"!=typeof t.iat||"number"!=typeof t.exp)throw new Error('token properties "issuedAt" (iat) or "expiration" (exp) are missing or invalid');var n=JSON.parse(t["x-ably-capability"]);return Vn({decodedToken:t,channels:n},e)}var r,i,s;return e}))}}function Rs(e){return function(e){for(var t=String(e),n="",r=void 0,i=void 0,s=0,o=os;t.charAt(0|s)||(o="=",s%1);n+=o.charAt(63&r>>8-s%1*8)){if((i=t.charCodeAt(s+=3/4))>255)throw new Error("'btoa' failed: The string to be encoded contains characters outside of the Latin1 range.");r=r<<8|i}return n}(ps(e,0).toString())}var ks=/^control_/;var Cs,ws,Ns=function(){function e(e,t){var n=t.getEventSource,r=t.getOptions;if(this.settings=e,this.eventSource=n&&n(e),!this.eventSource)throw new Error("EventSource API is not available.");this.headers=function(e){var t={SplitSDKClientKey:l(e.core.authorizationKey)?e.core.authorizationKey.slice(-4):"",SplitSDKVersion:e.version},n=e.runtime,r=n.ip,i=n.hostname;return r&&(t.SplitSDKMachineIP=r),i&&(t.SplitSDKMachineName=i),t}(e),this.options=r&&r(e)}return e.prototype.setEventHandler=function(e){this.handler=e},e.prototype.open=function(e){var t;this.close();var n=Object.keys(e.channels).map((function(e){var t=ks.test(e)?"[?occupancy=metrics.publishers]":"";return encodeURIComponent(t+e)})).join(","),r=this.settings.urls.streaming+"/sse?channels="+n+"&accessToken="+e.token+"&v=1.1&heartbeats=true",i=xr(this.settings);this.connection=new this.eventSource(i?r:r+"&SplitSDKVersion="+this.headers.SplitSDKVersion+"&SplitSDKClientKey="+this.headers.SplitSDKClientKey,Vn(i?{headers:$r(this.settings,this.headers)}:(null===(t=this.settings.sync.requestOptions)||void 0===t?void 0:t.getHeaderOverrides)?{headers:$r(this.settings,{})}:{},this.options)),this.handler&&(this.connection.addEventListener("open",this.handler.handleOpen),this.connection.addEventListener("message",this.handler.handleMessage),this.connection.addEventListener("error",this.handler.handleError))},e.prototype.close=function(){this.connection&&this.connection.close()},e}();function _s(e,t){e=[e[0]>>>16,65535&e[0],e[1]>>>16,65535&e[1]],t=[t[0]>>>16,65535&t[0],t[1]>>>16,65535&t[1]];var n=[0,0,0,0];return n[3]+=e[3]+t[3],n[2]+=n[3]>>>16,n[3]&=65535,n[2]+=e[2]+t[2],n[1]+=n[2]>>>16,n[2]&=65535,n[1]+=e[1]+t[1],n[0]+=n[1]>>>16,n[1]&=65535,n[0]+=e[0]+t[0],n[0]&=65535,[n[0]<<16|n[1],n[2]<<16|n[3]]}function As(e,t){e=[e[0]>>>16,65535&e[0],e[1]>>>16,65535&e[1]],t=[t[0]>>>16,65535&t[0],t[1]>>>16,65535&t[1]];var n=[0,0,0,0];return n[3]+=e[3]*t[3],n[2]+=n[3]>>>16,n[3]&=65535,n[2]+=e[2]*t[3],n[1]+=n[2]>>>16,n[2]&=65535,n[2]+=e[3]*t[2],n[1]+=n[2]>>>16,n[2]&=65535,n[1]+=e[1]*t[3],n[0]+=n[1]>>>16,n[1]&=65535,n[1]+=e[2]*t[2],n[0]+=n[1]>>>16,n[1]&=65535,n[1]+=e[3]*t[1],n[0]+=n[1]>>>16,n[1]&=65535,n[0]+=e[0]*t[3]+e[1]*t[2]+e[2]*t[1]+e[3]*t[0],n[0]&=65535,[n[0]<<16|n[1],n[2]<<16|n[3]]}function Is(e,t){return 32===(t%=64)?[e[1],e[0]]:t<32?[e[0]<>>32-t,e[1]<>>32-t]:(t-=32,[e[1]<>>32-t,e[0]<>>32-t])}function Os(e,t){return 0===(t%=64)?e:t<32?[e[0]<>>32-t,e[1]<>>1]),e=Fs(e=As(e,[4283543511,3981806797]),[0,e[0]>>>1]),e=Fs(e=As(e,[3301882366,444984403]),[0,e[0]>>>1])}function Ds(e,t){return function(e,t){t=t||0;for(var n=(e=e||"").length%16,r=e.length-n,i=[0,t],s=[0,t],o=[0,0],a=[0,0],u=[2277735313,289559509],c=[1291169091,658871167],l=0;l>>0).toString(16)).slice(-8)+("00000000"+(i[1]>>>0).toString(16)).slice(-8)+("00000000"+(s[0]>>>0).toString(16)).slice(-8)+("00000000"+(s[1]>>>0).toString(16)).slice(-8)}(cs(e),t>>>0)}function Ms(e){var t,n,r,i=[0];for(t=0;t=0;n-=1)i[n]=16*i[n]+r,r=i[n]/10|0,i[n]%=10;for(;r>0;)i.unshift(r%10),r=r/10|0}return i.join("")}function Ks(e,t){var n,r=e.settings,s=e.storage,o=e.splitApi,a=e.readiness,u=e.platform,c=e.telemetryTracker,l=xr(r)?void 0:Mr(r.core.key),f=r.log;try{n=new Ns(r,u)}catch(e){return void f.warn(It,[e])}var h=Ts(o.fetchAuth),p=new u.EventEmitter,g=Xi(f,p,c);n.setEventHandler(g);var d,m,v,y,S=l?void 0:is(f,t.segmentsSyncTask,s.segments),b=Es(f,s,t.splitsSyncTask,a.splits,c,l?void 0:t.segmentsSyncTask),E={},T={},R=!1,k=new Yi(C,r.scheduler.pushRetryBackoffBase);function C(){if(!d){f.info(ct),d=!1;var e=l?Object.keys(T):void 0;h(e).then((function(t){if(!d)return t.pushEnabled?void(e&&e.length=0?e.connDelay:60;f.info(at,[r,i]),v=setTimeout(C,1e3*r),y=setTimeout((function(){d||n.open(e)}),1e3*i),c.streamingEvent(de,t.exp)}(t)):(f.info(lt),void p.emit(Ki))})).catch((function(e){if(!d){if(f.error(rn,[e.message]),e.statusCode>=400&&e.statusCode<500)return c.streamingEvent(ye),void p.emit(Ki);p.emit(xi)}}))}}function w(){d||(d=!0,n.close(),f.info(ft),v&&clearTimeout(v),y&&clearTimeout(y),k.reset(),N())}function N(){b.stop(),l?i(T,(function(e){return e.worker.stop()})):S.stop()}function _(e){switch(e.u){case ws.BoundedFetchRequest:var t;try{o=e.d,a=e.c,t=vs(o,a)}catch(e){f.warn(Gt,["BoundedFetchRequest",e]);break}return void i(T,(function(n,r){var i,s,o,a,u=n.hash64,c=n.worker;i=t,s=u.hex,o=parseInt(s.slice(8),16)%(8*i.length),a=o%8,(i[Math.floor(o/8)]&1<0&&c.put(e,void 0,bs(e,r))}));case ws.KeyList:var n,r,s=void 0;try{s=ys(e.d,e.c),n=new Set(s.a),r=new Set(s.r)}catch(e){f.warn(Gt,["KeyList",e]);break}if(!e.n||!e.n.length){f.warn(Gt,["KeyList","No segment name was provided"]);break}return void i(T,(function(t){var i=t.hash64,s=t.worker,o=!!n.has(i.dec)||!r.has(i.dec)&&void 0;void 0!==o&&s.put(e,{added:o?[e.n[0]]:[],removed:o?[]:[e.n[0]]})}));case ws.SegmentRemoval:if(!e.n||!e.n.length){f.warn(Gt,["SegmentRemoval","No segment name was provided"]);break}return void i(T,(function(t){t.worker.put(e,{added:[],removed:e.n})}))}var o,a;i(T,(function(t,n){t.worker.put(e,void 0,bs(e,n))}))}return p.on(Ui,N),p.on(Pi,(function(){k.reset()})),p.on(Ki,(function(){m=!0,w(),p.emit(Ui)})),p.on(xi,(function(){n.close();var e=k.scheduleCall();f.info(ut,[e/1e3]),p.emit(Ui)})),p.on(Di.STREAMING_RESET,(function(){d||(v&&clearTimeout(v),C())})),p.on(zi,b.killSplit),p.on(Wi,b.put),p.on(Gi,b.put),l?(p.on(Bi,_),p.on(ji,_)):p.on(qi,S.put),Vn(Object.create(p),{stop:function(){w(),l&&this.remove(l)},start:function(){m||!1===d||(d=!1,l?this.add(l,t.segmentsSyncTask):setTimeout(C))},isRunning:function(){return!1===d},add:function(e,t){var n,r,i=Rs(e);E[i]||(E[i]=e,T[e]={hash64:(n=e,r=Ds(n).slice(0,16),{hex:r,dec:Ms(r)}),worker:rs(f,s,t,c)},R=!0,this.isRunning()&&setTimeout((function(){R&&(R=!1,C())}),0))},remove:function(e){var t=Rs(e);delete E[t],delete T[e]}})}function xs(e,t,n,r,i,s,o){var a=n.segments,u=n.largeSegments,c=!0,l=!0;function f(e){return l&&(e=Zr(i,e)),e}function h(e){var t;void 0!==e.type?t=e.type===ji?u.resetSegments(e):a.resetSegments(e):(t=a.resetSegments(e.ms||{}),t=u.resetSegments(e.ls||{})||t),n.save&&n.save(),wr(n)&&(t||c)&&(c=!1,r.emit(di))}function p(n,r,i,a){var u=r?new Promise((function(e){h(r),e(!0)})):t(o,i,a,f).then((function(e){return l=!1,h(e),!0}));return u.catch((function(t){return l&&s>n?(n+=1,e.warn(wt,[n,t]),p(n)):(l=!1,!1)}))}return function(e,t,n){return p(0,e,t,n)}}!function(e){e[e.None=0]="None",e[e.Gzip=1]="Gzip",e[e.Zlib=2]="Zlib"}(Cs||(Cs={})),function(e){e[e.UnboundedFetchRequest=0]="UnboundedFetchRequest",e[e.BoundedFetchRequest=1]="BoundedFetchRequest",e[e.KeyList=2]="KeyList",e[e.SegmentRemoval=3]="SegmentRemoval"}(ws||(ws={}));var Ps=36e5,Us=24*Ps;function Bs(e,t,n){var r,i=t.log,s=xr(t)?Us:Ps;return function o(a,u,c,l,f){r&&Date.now()-r>s&&(t.sync.flagSpecVersion=Ce);var h=e(a,u,c,t.sync.flagSpecVersion===Ce?l:void 0).catch((function(n){if((!n.statusCode||400===n.statusCode)&&function(e){return e.urls.sdk!==lr.urls.sdk}(t)&&t.sync.flagSpecVersion===Ce)return i.error(xn+"Proxy error detected. Retrying with spec 1.2. If you are using Split Proxy, please upgrade to latest version"),r=Date.now(),t.sync.flagSpecVersion="1.2",e(a,u,c);throw n}));return f&&(h=f(h)),h.then((function(e){return e.json()})).then((function(e){return e.splits?{ff:{d:e.splits,s:e.since,t:e.till}}:r?(i.info(xn+"Proxy error recovered"),r=void 0,o(-1,void 0,void 0,-1).then((function(e){return Promise.all([n.splits.clear(),n.rbSegments.clear()]).then((function(){return e}))}))):e}))}}function js(e,t){void 0===t&&(t=we);var n=e,r=n.conditions,i=void 0===r?[]:r,s=n.excluded,o=new Set;s&&s.segments&&s.segments.forEach((function(e){var n=e.type,r=e.name;(n===_e&&t===we||n===Ae&&t===Ne)&&o.add(r)}));for(var a=0;a0)return e.sets&&e.sets.some((function(e){return r.indexOf(e)>-1}));var o=i.length>0,a=s.length>0;if(!o&&!a)return!0;var u=o&&i.indexOf(e.name)>-1,c=a&&s.some((function(t){return p(e.name,t)}));return u||c}(r,n)?e.removed.push(r):(e.added.push(r),js(r).forEach((function(e){t.add(e)}))),e}),{added:[],removed:[]})}function zs(e,t,n,r,i,s,o,a){void 0===s&&(s=0),void 0===o&&(o=0);var u=n.splits,c=n.rbSegments,l=n.segments,f=!0;function h(e){return f&&s&&(e=Zr(s,e)),e}return function(s,p,g){return Promise.all([u.getChangeNumber(),c.getChangeNumber()]).then((function d(m,v){void 0===v&&(v=0);var y=m[0],S=m[1];return e.debug(je,m),Promise.resolve(g?g.type===Wi?Promise.resolve(c.contains(js(g.payload,Ne))).then((function(e){return e?{ff:{d:[g.payload],t:g.changeNumber}}:t(y,s,p,S,h)})):{rbs:{d:[g.payload],t:g.changeNumber}}:t(y,s,p,S,h)).then((function(t){f=!1;var s=new Set,o=!1;if(t.ff){var h=qs(t.ff.d,s,r),p=h.added,g=h.removed;e.debug(qe,[p.length,g.length]),o=u.update(p,g,t.ff.t)}var d=!1;if(t.rbs){var m=qs(t.rbs.d,s);p=m.added,g=m.removed;e.debug(ze,[p.length,g.length]),d=c.update(p,g,t.rbs.t)}return Promise.all([o,d,l.registerSegments(qr(s))]).then((function(e){var t=e[0],r=e[1];return n.save&&n.save(),!i||Promise.resolve(!i.splitsArrived||(t||r)&&(a||function(e){return Promise.resolve(e.getRegisteredSegments()).then((function(t){return Promise.all(t.map((function(t){return e.getChangeNumber(t)}))).then((function(e){return e.every((function(e){return void 0!==e}))}))}))}(l))).catch((function(){return!1})).then((function(e){return e&&i.emit(pi),!0}))}))})).catch((function(t){return e.warn(Nt,[t]),f&&o>v?(v+=1,e.info(ot,[v,t]),d(m,v)):(f=!1,!1)}))}))}}function Ws(e){var t=e.splitApi,n=e.storage,r=e.readiness,s=e.settings,o=s.log,a=function(e,t,n,r,i){return ii(r.log,zs(r.log,Bs(e,r,t),t,r.sync.__splitFiltersValidation,n.splits,r.startup.requestTimeoutBeforeReady,r.startup.retriesOnFailureBeforeReady,i),r.scheduler.featuresRefreshRate,"splitChangesUpdater")}(t.fetchSplitChanges,n,r,s,!0),u={},c=h(Mr(s.core.key),r,n);function l(){i(u,(function(e){e.start()}))}function f(){i(u,(function(e){e.isRunning()&&e.stop()}))}function h(e,n,r){var i=function(e,t,n,r,i){return ii(r.log,xs(r.log,function(e){return function(t,n,r,i){var s=e(t,n,r);return i&&(s=i(s)),s.then((function(e){return e.json()}))}}(e),t,n.segments,r.startup.requestTimeoutBeforeReady,r.startup.retriesOnFailureBeforeReady,i),r.scheduler.segmentsRefreshRate,"mySegmentsUpdater")}(t.fetchMemberships,r,n,s,e);function o(){n.isReady()||wr(r)||n.segments.emit(di)}return wr(r)?n.splits.once(pi,o):setTimeout(o,0),u[e]=i,i}return r.splits.on(pi,(function(){if(a.isRunning()){var e=wr(n);e!==c.isRunning()&&(o.info(rt,[e?"ON":"OFF"]),e?l():f())}})),{splitsSyncTask:a,segmentsSyncTask:c,start:function(){o.info(it),a.start(),wr(n)&&l()},stop:function(){o.info(st),a.isRunning()&&a.stop(),f()},isRunning:a.isRunning,syncAll:function(){var e=[a.execute()];return i(u,(function(t){e.push(t.execute())})),Promise.all(e)},add:h,remove:function(e){delete u[e]},get:function(e){return u[e]}}}function Gs(e){return null!=e&&"function"==typeof e.then}function Hs(e,t,n){return null==t||c(t)?t:(e.error(cn,[n,"attributes"]),!1)}function Qs(e,t,n){if(!Hs(e,t,n))return!1;var r=!0;return Object.keys(t).forEach((function(i){(function(e,t,n,r){if(!l(t)||0===t.length)return e.warn(r+": you passed an invalid attribute name, attribute name must be a non-empty string."),!1;var i=l(n),s=a(n),u=o(n),c=Array.isArray(n);return!!(i||s||u||c)||(e.warn(r+": you passed an invalid attribute value for "+t+". Acceptable types are: string, number, boolean and array of strings."),!1)})(e,i,t[i],n)||(r=!1)})),r}var Vs=/^[a-zA-Z0-9][-_.:a-zA-Z0-9]{0,79}$/,Js="event_type";var Ys={NULL:0,STRING:2,BOOLEAN:4,NUMBER:8},$s=300,Zs=32768,Xs=1024;function eo(e,t,n){if(null==t)return{properties:null,size:Xs};if(!c(t))return e.error(cn,[n,"properties"]),{properties:!1,size:Xs};var r=Object.keys(t),i=Vn({},t),s={properties:i,size:Xs};r.length>$s&&e.warn(Dt,[n]);for(var u=0;uZs){e.error(ln,[n]),s.properties=!1;break}}return s}var to=/[A-Z]/,no="traffic_type";function ro(e,t,n){return!t.isDestroyed()||(e.error(hn,[n]),!1)}function io(e,t,n,r){return!!t.isReadyFromCache()||(e.warn(Ct,[n,r?" for feature flag "+r.toString():""]),!1)}function so(e,t,n,r){return ro(e,t,n)&&io(e,t,n,r)}var oo="fallback - ",ao=function(){function e(e){void 0===e&&(e={}),this.fallbacks=e}return e.prototype.resolve=function(e,t){var n,r=null===(n=this.fallbacks.byFlag)||void 0===n?void 0:n[e];return r?this.copyWithLabel(r,t):this.fallbacks.global?this.copyWithLabel(this.fallbacks.global,t):{treatment:v,config:null,label:t}},e.prototype.copyWithLabel=function(e,t){return l(e)?{treatment:e,config:null,label:""+oo+t}:{treatment:e.treatment,config:e.config,label:""+oo+t}},e}(),uo="killed",co="default rule",lo="definition not found",fo="not ready",ho="exception",po="archived",go="not in split",mo="targeting rule type unsupported by sdk",vo="prerequisites not met",yo=oo+lo;function So(e,t,n,r,i){return!t.isReady()||r!==lo&&r!==yo&&null!=r||(e.warn(xt,[i,n]),!1)}function bo(e,t,n){e.warn(Ut,[n,t])}function Eo(e){var t=e.conditions,r=n(t,(function(e){return"ROLLOUT"===e.conditionType}));return r||(r=t[0]),r?r.partitions.map((function(e){return e.treatment})):[]}function To(e){return e?{name:e.name,trafficType:e.trafficTypeName,killed:e.killed,changeNumber:e.changeNumber||0,treatments:Eo(e),configs:e.configurations||{},sets:e.sets||[],defaultTreatment:e.defaultTreatment,impressionsDisabled:!0===e.impressionsDisabled,prerequisites:(e.prerequisites||[]).map((function(e){return{flagName:e.n,treatments:e.ts}}))}:null}function Ro(e){var t=[];return e.forEach((function(e){var n=To(e);n&&t.push(n)})),t}function ko(e,t,n){var r=n.readinessManager,i=n.sdkStatus,s=e.log,o=Ie(e.mode);return Vn(Object.create(i),{split:function(e){var n=Hn(s,e,z);if(!so(s,r,z)||!n)return o?Promise.resolve(null):null;var i=t.getSplit(n);return Gs(i)?i.catch((function(){return null})).then((function(e){return So(s,r,n,e,z),To(e)})):(So(s,r,n,i,z),To(i))},splits:function(){if(!so(s,r,W))return o?Promise.resolve([]):[];var e=t.getAll();return Gs(e)?e.catch((function(){return[]})).then(Ro):Ro(e)},names:function(){if(!so(s,r,G))return o?Promise.resolve([]):[];var e=t.getSplitNames();return Gs(e)?e.catch((function(){return[]})):e}})}var Co=function(){function e(){this.attributesCache={}}return e.prototype.setAttribute=function(e,t){return this.attributesCache[e]=t,!0},e.prototype.getAttribute=function(e){return this.attributesCache[e]},e.prototype.setAttributes=function(e){return this.attributesCache=Vn(this.attributesCache,e),!0},e.prototype.getAll=function(){return this.attributesCache},e.prototype.removeAttribute=function(e){return Object.keys(this.attributesCache).indexOf(e)>=0&&(delete this.attributesCache[e],!0)},e.prototype.clear=function(){return this.attributesCache={},!0},e}();function wo(e,t,n){var r=function(e,t){var n=new Co,r=t.getTreatment,i=t.getTreatmentWithConfig,s=t.getTreatments,o=t.getTreatmentsWithConfig,a=t.getTreatmentsByFlagSets,u=t.getTreatmentsWithConfigByFlagSets,c=t.getTreatmentsByFlagSet,l=t.getTreatmentsWithConfigByFlagSet;function f(e){var t=n.getAll();return Object.keys(t).length>0?Vn({},t,e):e}return Vn(t,{getTreatment:function(e,t,n,i){return r(e,t,f(n),i)},getTreatmentWithConfig:function(e,t,n,r){return i(e,t,f(n),r)},getTreatments:function(e,t,n,r){return s(e,t,f(n),r)},getTreatmentsWithConfig:function(e,t,n,r){return o(e,t,f(n),r)},getTreatmentsByFlagSets:function(e,t,n,r){return a(e,t,f(n),r)},getTreatmentsWithConfigByFlagSets:function(e,t,n,r){return u(e,t,f(n),r)},getTreatmentsByFlagSet:function(e,t,n,r){return c(e,t,f(n),r)},getTreatmentsWithConfigByFlagSet:function(e,t,n,r){return l(e,t,f(n),r)},setAttribute:function(t,r){var i={};return i[t]=r,!!Qs(e,i,"setAttribute")&&(e.debug("stored "+r+" for attribute "+t),n.setAttribute(t,r))},getAttribute:function(t){return e.debug("retrieved attribute "+t),n.getAttribute(t+"")},setAttributes:function(t){return!!Qs(e,t,"setAttributes")&&n.setAttributes(t)},getAttributes:function(){return n.getAll()},removeAttribute:function(t){return e.debug("removed attribute "+t),n.removeAttribute(t+"")},clearAttributes:function(){return n.clear()}})}(e,t);return Vn(r,{getTreatment:r.getTreatment.bind(r,n),getTreatmentWithConfig:r.getTreatmentWithConfig.bind(r,n),getTreatments:r.getTreatments.bind(r,n),getTreatmentsWithConfig:r.getTreatmentsWithConfig.bind(r,n),getTreatmentsByFlagSets:r.getTreatmentsByFlagSets.bind(r,n),getTreatmentsWithConfigByFlagSets:r.getTreatmentsWithConfigByFlagSets.bind(r,n),getTreatmentsByFlagSet:r.getTreatmentsByFlagSet.bind(r,n),getTreatmentsWithConfigByFlagSet:r.getTreatmentsWithConfigByFlagSet.bind(r,n),track:r.track.bind(r,n),isClientSide:!0,key:n})}var No={UNDEFINED:0,ALL_KEYS:1,IN_SEGMENT:2,WHITELIST:3,EQUAL_TO:4,GREATER_THAN_OR_EQUAL_TO:5,LESS_THAN_OR_EQUAL_TO:6,BETWEEN:7,EQUAL_TO_SET:8,CONTAINS_ANY_OF_SET:9,CONTAINS_ALL_OF_SET:10,PART_OF_SET:11,ENDS_WITH:12,STARTS_WITH:13,CONTAINS_STRING:14,IN_SPLIT_TREATMENT:15,EQUAL_TO_BOOLEAN:16,MATCHES_STRING:17,EQUAL_TO_SEMVER:18,GREATER_THAN_OR_EQUAL_TO_SEMVER:19,LESS_THAN_OR_EQUAL_TO_SEMVER:20,BETWEEN_SEMVER:21,IN_LIST_SEMVER:22,IN_LARGE_SEGMENT:23,IN_RULE_BASED_SEGMENT:24},_o={BOOLEAN:"BOOLEAN",STRING:"STRING",NUMBER:"NUMBER",SET:"SET",DATETIME:"DATETIME",NOT_SPECIFIED:"NOT_SPECIFIED"};function Ao(e){return e?e.segmentName||e.largeSegmentName:void 0}function Io(e){return e&&e.whitelist}function Oo(e){return e.value}function Fo(e){return new Date(e).setUTCHours(0,0,0,0)}function Lo(e){return new Date(e).setUTCSeconds(0,0)}function Do(e){var t=e.map((function(e){var t,n=e.matcherType,r=e.negate,i=e.keySelector,s=e.userDefinedSegmentMatcherData,o=e.userDefinedLargeSegmentMatcherData,a=e.whitelistMatcherData,u=e.unaryNumericMatcherData,c=e.betweenMatcherData,l=e.dependencyMatcherData,f=e.booleanMatcherData,h=e.stringMatcherData,p=e.betweenStringMatcherData,g=i&&i.attribute,d=function(e){var t=No[e];return t||No.UNDEFINED}(n),m=_o.STRING;return d===No.IN_SEGMENT?t=Ao(s):d===No.IN_LARGE_SEGMENT?t=Ao(o):d===No.EQUAL_TO?(t=Oo(u),m=_o.NUMBER,"DATETIME"===u.dataType&&(t=Fo(t),m=_o.DATETIME)):d===No.GREATER_THAN_OR_EQUAL_TO||d===No.LESS_THAN_OR_EQUAL_TO?(t=Oo(u),m=_o.NUMBER,"DATETIME"===u.dataType&&(t=Lo(t),m=_o.DATETIME)):d===No.BETWEEN?(t=c,m=_o.NUMBER,"DATETIME"===t.dataType&&(t=function(e){return{dataType:e.dataType,start:Lo(e.start),end:Lo(e.end)}}(t),m=_o.DATETIME)):d===No.BETWEEN_SEMVER?t=p:d===No.EQUAL_TO_SET||d===No.CONTAINS_ANY_OF_SET||d===No.CONTAINS_ALL_OF_SET||d===No.PART_OF_SET?(t=Io(a),m=_o.SET):d===No.WHITELIST||d===No.IN_LIST_SEMVER||d===No.STARTS_WITH||d===No.ENDS_WITH||d===No.CONTAINS_STRING?t=Io(a):d===No.IN_SPLIT_TREATMENT?(t=l,m=_o.NOT_SPECIFIED):d===No.EQUAL_TO_BOOLEAN?(m=_o.BOOLEAN,t=f):d===No.MATCHES_STRING||d===No.EQUAL_TO_SEMVER||d===No.GREATER_THAN_OR_EQUAL_TO_SEMVER||d===No.LESS_THAN_OR_EQUAL_TO_SEMVER?t=h:d===No.IN_RULE_BASED_SEGMENT&&(t=Ao(s),m=_o.NOT_SPECIFIED),{attribute:g,negate:r,type:d,name:n,value:t,dataType:m}}));return-1===r(t,(function(e){return e.type===No.UNDEFINED}))?t:[]}var Mo=function(){function e(e,t){if(100!==e[e.length-1])throw new RangeError("Provided invalid dataset as input");this._ranges=e,this._treatments=t}return e.parse=function(t){var n=t.reduce((function(e,t){var n=t.size,r=t.treatment;return e.ranges.push(e.inc+=n),e.treatments.push(r),e}),{inc:0,ranges:[],treatments:[]});return new e(n.ranges,n.treatments)},e.prototype.getTreatmentFor=function(e){if(e<0||e>100)throw new RangeError("Please provide a value between 0 and 100");var t=r(this._ranges,(function(t){return e<=t}));return this._treatments[t]},e}();var Ko=/^[0-9]+$/,xo=".";function Po(e,t){if(Ko.test(e)&&Ko.test(t)){var n=e.length-t.length;if(0!==n)return n}return et?1:0}function Uo(e){return e.replace(/^0+(?=\d)/,"")}function Bo(e){throw new Error("Unable to convert to Semver, incorrect format: "+e)}var jo=function(){function e(e){l(e)||Bo(e);var t=e.indexOf("+"),n=-1===t?[e]:[e.slice(0,t),e.slice(t+1)],r=n[0],i=n[1];""===i&&Bo(e),-1===(t=r.indexOf("-"))?(this._isStable=!0,this._preRelease=[]):(this._isStable=!1,this._preRelease=r.slice(t+1).split(xo).map((function(t){return t||Bo(e),Ko.test(t)?Uo(t):t})),r=r.slice(0,t));var s=r.split(xo).map((function(t){return t&&Ko.test(t)||Bo(e),Uo(t)}));3!==s.length&&Bo(e),this._major=s[0],this._minor=s[1],this._patch=s[2],this.version=s.join(xo),this._preRelease.length&&(this.version+="-"+this._preRelease.join(xo)),i&&(this.version+="+"+i)}return e.prototype.compare=function(e){if(this.version===e.version)return 0;var t=Po(this._major,e._major);if(0!==t)return t;if(0!==(t=Po(this._minor,e._minor)))return t;if(0!==(t=Po(this._patch,e._patch)))return t;if(!this._isStable&&e._isStable)return-1;if(this._isStable&&!e._isStable)return 1;for(var n=0,r=Math.min(this._preRelease.length,e._preRelease.length);n=e}},function(e){return function(t){return t<=e}},function(e){return function(t){return t>=e.start&&t<=e.end}},function(e){return function(t){for(var n=t.length===e.length,i=function(i){r(e,(function(e){return e===t[i]}))<0&&(n=!1)},s=0;s=0&&(n=!0)},s=0;s-1}))}},function(e,t,n){var r=e.split,i=e.treatments;function s(e,t,r){var i=!1;return Array.isArray(t)&&(i=-1!==t.indexOf(e.treatment)),n.debug(10,[r,e.treatment,e.label,r,t,i]),i}return function(e,o){var a=e.key,u=e.attributes;n.debug(11,[r,JSON.stringify(a),u?"\n attributes: "+JSON.stringify(u):""]);var c=o(n,a,r,u,t);return Gs(c)?c.then((function(e){return s(e,i,r)})):s(c,i,r)}},function(e){return function(t){return e===t}},function(e){var t=new RegExp(e);return function(e){return t.test(e)}},function(e){var t=new jo(e);return function(e){var n=new jo(e);return t.version===n.version}},function(e){var t=new jo(e);return function(e){return new jo(e).compare(t)>=0}},function(e){var t=new jo(e);return function(e){return new jo(e).compare(t)<=0}},function(e){var t=new jo(e.start),n=new jo(e.end);return function(e){var r=new jo(e);return t.compare(r)<=0&&n.compare(r)>=0}},function(e){if(!e||0===e.length)throw new Error("whitelistMatcherData is required for IN_LIST_SEMVER matcher type");var t=new Set(e.map((function(e){return new jo(e).version})));return function(e){var n=new jo(e).version;return t.has(n)}},function(e,t){return function(n){return!!t.largeSegments&&t.largeSegments.isInSegment(e,n)}},function e(t,n,r){return function(i,s){var o=i.key,a=i.attributes,u=Mr(o);function c(e){var t=e.conditions||[];if(!t.length)return!1;var i=$o(r,t,n)(Kr(o),void 0,void 0,void 0,a,s);return Gs(i)?i.then((function(e){return!!e})):!!i}function l(t){var i=t.type,c=t.name;return i===_e?n.segments.isInSegment(c,u):i===Ae?e(c,n,r)({key:o,attributes:a},s):!("large"!==i||!n.largeSegments)&&n.largeSegments.isInSegment(c,u)}function f(e){if(!e)return!1;var t=function(e){var t=e.excluded||{};return!(!t.keys||-1===t.keys.indexOf(u))||(t.segments||[]).reduce((function(e,t){return Gs(e)?e.then((function(e){return e||l(t)})):e||l(t)}),!1)}(e);return Gs(t)?t.then((function(t){return!t&&c(e)})):!t&&c(e)}var h=n.rbSegments.get(t);return Gs(h)?h.then(f):f(h)}}];function zo(e,t,n){var r,i=t.type,s=t.value;return qo[i]&&(r=qo[i](s,n,e)),r}function Wo(e,t){return{key:e,attributes:t}}function Go(e,t,n,r,i){var s,o,a=function(e,t){switch(e){case No.EQUAL_TO:return"DATETIME"===t?Fo:void 0;case No.GREATER_THAN_OR_EQUAL_TO:case No.LESS_THAN_OR_EQUAL_TO:case No.BETWEEN:return"DATETIME"===t?Lo:void 0;case No.IN_SPLIT_TREATMENT:case No.IN_RULE_BASED_SEGMENT:return Wo;default:return}}(t,r);switch(r){case _o.NUMBER:case _o.DATETIME:o=g(n),s=isNaN(o)?void 0:o;break;case _o.STRING:s=function(e){var t=e;return c(e)&&(t=e.matchingKey?e.matchingKey:void 0),d(t)||void 0}(n);break;case _o.SET:s=function(e){var t=Array.isArray(e)?m(e.map((function(e){return e+""}))):[];return t.length?t:void 0}(n);break;case _o.BOOLEAN:s=function(e){if(!0===e||!1===e)return e;if("string"==typeof e){var t=e.toLocaleLowerCase();if("true"===t)return!0;if("false"===t)return!1}}(n);break;case _o.NOT_SPECIFIED:s=n;break;default:s=void 0}return a&&(s=a(s,i)),e.debug(Ke,[n,r,s instanceof Object?JSON.stringify(s):s]),s}function Ho(e,t,n,r){var i=n.attribute,s=function(e,t,n,r){var i=void 0;return n?r?(i=r[n],e.debug(Me,[n,i])):e.warn(Rt,[n]):i=t,i}(e,t,i,r),o=Go(e,n.type,s,n.dataType,r);return void 0!==o?o:void e.warn(Tt,[s+(i?" for attribute "+i:"")])}function Qo(e,t,n,r){var i=gs(t,n),s=r.getTreatmentFor(i);return e.debug(De,[i,t,n,s]),s}function Vo(e,t,n,r,i,s){if(t)return!i||{treatment:Qo(e,n,r,i),label:s}}function Jo(e,t,n,r,i){return function(s,o,a,u,c,l){if("ROLLOUT"===i&&!function(e,t,n){return!(e<100&&gs(t,n)>e)}(a,s.bucketingKey,u))return{treatment:void 0,label:go};var f=t(s,c,l);return Gs(f)?f.then((function(t){return Vo(e,t,s.bucketingKey,o,n,r)})):Vo(e,f,s.bucketingKey,o,n,r)}}function Yo(e,t){function n(t){var n=t.every((function(e){return e}));return e.debug(Oe,[n]),n}return function(e,i,s){var o=t.map((function(t){return t(e,i,s)}));return-1!==r(o,Gs)?Promise.all(o).then(n):n(o)}}function $o(e,t,n){for(var i=[],s=0;s0?Promise.all(a).then((function(){return o})):o}var oa={treatment:v,label:fo};function aa(e){var t={};return e.forEach((function(e){t[e]=oa})),t}function ua(e){if(e&&e.properties)try{return JSON.stringify(e.properties)}catch(e){}}function ca(e){var t=e.sdkReadinessManager.readinessManager,n=e.storage,r=e.settings,i=e.impressionsTracker,s=e.eventTracker,o=e.telemetryTracker,a=e.fallbackTreatmentsCalculator,u=r.log,l=r.mode,f=Ie(l);function h(e,r,s,a,c,l){void 0===c&&(c=!1),void 0===l&&(l=D);var h=o.trackEval(c?se:re),p=function(t){var n=[],o=d(t,r,e,ua(a),c,l,n);return i.track(n,s),h(n[0]&&n[0].imp.label),o},g=t.isReadyFromCache()?na(u,e,r,s,n):f?Promise.resolve(oa):oa;return Gs(g)?g.then((function(e){return p(e)})):p(g)}function p(e,r,s,a,c,l){void 0===c&&(c=!1),void 0===l&&(l=M);var h=o.trackEval(c?oe:ie),p=function(t){var n=[],r={},o=ua(a);return Object.keys(t).forEach((function(i){r[i]=d(t[i],i,e,o,c,l,n)})),i.track(n,s),h(n[0]&&n[0].imp.label),r},g=t.isReadyFromCache()?ra(u,e,r,s,n):f?Promise.resolve(aa(r)):aa(r);return Gs(g)?g.then((function(e){return p(e)})):p(g)}function g(e,r,s,a,c,l,h){void 0===c&&(c=!1),void 0===l&&(l=ue),void 0===h&&(h=U);var p=o.trackEval(l),g=function(t){var n=[],r={},o=ua(a);return Object.keys(t).forEach((function(i){r[i]=d(t[i],i,e,o,c,h,n)})),i.track(n,s),p(n[0]&&n[0].imp.label),r},m=t.isReadyFromCache()?function(e,t,n,r,i,s){var o;function a(o){for(var a,u=new Set,c=0;c-1?function(e,t,n,r){var i=Qn(e,n,t,"flag sets","flag set"),s=i?Zn(e,i,t):[];return r.length>0&&(s=s.filter((function(n){return r.indexOf(n)>-1||(e.warn(Jt,[t,n]),!1)}))),!!s.length&&s}(i,t,s,e.sync.__splitFiltersValidation.groupedFilters.bySet):p(t,M)?Qn(i,s,t):Hn(i,s,t),f=Hs(i,o,t),h=ro(i,n,t),g=function(e,t,n){if(c(t)){var r=eo(e,t.properties,n).properties;return r&&Object.keys(r).length>0?{properties:r}:void 0}t&&e.error(cn,[n,"evaluation options"])}(i,a,t);return io(i,n,t,l),{valid:h&&u&&l&&!1!==f,key:u,nameOrNames:l,attributes:f,options:g}}function u(e,t){var n=r.resolve(e,""),i=n.treatment,s=n.config;return t?{treatment:i,config:s}:i}function f(e){return s?Promise.resolve(e):e}return{getTreatment:function(e,n,r,i){var s=o(D,e,n,r,i);return s.valid?t.getTreatment(s.key,s.nameOrNames,s.attributes,s.options):f(u(s.nameOrNames,!1))},getTreatmentWithConfig:function(e,n,r,i){var s=o(K,e,n,r,i);return s.valid?t.getTreatmentWithConfig(s.key,s.nameOrNames,s.attributes,s.options):f(u(s.nameOrNames,!0))},getTreatments:function(e,n,r,i){var s=o(M,e,n,r,i);if(s.valid)return t.getTreatments(s.key,s.nameOrNames,s.attributes,s.options);var a={};return s.nameOrNames&&s.nameOrNames.forEach((function(e){return a[e]=u(e,!1)})),f(a)},getTreatmentsWithConfig:function(e,n,r,i){var s=o(x,e,n,r,i);if(s.valid)return t.getTreatmentsWithConfig(s.key,s.nameOrNames,s.attributes,s.options);var a={};return s.nameOrNames&&s.nameOrNames.forEach((function(e){return a[e]=u(e,!0)})),f(a)},getTreatmentsByFlagSets:function(e,n,r,i){var s=o(U,e,n,r,i);return s.valid?t.getTreatmentsByFlagSets(s.key,s.nameOrNames,s.attributes,s.options):f({})},getTreatmentsWithConfigByFlagSets:function(e,n,r,i){var s=o(j,e,n,r,i);return s.valid?t.getTreatmentsWithConfigByFlagSets(s.key,s.nameOrNames,s.attributes,s.options):f({})},getTreatmentsByFlagSet:function(e,n,r,i){var s=o(P,e,[n],r,i);return s.valid?t.getTreatmentsByFlagSet(s.key,s.nameOrNames[0],s.attributes,s.options):f({})},getTreatmentsWithConfigByFlagSet:function(e,n,r,i){var s=o(B,e,[n],r,i);return s.valid?t.getTreatmentsWithConfigByFlagSet(s.key,s.nameOrNames[0],s.attributes,s.options):f({})},track:function(e,r,o,u,c){var f=ir(i,e,q),h=function(e,t,n){if(null==t)e.error(pn,[n,no]);else if(l(t)){if(0!==t.length)return to.test(t)&&(e.warn(Pt,[n]),t=t.toLowerCase()),t;e.error(vn,[n,no])}else e.error(mn,[n,no]);return!1}(i,r,q),p=function(e,t,n){if(null==t)e.error(pn,[n,Js]);else if(l(t))if(0===t.length)e.error(vn,[n,Js]);else{if(Vs.test(t))return t;e.error(un,[n,t])}else e.error(mn,[n,Js]);return!1}(i,o,q),g=function(e,t,n){return a(t)||null==t?t:(e.error(fn,[n]),!1)}(i,u,q),d=eo(i,c,q),m=d.properties,v=d.size;return ro(i,n,q)&&f&&h&&p&&!1!==g&&!1!==m?t.track(f,h,p,g,m,v):!!s&&Promise.resolve(!1)}}}var fa=1e3;function ha(e,t){var n=e.sdkReadinessManager,r=e.syncManager,i=e.storage,s=e.signalListener,o=e.settings,a=e.telemetryTracker,u=e.uniqueKeysTracker,c=0;function l(){return r?r.flush():Promise.resolve()}return Vn(Object.create(n.sdkStatus),la(o,ca(e),n.readinessManager,e.fallbackTreatmentsCalculator),{flush:function(){return function(e,t){var n=Date.now(),r=n-c;return ra?e:a+1}var c=!1;n.splitsCacheLoaded?c=!0:n.once(gi,(function(){if(c=!0,!h&&!g)try{u(),o.emit(yi,h)}catch(e){setTimeout((function(){throw e}),0)}}));var l=!1;function f(){l||h||(l=!0,u(),o.emit(mi,"Split SDK emitted SDK_READY_TIMED_OUT event."))}var h=!1;n.on(pi,m),s.on(di,m);var p,g=!1;function d(){g=!1,i>0&&!h&&(p=setTimeout(f,i))}function m(e){if(!g)if(h)try{u(),o.emit(Si,e)}catch(e){setTimeout((function(){throw e}),0)}else if(n.splitsArrived&&s.segmentsArrived){clearTimeout(p),h=!0;try{u(),c||(c=!0,o.emit(yi,h)),o.emit(vi)}catch(e){setTimeout((function(){throw e}),0)}}}return n.initCallbacks.push(d),n.hasInit&&d(),{splits:n,segments:s,gate:o,shared:function(){return xa(e,t,n,!0)},timeout:f,setDestroyed:function(){g=!0},init:function(){n.hasInit||(n.hasInit=!0,n.initCallbacks.forEach((function(e){return e()})))},destroy:function(){g=!0,u(),clearTimeout(p),r||(n.hasInit=!1)},isReady:function(){return h},isReadyFromCache:function(){return c},isTimedout:function(){return l&&!h},hasTimedout:function(){return l},isDestroyed:function(){return g},isOperational:function(){return(h||c)&&!g},lastUpdate:function(){return a}}}var Pa="newListener",Ua="removeListener",Ba=new Error(mi);function ja(e,t,n){void 0===n&&(n=xa(e,t));var r=t.log,i=0,s=0;n.gate.on(Ua,(function(e){e===vi&&s--})),n.gate.on(Pa,(function(e){e===vi||e===mi?n.isReady()?r.error(Xt,[e===vi?"SDK_READY":"SDK_READY_TIMED_OUT"]):e===vi&&s++:e===yi&&n.isReadyFromCache()&&r.error(Xt,["SDK_READY_FROM_CACHE"])}));var o,a=o=Ka(new Promise((function(e,t){n.gate.once(vi,(function(){r.info(Ze),s!==i||o.hasOnFulfilled()||r.warn(kt),e()})),n.gate.once(mi,(function(e){t(new Error(e))}))})),u);function u(e){r.error(e&&e.message)}function c(){return{isReady:n.isReady(),isReadyFromCache:n.isReadyFromCache(),isTimedout:n.isTimedout(),hasTimedout:n.hasTimedout(),isDestroyed:n.isDestroyed(),isOperational:n.isOperational(),lastUpdate:n.lastUpdate()}}return n.gate.once(yi,(function(){r.info($e)})),{readinessManager:n,shared:function(){return ja(e,t,n.shared())},incInternalReadyCbCount:function(){i++},sdkStatus:Vn(Object.create(n.gate),{Event:{SDK_READY:vi,SDK_READY_FROM_CACHE:yi,SDK_UPDATE:Si,SDK_READY_TIMED_OUT:mi},ready:function(){return n.hasTimedout()?n.isReady()?Promise.resolve():Ka(Promise.reject(new Error("Split SDK has emitted SDK_READY_TIMED_OUT event.")),u):a},whenReady:function(){return new Promise((function(e,t){n.isReady()?e():n.hasTimedout()?t(Ba):(n.gate.once(vi,e),n.gate.once(mi,(function(){return t(Ba)})))}))},whenReadyFromCache:function(){return new Promise((function(e,t){n.isReadyFromCache()?e(n.isReady()):n.hasTimedout()?t(Ba):(n.gate.once(yi,(function(){return e(n.isReady())})),n.gate.once(mi,(function(){return t(Ba)})))}))},getStatus:c,__getStatus:c})}}function qa(e){function t(t){vr(t)?e.setLogLevel(t):e.error(Zt)}return{enable:function(){t(gr.DEBUG)},setLogLevel:t,setLogger:function(t){e.setLogger(t)},disable:function(){t(gr.NONE)},LogLevel:gr}}var za={add:function(){return!0},contains:function(){return!0},clear:function(){}};function Wa(e){var t=e.settings,n=e.platform,r=e.storageFactory,i=e.splitApiFactory,s=e.extraProps,o=e.syncManagerFactory,a=e.SignalListener,u=e.impressionsObserverFactory,c=e.integrationsManagerFactory,l=e.sdkManagerFactory,f=e.sdkClientMethodFactory,h=e.filterAdapterFactory,p=e.lazyInit,g=t.log,d=t.sync.impressionsMode,m=t.initialRolloutPlan,v=t.core.key,y=!1,S=[];function T(e){y?e():S.push(e)}var R=ja(n.EventEmitter,t),k=R.readinessManager,C=r({settings:t,onReadyCb:function(e){e?k.timeout():(k.splits.emit(pi),k.segments.emit(di))},onReadyFromCacheCb:function(){k.splits.emit(gi)}}),w=new ao(t.fallbackTreatments);m&&(sr(g,m,C,v&&Mr(v)),C.splits.getChangeNumber()>-1&&k.splits.emit(gi));var N,_,A={},I=function(e,t){if(e&&t){var n=wi(t);return{trackEval:function(n){var r=wi(t);return function(t){switch(t){case ho:return void e.recordException(n);case fo:e.recordNonReadyUsage&&e.recordNonReadyUsage()}e.recordLatency(n,r())}},trackHttp:function(n){var r=wi(t);return function(t){e.recordHttpLatency(n,r()),t&&t.statusCode?e.recordHttpError(n,t.statusCode):e.recordSuccessfulSync(n,Date.now())}},sessionLength:function(){e.recordSessionLength&&e.recordSessionLength(n())},streamingEvent:function(t,n){t===ye?e.recordAuthRejections():(e.recordStreamingEvents({e:t,d:n,t:Date.now()}),t===de&&e.recordTokenRefreshes())},addTag:function(t){e.addTag&&e.addTag(t)},trackUpdatesFromSSE:function(t){e.recordUpdatesFromSSE(t)}}}var r=function(){return function(){}};return{trackEval:r,trackHttp:r,sessionLength:function(){},streamingEvent:function(){},addTag:function(){},trackUpdatesFromSSE:function(){}}}(C.telemetry,n.now),O=c&&c({settings:t,storage:C,telemetryTracker:I}),L=u(),D=function(e,t,n){var r;return void 0===n&&(n=za),{track:function(r,i){n.add(r,i)?t.track(r,i):e.debug(zn+"The feature "+i+" and key "+r+" exist in the filter")},start:function(){n.refreshRate&&(r=setInterval(n.clear,n.refreshRate))},stop:function(){clearInterval(r)}}}(g,C.uniqueKeys,h&&h()),M=function(e,t){return{process:function(n){var r=Date.now();return e.track(n.feature,r,1),t.track(n.keyName,n.feature),!1}}}(C.impressionCounts,D),K=d===E?(N=L,_=C.impressionCounts,{process:function(e){if(e.properties)return!0;e.pt=N.testAndSet(e);var t=Date.now();return e.pt&&_.track(e.feature,t,1),!e.pt||e.pt0&&(s=t[0]),s instanceof Error)throw s;var o=new Error("Unhandled error."+(s?" ("+s.message+")":""));throw o.context=s,o}var a=i[e];if(void 0===a)return!1;if("function"==typeof a)Qa(a,this,t);else for(var u=a.length,c=function(e,t){for(var n=new Array(t),r=0;r=0;s--)if(n[s]===t||n[s].listener===t){o=n[s].listener,i=s;break}if(i<0)return this;0===i?n.shift():function(e,t){for(;t+1=0;r--)this.removeListener(e,t[r]);return this};var $a={getFetch:function(){return"function"==typeof fetch?fetch:Ga},getEventSource:function(){return"function"==typeof EventSource?EventSource:void 0},EventEmitter:Va,now:"object"==typeof performance&&"function"==typeof performance.now?performance.now.bind(performance):Date.now};function Za(e,t){var n=function(e){return hr(e,Jr)}(e),r=function(e,t){La||(La=Ji(Ws,Ks));var n={settings:e,platform:t,storageFactory:e.storage,splitApiFactory:ri,syncManagerFactory:La,sdkManagerFactory:ko,sdkClientMethodFactory:ga,SignalListener:ya,integrationsManagerFactory:e.integrations&&e.integrations.length>0?wa.bind(null,e.integrations):void 0,impressionsObserverFactory:Ca,extraProps:function(e){return{UserConsent:_a(e)}}};switch(e.mode){case R:n.splitApiFactory=void 0,n.syncManagerFactory=Ma,n.SignalListener=void 0;break;case w:n.syncManagerFactory=void 0;break;case N:Da||(Da=Ji(void 0,void 0)),n.syncManagerFactory=Da}return n}(n,$a);return t&&t(r),Wa(r)}var Xa=/[^.]+$/;var eu=function(e){function n(t,n){var r=e.call(this,t)||this;return r.matchingKey=n,r.regexSplitsCacheKey=new RegExp("^"+t+"\\.(splits?|trafficType|flagSet)\\."),r}return t(n,e),n.prototype.buildSegmentNameKey=function(e){return this.prefix+"."+this.matchingKey+".segment."+e},n.prototype.extractSegmentName=function(e){var t=this.prefix+"."+this.matchingKey+".segment.";if(p(e,t))return e.slice(t.length)},n.prototype.buildLastUpdatedKey=function(){return this.prefix+".splits.lastUpdated"},n.prototype.isSplitsCacheKey=function(e){return this.regexSplitsCacheKey.test(e)},n.prototype.buildTillKey=function(){return this.prefix+"."+this.matchingKey+".segments.till"},n.prototype.isSplitKey=function(e){return p(e,this.prefix+".split.")},n.prototype.isRBSegmentKey=function(e){return p(e,this.prefix+".rbsegment.")},n.prototype.buildSplitsWithSegmentCountKey=function(){return this.prefix+".splits.usingSegments"},n.prototype.buildLastClear=function(){return this.prefix+".lastClear"},n}(function(){function e(e){void 0===e&&(e="SPLITIO"),this.prefix=e}return e.prototype.buildTrafficTypeKey=function(e){return this.prefix+".trafficType."+e},e.prototype.buildFlagSetKey=function(e){return this.prefix+".flagSet."+e},e.prototype.buildSplitKey=function(e){return this.prefix+".split."+e},e.prototype.buildSplitsTillKey=function(){return this.prefix+".splits.till"},e.prototype.buildSplitKeyPrefix=function(){return this.prefix+".split."},e.prototype.buildRBSegmentKey=function(e){return this.prefix+".rbsegment."+e},e.prototype.buildRBSegmentsTillKey=function(){return this.prefix+".rbsegments.till"},e.prototype.buildRBSegmentKeyPrefix=function(){return this.prefix+".rbsegment."},e.prototype.buildSegmentNameKey=function(e){return this.prefix+".segment."+e},e.prototype.buildSegmentTillKey=function(e){return this.prefix+".segment."+e+".till"},e.prototype.extractKey=function(e){var t=e.match(Xa);if(t&&t.length)return t[0];throw new Error("Invalid latency key provided")},e.prototype.buildHashKey=function(){return this.prefix+".hash"},e}());function tu(e,t){return{buildSegmentNameKey:function(n){return e+"."+t+".largeSegment."+n},extractSegmentName:function(n){var r=e+"."+t+".largeSegment.";if(p(n,r))return n.slice(r.length)},buildTillKey:function(){return e+"."+t+".largeSegments.till"}}}var nu="storage:localstorage: ",ru="1",iu=function(e){function n(t,n,r){var i=e.call(this)||this;return i.keys=n,i.log=t.log,i.flagSetsFilter=t.sync.__splitFiltersValidation.groupedFilters.bySet,i.storage=r,i}return t(n,e),n.prototype._decrementCount=function(e){var t=g(this.storage.getItem(e))-1;t>0?this.storage.setItem(e,t+""):this.storage.removeItem(e)},n.prototype._decrementCounts=function(e){try{var t=this.keys.buildTrafficTypeKey(e.trafficTypeName);if(this._decrementCount(t),Cr(e)){var n=this.keys.buildSplitsWithSegmentCountKey();this._decrementCount(n)}}catch(e){this.log.error(nu+e)}},n.prototype._incrementCounts=function(e){try{var t=this.keys.buildTrafficTypeKey(e.trafficTypeName);if(this.storage.setItem(t,g(this.storage.getItem(t))+1+""),Cr(e)){var n=this.keys.buildSplitsWithSegmentCountKey();this.storage.setItem(n,g(this.storage.getItem(n))+1+"")}}catch(e){this.log.error(nu+e)}},n.prototype.clear=function(){for(var e=this,t=this.storage.length,n=[],r=0;r0},n.prototype.usesSegments=function(){if(!this.hasSync)return!0;var e=this.storage.getItem(this.keys.buildSplitsWithSegmentCountKey()),t=null===e?0:g(e);return!a(t)||t>0},n.prototype.getNamesByFlagSets=function(e){var t=this;return e.map((function(e){var n=t.keys.buildFlagSetKey(e),r=t.storage.getItem(n);return new Set(r?JSON.parse(r):[])}))},n.prototype.addToFlagSets=function(e){var t=this;e.sets&&e.sets.forEach((function(n){if(!(t.flagSetsFilter.length>0)||t.flagSetsFilter.some((function(e){return e===n}))){var r=t.keys.buildFlagSetKey(n),i=t.storage.getItem(r),s=new Set(i?JSON.parse(i):[]);s.add(e.name),t.storage.setItem(r,JSON.stringify(qr(s)))}}))},n.prototype.removeFromFlagSets=function(e,t){var n=this;t&&t.forEach((function(t){n.removeNames(t,e)}))},n.prototype.removeNames=function(e,t){var n=this.keys.buildFlagSetKey(e),r=this.storage.getItem(n);if(r){var i=new Set(JSON.parse(r));i.delete(t),0!==i.size?this.storage.setItem(n,JSON.stringify(qr(i))):this.storage.removeItem(n)}},n}(kr),su=function(){function e(e,t,n){this.keys=t,this.log=e.log,this.storage=n}return e.prototype.clear=function(){var e=this;this.getNames().forEach((function(t){return e.remove(t)})),this.storage.removeItem(this.keys.buildRBSegmentsTillKey())},e.prototype.update=function(e,t,n){var r=this;this.setChangeNumber(n);var i=e.map((function(e){return r.add(e)})).some((function(e){return e}));return t.map((function(e){return r.remove(e.name)})).some((function(e){return e}))||i},e.prototype.setChangeNumber=function(e){try{this.storage.setItem(this.keys.buildRBSegmentsTillKey(),e+""),this.storage.setItem(this.keys.buildLastUpdatedKey(),Date.now()+"")}catch(e){this.log.error(nu+e)}},e.prototype.updateSegmentCount=function(e){var t=this.keys.buildSplitsWithSegmentCountKey(),n=g(this.storage.getItem(t))+e;n>0?this.storage.setItem(t,n+""):this.storage.removeItem(t)},e.prototype.add=function(e){try{var t=e.name,n=this.keys.buildRBSegmentKey(t),r=this.storage.getItem(n),i=r?JSON.parse(r):null;this.storage.setItem(n,JSON.stringify(e));var s=0;return i&&Cr(i)&&s--,Cr(e)&&s++,0!==s&&this.updateSegmentCount(s),!0}catch(e){return this.log.error(nu+e),!1}},e.prototype.remove=function(e){try{var t=this.get(e);return!!t&&(this.storage.removeItem(this.keys.buildRBSegmentKey(e)),Cr(t)&&this.updateSegmentCount(-1),!0)}catch(e){return this.log.error(nu+e),!1}},e.prototype.getNames=function(){for(var e=this.storage.length,t=[],n=0;n0},e}(),ou=function(e){function n(t,n,r){var i=e.call(this)||this;return i.log=t,i.keys=n,i.storage=r,i}return t(n,e),n.prototype.addSegment=function(e){var t=this.keys.buildSegmentNameKey(e);try{return this.storage.getItem(t)!==ru&&(this.storage.setItem(t,ru),!0)}catch(e){return this.log.error(nu+e),!1}},n.prototype.removeSegment=function(e){var t=this.keys.buildSegmentNameKey(e);try{return this.storage.getItem(t)===ru&&(this.storage.removeItem(t),!0)}catch(e){return this.log.error(nu+e),!1}},n.prototype.isInSegment=function(e){return this.storage.getItem(this.keys.buildSegmentNameKey(e))===ru},n.prototype.getRegisteredSegments=function(){for(var e=[],t=0,n=this.storage.length;t=1?e.expirationDays:au;if(l %s"],[Je,On+"[%s] Result: %s. Rule value: %s. Evaluation value: %s"],[Ye,An+"Evaluates to default treatment. %s"],[25,Wn+"Registering cleanup handler %s"],[26,Wn+"Deregistering cleanup handler %s"],[xe,"Retrieving default SDK client."],[Pe,"Retrieving existing SDK client."],[Ue,"Retrieving manager instance."],[Be,Mn+"Feature flags data: \n%s"],[je,xn+"Spin up feature flags update using since = %s and rbSince = %s."],[qe,xn+"New feature flags: %s. Removed feature flags: %s."],[ze,xn+"New rule-based segments: %s. Removed rule-based segments: %s."],[We,Kn+"New SSE message received, with data: %s."],[Ge,Ln+": Starting %s. Running each %s millis"],[He,Ln+": Running %s"],[Qe,Ln+": Stopping %s"],[Ve,wn+': feature flags filtering criteria is "%s".']]);return Za.SplitFactory=Za,Za.InLocalStorage=function(e){void 0===e&&(e={});var t=function(e){return e?e+".SPLITIO":"SPLITIO"}(e.prefix);function n(n){var r=n.settings,i=n.settings,s=i.log,o=i.scheduler,a=o.impressionsQueueSize,u=o.eventsQueueSize,c=lu(s,t,e.wrapper);if(!c)return Gr(n);var l,f=Mr(r.core.key),h=new eu(t,f),p=new iu(r,h,c),g=new su(r,h,c),d=new ou(s,h,c),m=new ou(s,tu(t,f),c);return{splits:p,rbSegments:g,segments:d,largeSegments:m,impressions:new Ir(a),impressionCounts:new Dr,events:new Or(u),telemetry:Br(n)?new jr(p,d):void 0,uniqueKeys:new zr,validateCache:function(){return l||(l=function(e,t,n,r,i,s,o,a){return Promise.resolve(t.load&&t.load()).then((function(){var u=Date.now(),c=i.getChangeNumber()>-1;if(cu(e,t,n,r,u,c)){i.clear(),s.clear(),o.clear(),a.clear();try{t.setItem(r.buildLastClear(),u+"")}catch(e){n.log.error(nu+e)}return t.save&&t.save(),!1}return c}))}(e,c,r,h,p,g,d,m))},save:function(){return c.save&&c.save()},destroy:function(){return c.whenSaved&&c.whenSaved()},shared:function(e){return{splits:this.splits,rbSegments:this.rbSegments,segments:new ou(s,new eu(t,e),c),largeSegments:new ou(s,tu(t,e),c),impressions:this.impressions,impressionCounts:this.impressionCounts,events:this.events,telemetry:this.telemetry,uniqueKeys:this.uniqueKeys,destroy:function(){}}}}}return n.type=A,n},Za.ErrorLogger=function(){return new Er({logLevel:"ERROR"},new Map(fu))},Za.WarnLogger=function(){return new Er({logLevel:"WARN"},new Map(hu))},Za.InfoLogger=function(){return new Er({logLevel:"INFO"},new Map(gu))},Za.DebugLogger=function(){return new Er({logLevel:"DEBUG"},new Map(du))},Za})); From e4ab014142eec621812ccfc7f2b084085e10ba4e Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 17 Dec 2025 16:07:49 -0300 Subject: [PATCH 11/55] Add web platform to example --- splitio/example/pubspec.lock | 71 ++++++++++++------ splitio/example/web/favicon.png | Bin 0 -> 917 bytes splitio/example/web/icons/Icon-192.png | Bin 0 -> 5292 bytes splitio/example/web/icons/Icon-512.png | Bin 0 -> 8252 bytes .../example/web/icons/Icon-maskable-192.png | Bin 0 -> 5594 bytes .../example/web/icons/Icon-maskable-512.png | Bin 0 -> 20998 bytes splitio/example/web/index.html | 39 ++++++++++ splitio/example/web/manifest.json | 35 +++++++++ 8 files changed, 121 insertions(+), 24 deletions(-) create mode 100644 splitio/example/web/favicon.png create mode 100644 splitio/example/web/icons/Icon-192.png create mode 100644 splitio/example/web/icons/Icon-512.png create mode 100644 splitio/example/web/icons/Icon-maskable-192.png create mode 100644 splitio/example/web/icons/Icon-maskable-512.png create mode 100644 splitio/example/web/index.html create mode 100644 splitio/example/web/manifest.json diff --git a/splitio/example/pubspec.lock b/splitio/example/pubspec.lock index 624c7f2..31b9465 100644 --- a/splitio/example/pubspec.lock +++ b/splitio/example/pubspec.lock @@ -75,30 +75,35 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" leak_tracker: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.dev" source: hosted - version: "10.0.9" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: @@ -127,10 +132,10 @@ packages: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" path: dependency: transitive description: @@ -166,28 +171,38 @@ packages: path: ".." relative: true source: path - version: "1.0.0-rc.1" + version: "1.0.0" splitio_android: dependency: transitive description: - path: "../../splitio_android" - relative: true - source: path - version: "1.0.0-rc.1" + name: splitio_android + sha256: "344bf82de6694cffb8dd80a96ee734e31bb838d2b4693fb046e0fc98e31512ca" + url: "https://pub.dev" + source: hosted + version: "1.0.0" splitio_ios: dependency: transitive description: - path: "../../splitio_ios" - relative: true - source: path - version: "1.0.0-rc.1" + name: splitio_ios + sha256: "1c078bc49bf7b30df6ca50accb6a9eecf592ec607e20e77b1c6ecdabc7a44dc9" + url: "https://pub.dev" + source: hosted + version: "1.0.0" splitio_platform_interface: dependency: transitive description: - path: "../../splitio_platform_interface" + name: splitio_platform_interface + sha256: "8bcb1cab9f5fffb7b79cfeeaf6c80d82f8ede55c8d6ca7578ec78653f3f9e499" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + splitio_web: + dependency: transitive + description: + path: "../../splitio_web" relative: true source: path - version: "2.0.0-rc.1" + version: "1.0.0" stack_trace: dependency: transitive description: @@ -224,18 +239,18 @@ packages: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.7" vector_math: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: @@ -244,6 +259,14 @@ packages: url: "https://pub.dev" source: hosted version: "15.0.0" + web: + dependency: transitive + description: + name: web + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + url: "https://pub.dev" + source: hosted + version: "0.5.1" sdks: - dart: ">=3.7.0-0 <4.0.0" + dart: ">=3.8.0-0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/splitio/example/web/favicon.png b/splitio/example/web/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..8aaa46ac1ae21512746f852a42ba87e4165dfdd1 GIT binary patch literal 917 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|I14-?iy0X7 zltGxWVyS%@P(fs7NJL45ua8x7ey(0(N`6wRUPW#JP&EUCO@$SZnVVXYs8ErclUHn2 zVXFjIVFhG^g!Ppaz)DK8ZIvQ?0~DO|i&7O#^-S~(l1AfjnEK zjFOT9D}DX)@^Za$W4-*MbbUihOG|wNBYh(yU7!lx;>x^|#0uTKVr7USFmqf|i<65o z3raHc^AtelCMM;Vme?vOfh>Xph&xL%(-1c06+^uR^q@XSM&D4+Kp$>4P^%3{)XKjo zGZknv$b36P8?Z_gF{nK@`XI}Z90TzwSQO}0J1!f2c(B=V`5aP@1P1a|PZ!4!3&Gl8 zTYqUsf!gYFyJnXpu0!n&N*SYAX-%d(5gVjrHJWqXQshj@!Zm{!01WsQrH~9=kTxW#6SvuapgMqt>$=j#%eyGrQzr zP{L-3gsMA^$I1&gsBAEL+vxi1*Igl=8#8`5?A-T5=z-sk46WA1IUT)AIZHx1rdUrf zVJrJn<74DDw`j)Ki#gt}mIT-Q`XRa2-jQXQoI%w`nb|XblvzK${ZzlV)m-XcwC(od z71_OEC5Bt9GEXosOXaPTYOia#R4ID2TiU~`zVMl08TV_C%DnU4^+HE>9(CE4D6?Fz oujB08i7adh9xk7*FX66dWH6F5TM;?E2b5PlUHx3vIVCg!0Dx9vYXATM literal 0 HcmV?d00001 diff --git a/splitio/example/web/icons/Icon-192.png b/splitio/example/web/icons/Icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..b749bfef07473333cf1dd31e9eed89862a5d52aa GIT binary patch literal 5292 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 literal 0 HcmV?d00001 diff --git a/splitio/example/web/icons/Icon-512.png b/splitio/example/web/icons/Icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..88cfd48dff1169879ba46840804b412fe02fefd6 GIT binary patch literal 8252 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 literal 0 HcmV?d00001 diff --git a/splitio/example/web/icons/Icon-maskable-192.png b/splitio/example/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000000000000000000000000000000000000..eb9b4d76e525556d5d89141648c724331630325d GIT binary patch literal 5594 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 X>c!)3PgKfL&lX^$Z>Cpu&6)6jvi^Z! literal 0 HcmV?d00001 diff --git a/splitio/example/web/icons/Icon-maskable-512.png b/splitio/example/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000000000000000000000000000000000000..d69c56691fbdb0b7efa65097c7cc1edac12a6d3e GIT binary patch literal 20998 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 MPgg&ebxsLQ025IeI{*Lx literal 0 HcmV?d00001 diff --git a/splitio/example/web/index.html b/splitio/example/web/index.html new file mode 100644 index 0000000..6d72a53 --- /dev/null +++ b/splitio/example/web/index.html @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + splitio_example + + + + + + + diff --git a/splitio/example/web/manifest.json b/splitio/example/web/manifest.json new file mode 100644 index 0000000..142b95a --- /dev/null +++ b/splitio/example/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "splitio_example", + "short_name": "splitio_example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "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" + } + ] +} From ab0826d5bce473f717dd76b6a2843b8f53e73148 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 17 Dec 2025 16:17:47 -0300 Subject: [PATCH 12/55] Use local path for splitio_web dependency --- splitio/pubspec.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/splitio/pubspec.yaml b/splitio/pubspec.yaml index 0e0dc54..c7d1672 100644 --- a/splitio/pubspec.yaml +++ b/splitio/pubspec.yaml @@ -23,7 +23,8 @@ dependencies: sdk: flutter splitio_android: ^1.0.0 splitio_ios: ^1.0.0 - splitio_web: ^1.0.0 + splitio_web: # ^1.0.0 + path: ../splitio_web # @TODO remove when splitio_web is published splitio_platform_interface: ^2.0.0 dev_dependencies: flutter_test: From 505040d8eecf27770feb4f7d1b8fbbc92ae1d228 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 18 Dec 2025 11:37:50 -0300 Subject: [PATCH 13/55] Add SDK initialization and create factory instance with JS interop --- splitio_web/lib/splitio_web.dart | 90 +++++++++++++++++++++++++++++ splitio_web/lib/src/js_interop.dart | 9 +++ 2 files changed, 99 insertions(+) create mode 100644 splitio_web/lib/src/js_interop.dart diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index 4fd62fb..cc9bdfb 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -1,5 +1,15 @@ +import 'dart:async'; +import 'dart:js_interop'; +import 'dart:js_interop_unsafe'; import 'package:flutter_web_plugins/flutter_web_plugins.dart' show Registrar; import 'package:splitio_platform_interface/splitio_platform_interface.dart'; +import 'package:splitio_web/src/js_interop.dart'; +import 'package:web/web.dart'; + +extension on Window { + @JS() + external JS_BrowserSDKPackage? splitio; +} /// Web implementation of [SplitioPlatform]. class SplitioWeb extends SplitioPlatform { @@ -8,4 +18,84 @@ class SplitioWeb extends SplitioPlatform { SplitioPlatform.instance = SplitioWeb(); } + // Future to queue method calls until SDK is initialized + Future? _initFuture; + + late JS_IBrowserSDK _factory; + + @override + Future init({ + required String apiKey, + required String matchingKey, + required String? bucketingKey, + SplitConfiguration? sdkConfiguration, + }) async { + if (_initFuture == null) { + _initFuture = this._init(apiKey: apiKey, matchingKey: matchingKey, bucketingKey: bucketingKey, sdkConfiguration: sdkConfiguration); + } + return _initFuture; + } + + Future _init({ + required String apiKey, + required String matchingKey, + required String? bucketingKey, + SplitConfiguration? sdkConfiguration, + }) async { + await _loadSplitSdk(); + + final config = _buildConfig(apiKey, matchingKey, bucketingKey, sdkConfiguration); + + // Create factory instance + this._factory = window.splitio!.SplitFactory.callAsFunction(null, config) as JS_IBrowserSDK; + + return; + } + + // Checks whether the Split Browser SDK was manually loaded (`window.splitio != null`). + // If not, loads it by injecting a script tag. + static Future _loadSplitSdk() async { + if (window.splitio != null) { + return; // Already loaded + } + + // Create and inject script tag + final script = document.createElement('script') as HTMLScriptElement; + script.type = 'text/javascript'; + script.src = 'packages/splitio_web/web/split-browser-1.6.0.full.min.js'; + + // Wait for script to load + final completer = Completer(); + + script.onload = (Event event) { + completer.complete(); + }.toJS; + + script.onerror = (Event event) { + completer.completeError(Exception('Failed to load Split SDK')); + }.toJS; + + document.head!.appendChild(script); + + await completer.future; + + if (window.splitio == null) { + throw Exception('Split Browser SDK failed to initialize after loading'); + } + } + + // Map SplitConfiguration to JS equivalent object + JSObject _buildConfig(String apiKey, String matchingKey, String? bucketingKey, SplitConfiguration? configuration) { + final core = JSObject(); + core.setProperty('authorizationKey'.toJS, apiKey.toJS); + // @TODO: set bucketingKey if provided + core.setProperty('key'.toJS, matchingKey.toJS); + + final config = JSObject(); + config.setProperty('core'.toJS, core); + + // @TODO: complete config + return config; + } + } diff --git a/splitio_web/lib/src/js_interop.dart b/splitio_web/lib/src/js_interop.dart new file mode 100644 index 0000000..28192c0 --- /dev/null +++ b/splitio_web/lib/src/js_interop.dart @@ -0,0 +1,9 @@ +import 'dart:js_interop'; + +@JS() +extension type JS_IBrowserSDK._(JSObject _) implements JSObject {} + +@JS() +extension type JS_BrowserSDKPackage._(JSObject _) implements JSObject { + external JSFunction SplitFactory; +} From ead003ba3abb146a776bf57ed0e8f76e4283e648 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Mon, 22 Dec 2025 12:47:04 -0300 Subject: [PATCH 14/55] Complete the mapping of SplitConfiguration (Flutter SDK config) to JS SDK configuration object --- .github/workflows/test.yml | 2 + .../lib/split_configuration.dart | 8 +- splitio_web/lib/splitio_web.dart | 218 +++++++++++++++- splitio_web/lib/src/js_interop.dart | 19 +- splitio_web/test/splitio_web_test.dart | 240 ++++++++++++++++++ .../test/utils/js_interop_test_utils.dart | 34 +++ 6 files changed, 510 insertions(+), 11 deletions(-) create mode 100644 splitio_web/test/splitio_web_test.dart create mode 100644 splitio_web/test/utils/js_interop_test_utils.dart diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index eb7e486..3a4a238 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,3 +25,5 @@ jobs: run: cd splitio_android/; flutter test - name: Run flutter splitio_ios test run: cd splitio_ios/; flutter test + - name: Run flutter splitio_web test + run: cd splitio_web/; flutter test --platform=chrome diff --git a/splitio_platform_interface/lib/split_configuration.dart b/splitio_platform_interface/lib/split_configuration.dart index ffe9a9d..1d54fdd 100644 --- a/splitio_platform_interface/lib/split_configuration.dart +++ b/splitio_platform_interface/lib/split_configuration.dart @@ -21,7 +21,7 @@ class SplitConfiguration { /// /// [eventFlushInterval] When using .track, how often the events queue is flushed to Split servers. /// - /// [eventsPerPush] Maximum size of the batch to push events. + /// [eventsPerPush] Maximum size of the batch to push events. Not supported in Web. /// /// [trafficType] The default traffic type for events tracked using the track method. If not specified, every track call should specify a traffic type. /// @@ -29,7 +29,7 @@ class SplitConfiguration { /// /// [streamingEnabled] Boolean flag to enable the streaming service as default synchronization mechanism when in foreground. In the event of an issue with streaming, the SDK will fallback to the polling mechanism. If false, the SDK will poll for changes as usual without attempting to use streaming. /// - /// [persistentAttributesEnabled] Enables saving attributes on persistent cache which is loaded as part of the SDK_READY_FROM_CACHE flow. All functions that mutate the stored attributes map affect the persistent cache. + /// [persistentAttributesEnabled] Enables saving attributes on persistent cache which is loaded as part of the SDK_READY_FROM_CACHE flow. All functions that mutate the stored attributes map affect the persistent cache. Not supported in Web. /// /// [impressionListener] Enables impression listener. If true, generated impressions will be streamed in the impressionsStream() method of Splitio. /// @@ -41,13 +41,13 @@ class SplitConfiguration { /// /// [userConsent] User consent status used to control the tracking of events and impressions. Possible values are [UserConsent.granted], [UserConsent.declined], and [UserConsent.unknown]. /// - /// [encryptionEnabled] If set to true, the local database contents is encrypted. Defaults to false. + /// [encryptionEnabled] If set to true, the local database contents is encrypted. Defaults to false. Not supported in Web. /// /// [logLevel] Enables logging according to the level specified. Options are [SplitLogLevel.verbose], [SplitLogLevel.none], [SplitLogLevel.debug], [SplitLogLevel.info], [SplitLogLevel.warning], and [SplitLogLevel.error]. /// /// [readyTimeout] Maximum amount of time in seconds to wait before firing the SDK_READY_TIMED_OUT event. Defaults to 10 seconds. /// - /// [certificatePinningConfiguration] Certificate pinning configuration. Pins need to have the format of a base64 SHA-256 or base64 SHA-1 hashes of the SPKI (ex.: "sha256/7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y="). + /// [certificatePinningConfiguration] Certificate pinning configuration. Pins need to have the format of a base64 SHA-256 or base64 SHA-1 hashes of the SPKI (ex.: "sha256/7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y="). Not supported in Web. SplitConfiguration({ int? featuresRefreshRate, int? segmentsRefreshRate, diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index cc9bdfb..d175b7d 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -22,6 +22,8 @@ class SplitioWeb extends SplitioPlatform { Future? _initFuture; late JS_IBrowserSDK _factory; + String? _trafficType; + bool _impressionListener = false; @override Future init({ @@ -49,6 +51,33 @@ class SplitioWeb extends SplitioPlatform { // Create factory instance this._factory = window.splitio!.SplitFactory.callAsFunction(null, config) as JS_IBrowserSDK; + if (sdkConfiguration != null) { + if (sdkConfiguration.configurationMap['trafficType'] is String) { + this._trafficType = sdkConfiguration.configurationMap['trafficType']; + } + + if (sdkConfiguration.configurationMap['impressionListener'] is bool) { + this._impressionListener = + sdkConfiguration.configurationMap['impressionListener']; + } + + // Log warnings regarding unsupported configs. Not done in _buildConfig to reuse the factory logger + final unsupportedConfigs = [ + 'encryptionEnabled', + 'certificatePinningConfiguration', + 'persistentAttributesEnabled', + 'eventsPerPush' + ]; + for (final configName in unsupportedConfigs) { + if (sdkConfiguration.configurationMap[configName] != null) { + this._factory.settings.log.warn.callAsFunction( + this._factory.settings.log, + 'Config $configName is not supported by the Web package. This config will be ignored.' + .toJS); + } + } + } + return; } @@ -85,17 +114,194 @@ class SplitioWeb extends SplitioPlatform { } // Map SplitConfiguration to JS equivalent object - JSObject _buildConfig(String apiKey, String matchingKey, String? bucketingKey, SplitConfiguration? configuration) { + static JSObject _buildConfig(String apiKey, String matchingKey, String? bucketingKey, SplitConfiguration? configuration) { + final config = JSObject(); + final core = JSObject(); core.setProperty('authorizationKey'.toJS, apiKey.toJS); - // @TODO: set bucketingKey if provided - core.setProperty('key'.toJS, matchingKey.toJS); - - final config = JSObject(); + core.setProperty('key'.toJS, _buildKey(matchingKey, bucketingKey)); config.setProperty('core'.toJS, core); - // @TODO: complete config + if (configuration != null) { + final scheduler = JSObject(); + if (configuration.configurationMap.containsKey('featuresRefreshRate')) + scheduler.setProperty( + 'featuresRefreshRate'.toJS, + (configuration.configurationMap['featuresRefreshRate'] as int) + .toJS); + if (configuration.configurationMap.containsKey('segmentsRefreshRate')) + scheduler.setProperty( + 'segmentsRefreshRate'.toJS, + (configuration.configurationMap['segmentsRefreshRate'] as int) + .toJS); + if (configuration.configurationMap.containsKey('impressionsRefreshRate')) + scheduler.setProperty( + 'impressionsRefreshRate'.toJS, + (configuration.configurationMap['impressionsRefreshRate'] as int) + .toJS); + if (configuration.configurationMap.containsKey('telemetryRefreshRate')) + scheduler.setProperty( + 'telemetryRefreshRate'.toJS, + (configuration.configurationMap['telemetryRefreshRate'] as int) + .toJS); + if (configuration.configurationMap.containsKey('eventsQueueSize')) + scheduler.setProperty('eventsQueueSize'.toJS, + (configuration.configurationMap['eventsQueueSize'] as int).toJS); + if (configuration.configurationMap.containsKey('impressionsQueueSize')) + scheduler.setProperty( + 'impressionsQueueSize'.toJS, + (configuration.configurationMap['impressionsQueueSize'] as int) + .toJS); + if (configuration.configurationMap.containsKey('eventFlushInterval')) + scheduler.setProperty('eventsPushRate'.toJS, + (configuration.configurationMap['eventFlushInterval'] as int).toJS); + config.setProperty('scheduler'.toJS, scheduler); + + if (configuration.configurationMap.containsKey('streamingEnabled')) + config.setProperty('streamingEnabled'.toJS, + (configuration.configurationMap['streamingEnabled'] as bool).toJS); + + final urls = JSObject(); + if (configuration.configurationMap.containsKey('sdkEndpoint')) + urls.setProperty('sdk'.toJS, + (configuration.configurationMap['sdkEndpoint'] as String).toJS); + if (configuration.configurationMap.containsKey('eventsEndpoint')) + urls.setProperty('events'.toJS, + (configuration.configurationMap['eventsEndpoint'] as String).toJS); + if (configuration.configurationMap.containsKey('authServiceEndpoint')) + urls.setProperty( + 'auth'.toJS, + (configuration.configurationMap['authServiceEndpoint'] as String) + .toJS); + if (configuration.configurationMap + .containsKey('streamingServiceEndpoint')) + urls.setProperty( + 'streaming'.toJS, + (configuration.configurationMap['streamingServiceEndpoint'] + as String) + .toJS); + if (configuration.configurationMap + .containsKey('telemetryServiceEndpoint')) + urls.setProperty( + 'telemetry'.toJS, + (configuration.configurationMap['telemetryServiceEndpoint'] + as String) + .toJS); + config.setProperty('urls'.toJS, urls); + + final sync = JSObject(); + if (configuration.configurationMap['impressionsMode'] != null) { + sync.setProperty( + 'impressionsMode'.toJS, + (configuration.configurationMap['impressionsMode'] as String) + .toUpperCase() + .toJS); + } + + if (configuration.configurationMap['syncEnabled'] != null) { + sync.setProperty('enabled'.toJS, + (configuration.configurationMap['syncEnabled'] as bool).toJS); + } + + if (configuration.configurationMap['syncConfig'] != null) { + final syncConfig = configuration.configurationMap['syncConfig'] + as Map>; + final List> splitFilters = []; + + if (syncConfig['syncConfigNames'] != null && + syncConfig['syncConfigNames']!.isNotEmpty) { + splitFilters + .add({'type': 'byName', 'values': syncConfig['syncConfigNames']}); + } + + if (syncConfig['syncConfigPrefixes'] != null && + syncConfig['syncConfigPrefixes']!.isNotEmpty) { + splitFilters.add( + {'type': 'byPrefix', 'values': syncConfig['syncConfigPrefixes']}); + } + + if (syncConfig['syncConfigFlagSets'] != null && + syncConfig['syncConfigFlagSets']!.isNotEmpty) { + splitFilters.add( + {'type': 'bySet', 'values': syncConfig['syncConfigFlagSets']}); + } + sync.setProperty('splitFilters'.toJS, splitFilters.jsify()); + } + config.setProperty('sync'.toJS, sync); + + if (configuration.configurationMap['userConsent'] != null) { + config.setProperty( + 'userConsent'.toJS, + (configuration.configurationMap['userConsent'] as String) + .toUpperCase() + .toJS); + } + + final logLevel = configuration.configurationMap['logLevel']; + if (logLevel is String) { + final logger = logLevel == SplitLogLevel.verbose.toString() || + logLevel == SplitLogLevel.debug.toString() + ? window.splitio!.DebugLogger?.callAsFunction(null) + : logLevel == SplitLogLevel.info.toString() + ? window.splitio!.InfoLogger?.callAsFunction(null) + : logLevel == SplitLogLevel.warning.toString() + ? window.splitio!.WarnLogger?.callAsFunction(null) + : logLevel == SplitLogLevel.error.toString() + ? window.splitio!.ErrorLogger?.callAsFunction(null) + : null; + if (logger != null) { + config.setProperty('debug'.toJS, logger); // Browser SDK + } else { + config.setProperty( + 'debug'.toJS, logLevel.toUpperCase().toJS); // JS SDK + } + } else if (configuration.configurationMap['enableDebug'] == true) { + config.setProperty( + 'debug'.toJS, window.splitio!.DebugLogger?.callAsFunction(null)); + } + + if (configuration.configurationMap['readyTimeout'] != null) { + final startup = JSObject(); + startup.setProperty('readyTimeout'.toJS, + (configuration.configurationMap['readyTimeout'] as int).toJS); + config.setProperty('startup'.toJS, startup); + } + + final storageOptions = JSObject(); + storageOptions.setProperty('type'.toJS, 'LOCALSTORAGE'.toJS); + if (configuration.configurationMap['rolloutCacheConfiguration'] != null) { + final rolloutCacheConfiguration = + configuration.configurationMap['rolloutCacheConfiguration'] + as Map; + if (rolloutCacheConfiguration['expirationDays'] != null) { + storageOptions.setProperty('expirationDays'.toJS, + (rolloutCacheConfiguration['expirationDays'] as int).toJS); + } + if (rolloutCacheConfiguration['clearOnInit'] != null) { + storageOptions.setProperty('clearOnInit'.toJS, + (rolloutCacheConfiguration['clearOnInit'] as bool).toJS); + } + } + if (window.splitio!.InLocalStorage != null) { + config.setProperty( + 'storage'.toJS, + window.splitio!.InLocalStorage + ?.callAsFunction(null, storageOptions)); // Browser SDK + } else { + config.setProperty('storage'.toJS, storageOptions); // JS SDK + } + } + return config; } + static JSAny _buildKey(String matchingKey, String? bucketingKey) { + if (bucketingKey != null) { + final splitKey = JSObject(); + splitKey.setProperty('matchingKey'.toJS, matchingKey.toJS); + splitKey.setProperty('bucketingKey'.toJS, bucketingKey.toJS); + return splitKey; + } + return matchingKey.toJS; + } } diff --git a/splitio_web/lib/src/js_interop.dart b/splitio_web/lib/src/js_interop.dart index 28192c0..fd65946 100644 --- a/splitio_web/lib/src/js_interop.dart +++ b/splitio_web/lib/src/js_interop.dart @@ -1,9 +1,26 @@ import 'dart:js_interop'; @JS() -extension type JS_IBrowserSDK._(JSObject _) implements JSObject {} +extension type JS_Logger._(JSObject _) implements JSObject { + external JSFunction warn; +} + +@JS() +extension type JS_ISettings._(JSObject _) implements JSObject { + external JS_Logger log; +} + +@JS() +extension type JS_IBrowserSDK._(JSObject _) implements JSObject { + external JS_ISettings settings; +} @JS() extension type JS_BrowserSDKPackage._(JSObject _) implements JSObject { external JSFunction SplitFactory; + external JSFunction? InLocalStorage; + external JSFunction? DebugLogger; + external JSFunction? InfoLogger; + external JSFunction? WarnLogger; + external JSFunction? ErrorLogger; } diff --git a/splitio_web/test/splitio_web_test.dart b/splitio_web/test/splitio_web_test.dart new file mode 100644 index 0000000..5c3dafe --- /dev/null +++ b/splitio_web/test/splitio_web_test.dart @@ -0,0 +1,240 @@ +import 'dart:js_interop'; +import 'dart:js_interop_unsafe'; +import 'package:web/web.dart' as web; +import 'package:flutter_test/flutter_test.dart'; +import 'package:splitio_web/splitio_web.dart'; +import 'package:splitio_web/src/js_interop.dart'; +import 'package:splitio_platform_interface/split_configuration.dart'; +import 'package:splitio_platform_interface/split_sync_config.dart'; +import 'package:splitio_platform_interface/split_rollout_cache_configuration.dart'; +import 'utils/js_interop_test_utils.dart'; + +extension on web.Window { + @JS() + external JS_BrowserSDKPackage? splitio; +} + +void main() { + String methodName = ''; + dynamic methodArguments; + + setUp(() { + final mockFactory = JSObject(); + + final mockSplitio = JSObject(); + mockSplitio['SplitFactory'] = (JSAny? arg1) { + methodName = 'SplitFactory'; + methodArguments = [arg1]; + return mockFactory; + }.toJS; + + (web.window as JSObject).setProperty('splitio'.toJS, mockSplitio); + }); + + group('initialization', () { + test('init with matching key only', () async { + SplitioWeb _platform = SplitioWeb(); + + await _platform.init( + apiKey: 'api-key', matchingKey: 'matching-key', bucketingKey: null); + + expect(methodName, 'SplitFactory'); + expect( + jsObjectToMap(methodArguments[0]), + equals({ + 'core': { + 'authorizationKey': 'api-key', + 'key': 'matching-key', + } + })); + }); + + test('init with bucketing key', () async { + SplitioWeb _platform = SplitioWeb(); + + await _platform.init( + apiKey: 'api-key', + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key'); + + expect(methodName, 'SplitFactory'); + expect( + jsObjectToMap(methodArguments[0]), + equals({ + 'core': { + 'authorizationKey': 'api-key', + 'key': { + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + }, + } + })); + }); + + test('init with config: empty config', () async { + SplitioWeb _platform = SplitioWeb(); + + await _platform.init( + apiKey: 'api-key', + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + sdkConfiguration: SplitConfiguration()); + + expect(methodName, 'SplitFactory'); + expect( + jsObjectToMap(methodArguments[0]), + equals({ + 'core': { + 'authorizationKey': 'api-key', + 'key': { + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + }, + }, + 'startup': { + 'readyTimeout': 10, + }, + 'scheduler': {}, + 'urls': {}, + 'sync': {}, + 'storage': {'type': 'LOCALSTORAGE'} + })); + }); + + // @TODO validate warning for unsupported config options + // @TODO validate full config with pluggable Browser SDK modules + test('init with config: full config', () async { + SplitioWeb _platform = SplitioWeb(); + + await _platform.init( + apiKey: 'api-key', + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + sdkConfiguration: SplitConfiguration( + featuresRefreshRate: 1, + segmentsRefreshRate: 2, + impressionsRefreshRate: 3, + telemetryRefreshRate: 4, + eventsQueueSize: 5, + impressionsQueueSize: 6, + eventFlushInterval: 7, + // eventsPerPush: 8, // unsupported in Web + trafficType: 'user', + enableDebug: false, // deprecated, logLevel has precedence + streamingEnabled: false, + // persistentAttributesEnabled: true, // unsupported in Web + impressionListener: true, + sdkEndpoint: 'sdk-endpoint', + eventsEndpoint: 'events-endpoint', + authServiceEndpoint: 'auth-service-endpoint', + streamingServiceEndpoint: 'streaming-service-endpoint', + telemetryServiceEndpoint: 'telemetry-service-endpoint', + syncConfig: SyncConfig( + names: ['flag_1', 'flag_2'], prefixes: ['prefix_1']), + impressionsMode: ImpressionsMode.none, + syncEnabled: true, + userConsent: UserConsent.granted, + // encryptionEnabled: true, // unsupported in Web + logLevel: SplitLogLevel.info, + readyTimeout: 1, + // certificatePinningConfiguration: + // CertificatePinningConfiguration(), // unsupported in Web + rolloutCacheConfiguration: RolloutCacheConfiguration( + expirationDays: 100, + clearOnInit: true, + ))); + + expect(methodName, 'SplitFactory'); + expect( + jsObjectToMap(methodArguments[0]), + equals({ + 'core': { + 'authorizationKey': 'api-key', + 'key': { + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + }, + }, + 'streamingEnabled': false, + 'startup': { + 'readyTimeout': 1, + }, + 'debug': 'INFO', + 'scheduler': { + 'featuresRefreshRate': 1, + 'segmentsRefreshRate': 2, + 'impressionsRefreshRate': 3, + 'telemetryRefreshRate': 4, + 'eventsQueueSize': 5, + 'impressionsQueueSize': 6, + 'eventsPushRate': 7, + }, + 'urls': { + 'sdk': 'sdk-endpoint', + 'events': 'events-endpoint', + 'auth': 'auth-service-endpoint', + 'streaming': 'streaming-service-endpoint', + 'telemetry': 'telemetry-service-endpoint', + }, + 'sync': { + 'impressionsMode': 'NONE', + 'enabled': true, + 'splitFilters': [ + { + 'type': 'byName', + 'values': ['flag_1', 'flag_2'] + }, + { + 'type': 'byPrefix', + 'values': ['prefix_1'] + } + ] + }, + 'userConsent': 'GRANTED', + 'storage': { + 'type': 'LOCALSTORAGE', + 'expirationDays': 100, + 'clearOnInit': true + } + })); + }); + + test('init with config: SyncConfig.flagSets', () async { + SplitioWeb _platform = SplitioWeb(); + + await _platform.init( + apiKey: 'api-key', + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + sdkConfiguration: SplitConfiguration( + syncConfig: SyncConfig.flagSets(['flag_set_1', 'flag_set_2']))); + + expect(methodName, 'SplitFactory'); + expect( + jsObjectToMap(methodArguments[0]), + equals({ + 'core': { + 'authorizationKey': 'api-key', + 'key': { + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + }, + }, + 'startup': { + 'readyTimeout': 10, + }, + 'scheduler': {}, + 'urls': {}, + 'sync': { + 'splitFilters': [ + { + 'type': 'bySet', + 'values': ['flag_set_1', 'flag_set_2'] + } + ] + }, + 'storage': {'type': 'LOCALSTORAGE'} + })); + }); + }); +} diff --git a/splitio_web/test/utils/js_interop_test_utils.dart b/splitio_web/test/utils/js_interop_test_utils.dart new file mode 100644 index 0000000..153e627 --- /dev/null +++ b/splitio_web/test/utils/js_interop_test_utils.dart @@ -0,0 +1,34 @@ +import 'dart:js_interop'; + +@JS('Object.keys') +external JSArray _objectKeys(JSObject obj); + +@JS('Reflect.get') +external JSAny? _reflectGet(JSObject target, JSAny propertyKey); + +List jsArrayToList(JSArray obj) { + return obj.toDart.map(jsAnyToDart).toList(); +} + +Map jsObjectToMap(JSObject obj) { + return { + for (final jsKey in _objectKeys(obj).toDart) + jsKey.toDart: jsAnyToDart(_reflectGet(obj, jsKey)), + }; +} + +dynamic jsAnyToDart(JSAny? value) { + if (value is JSArray) { + return jsArrayToList(value); + } else if (value is JSObject) { + return jsObjectToMap(value); + } else if (value is JSString) { + return value.toDart; + } else if (value is JSNumber) { + return value.toDartInt; + } else if (value is JSBoolean) { + return value.toDart; + } else { + return value; // JS null and undefined are null in Dart + } +} From bf7ab850874c6802d352c6b92b23a904e195d046 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Mon, 22 Dec 2025 12:54:42 -0300 Subject: [PATCH 15/55] Linter --- splitio_web/lib/splitio_web.dart | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index d175b7d..28e4459 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -33,7 +33,11 @@ class SplitioWeb extends SplitioPlatform { SplitConfiguration? sdkConfiguration, }) async { if (_initFuture == null) { - _initFuture = this._init(apiKey: apiKey, matchingKey: matchingKey, bucketingKey: bucketingKey, sdkConfiguration: sdkConfiguration); + _initFuture = this._init( + apiKey: apiKey, + matchingKey: matchingKey, + bucketingKey: bucketingKey, + sdkConfiguration: sdkConfiguration); } return _initFuture; } @@ -46,10 +50,12 @@ class SplitioWeb extends SplitioPlatform { }) async { await _loadSplitSdk(); - final config = _buildConfig(apiKey, matchingKey, bucketingKey, sdkConfiguration); + final config = + _buildConfig(apiKey, matchingKey, bucketingKey, sdkConfiguration); // Create factory instance - this._factory = window.splitio!.SplitFactory.callAsFunction(null, config) as JS_IBrowserSDK; + this._factory = window.splitio!.SplitFactory.callAsFunction(null, config) + as JS_IBrowserSDK; if (sdkConfiguration != null) { if (sdkConfiguration.configurationMap['trafficType'] is String) { @@ -114,7 +120,8 @@ class SplitioWeb extends SplitioPlatform { } // Map SplitConfiguration to JS equivalent object - static JSObject _buildConfig(String apiKey, String matchingKey, String? bucketingKey, SplitConfiguration? configuration) { + static JSObject _buildConfig(String apiKey, String matchingKey, + String? bucketingKey, SplitConfiguration? configuration) { final config = JSObject(); final core = JSObject(); From 4400f260056b7d0ae964489474879e6b26da984e Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Mon, 22 Dec 2025 14:00:01 -0300 Subject: [PATCH 16/55] Remove code for unsupported configs --- splitio_web/lib/splitio_web.dart | 16 ---------------- splitio_web/lib/src/js_interop.dart | 11 ----------- 2 files changed, 27 deletions(-) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index 28e4459..7a3ad91 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -66,22 +66,6 @@ class SplitioWeb extends SplitioPlatform { this._impressionListener = sdkConfiguration.configurationMap['impressionListener']; } - - // Log warnings regarding unsupported configs. Not done in _buildConfig to reuse the factory logger - final unsupportedConfigs = [ - 'encryptionEnabled', - 'certificatePinningConfiguration', - 'persistentAttributesEnabled', - 'eventsPerPush' - ]; - for (final configName in unsupportedConfigs) { - if (sdkConfiguration.configurationMap[configName] != null) { - this._factory.settings.log.warn.callAsFunction( - this._factory.settings.log, - 'Config $configName is not supported by the Web package. This config will be ignored.' - .toJS); - } - } } return; diff --git a/splitio_web/lib/src/js_interop.dart b/splitio_web/lib/src/js_interop.dart index fd65946..312f50e 100644 --- a/splitio_web/lib/src/js_interop.dart +++ b/splitio_web/lib/src/js_interop.dart @@ -1,18 +1,7 @@ import 'dart:js_interop'; -@JS() -extension type JS_Logger._(JSObject _) implements JSObject { - external JSFunction warn; -} - -@JS() -extension type JS_ISettings._(JSObject _) implements JSObject { - external JS_Logger log; -} - @JS() extension type JS_IBrowserSDK._(JSObject _) implements JSObject { - external JS_ISettings settings; } @JS() From da974158f49754af860199b3bea1467c463e55bf Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Mon, 22 Dec 2025 14:01:27 -0300 Subject: [PATCH 17/55] Log warning for unsupported configs --- splitio_web/lib/splitio_web.dart | 16 ++++++++++++++++ splitio_web/lib/src/js_interop.dart | 11 +++++++++++ 2 files changed, 27 insertions(+) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index 7a3ad91..28e4459 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -66,6 +66,22 @@ class SplitioWeb extends SplitioPlatform { this._impressionListener = sdkConfiguration.configurationMap['impressionListener']; } + + // Log warnings regarding unsupported configs. Not done in _buildConfig to reuse the factory logger + final unsupportedConfigs = [ + 'encryptionEnabled', + 'certificatePinningConfiguration', + 'persistentAttributesEnabled', + 'eventsPerPush' + ]; + for (final configName in unsupportedConfigs) { + if (sdkConfiguration.configurationMap[configName] != null) { + this._factory.settings.log.warn.callAsFunction( + this._factory.settings.log, + 'Config $configName is not supported by the Web package. This config will be ignored.' + .toJS); + } + } } return; diff --git a/splitio_web/lib/src/js_interop.dart b/splitio_web/lib/src/js_interop.dart index 312f50e..fd65946 100644 --- a/splitio_web/lib/src/js_interop.dart +++ b/splitio_web/lib/src/js_interop.dart @@ -1,7 +1,18 @@ import 'dart:js_interop'; +@JS() +extension type JS_Logger._(JSObject _) implements JSObject { + external JSFunction warn; +} + +@JS() +extension type JS_ISettings._(JSObject _) implements JSObject { + external JS_Logger log; +} + @JS() extension type JS_IBrowserSDK._(JSObject _) implements JSObject { + external JS_ISettings settings; } @JS() From eb71ff5047309ebd61a3f0b1c9913191da037df0 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Mon, 22 Dec 2025 16:31:06 -0300 Subject: [PATCH 18/55] Add tests --- splitio_web/lib/splitio_web.dart | 6 +- splitio_web/test/splitio_web_test.dart | 82 ++++++++++++++++++-------- 2 files changed, 59 insertions(+), 29 deletions(-) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index 28e4459..78dfbbe 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -69,10 +69,10 @@ class SplitioWeb extends SplitioPlatform { // Log warnings regarding unsupported configs. Not done in _buildConfig to reuse the factory logger final unsupportedConfigs = [ - 'encryptionEnabled', 'certificatePinningConfiguration', - 'persistentAttributesEnabled', - 'eventsPerPush' + 'encryptionEnabled', + 'eventsPerPush', + 'persistentAttributesEnabled' ]; for (final configName in unsupportedConfigs) { if (sdkConfiguration.configurationMap[configName] != null) { diff --git a/splitio_web/test/splitio_web_test.dart b/splitio_web/test/splitio_web_test.dart index 5c3dafe..c575765 100644 --- a/splitio_web/test/splitio_web_test.dart +++ b/splitio_web/test/splitio_web_test.dart @@ -4,6 +4,7 @@ import 'package:web/web.dart' as web; import 'package:flutter_test/flutter_test.dart'; import 'package:splitio_web/splitio_web.dart'; import 'package:splitio_web/src/js_interop.dart'; +import 'package:splitio_platform_interface/split_certificate_pinning_configuration.dart'; import 'package:splitio_platform_interface/split_configuration.dart'; import 'package:splitio_platform_interface/split_sync_config.dart'; import 'package:splitio_platform_interface/split_rollout_cache_configuration.dart'; @@ -15,19 +16,25 @@ extension on web.Window { } void main() { - String methodName = ''; - dynamic methodArguments; + final List<({String methodName, List methodArguments})> calls = []; - setUp(() { - final mockFactory = JSObject(); + final mockLog = JSObject(); + mockLog['warn'] = (JSAny? arg1) { + calls.add((methodName: 'warn', methodArguments: [arg1])); + }.toJS; + final mockSettings = JSObject(); + mockSettings['log'] = mockLog; + + final mockFactory = JSObject(); + mockFactory['settings'] = mockSettings; - final mockSplitio = JSObject(); - mockSplitio['SplitFactory'] = (JSAny? arg1) { - methodName = 'SplitFactory'; - methodArguments = [arg1]; - return mockFactory; - }.toJS; + final mockSplitio = JSObject(); + mockSplitio['SplitFactory'] = (JSAny? arg1) { + calls.add((methodName: 'SplitFactory', methodArguments: [arg1])); + return mockFactory; + }.toJS; + setUp(() { (web.window as JSObject).setProperty('splitio'.toJS, mockSplitio); }); @@ -38,9 +45,9 @@ void main() { await _platform.init( apiKey: 'api-key', matchingKey: 'matching-key', bucketingKey: null); - expect(methodName, 'SplitFactory'); + expect(calls.last.methodName, 'SplitFactory'); expect( - jsObjectToMap(methodArguments[0]), + jsObjectToMap(calls.last.methodArguments[0]), equals({ 'core': { 'authorizationKey': 'api-key', @@ -57,9 +64,9 @@ void main() { matchingKey: 'matching-key', bucketingKey: 'bucketing-key'); - expect(methodName, 'SplitFactory'); + expect(calls.last.methodName, 'SplitFactory'); expect( - jsObjectToMap(methodArguments[0]), + jsObjectToMap(calls.last.methodArguments[0]), equals({ 'core': { 'authorizationKey': 'api-key', @@ -80,9 +87,9 @@ void main() { bucketingKey: 'bucketing-key', sdkConfiguration: SplitConfiguration()); - expect(methodName, 'SplitFactory'); + expect(calls.last.methodName, 'SplitFactory'); expect( - jsObjectToMap(methodArguments[0]), + jsObjectToMap(calls.last.methodArguments[0]), equals({ 'core': { 'authorizationKey': 'api-key', @@ -101,7 +108,6 @@ void main() { })); }); - // @TODO validate warning for unsupported config options // @TODO validate full config with pluggable Browser SDK modules test('init with config: full config', () async { SplitioWeb _platform = SplitioWeb(); @@ -118,11 +124,11 @@ void main() { eventsQueueSize: 5, impressionsQueueSize: 6, eventFlushInterval: 7, - // eventsPerPush: 8, // unsupported in Web + eventsPerPush: 8, // unsupported in Web trafficType: 'user', enableDebug: false, // deprecated, logLevel has precedence streamingEnabled: false, - // persistentAttributesEnabled: true, // unsupported in Web + persistentAttributesEnabled: true, // unsupported in Web impressionListener: true, sdkEndpoint: 'sdk-endpoint', eventsEndpoint: 'events-endpoint', @@ -134,19 +140,19 @@ void main() { impressionsMode: ImpressionsMode.none, syncEnabled: true, userConsent: UserConsent.granted, - // encryptionEnabled: true, // unsupported in Web + encryptionEnabled: true, // unsupported in Web logLevel: SplitLogLevel.info, readyTimeout: 1, - // certificatePinningConfiguration: - // CertificatePinningConfiguration(), // unsupported in Web + certificatePinningConfiguration: CertificatePinningConfiguration() + .addPin('host', 'pin'), // unsupported in Web rolloutCacheConfiguration: RolloutCacheConfiguration( expirationDays: 100, clearOnInit: true, ))); - expect(methodName, 'SplitFactory'); + expect(calls[calls.length - 5].methodName, 'SplitFactory'); expect( - jsObjectToMap(methodArguments[0]), + jsObjectToMap(calls[calls.length - 5].methodArguments[0]), equals({ 'core': { 'authorizationKey': 'api-key', @@ -197,6 +203,30 @@ void main() { 'clearOnInit': true } })); + + expect(calls[calls.length - 4].methodName, 'warn'); + expect( + jsAnyToDart(calls[calls.length - 4].methodArguments[0]), + equals( + 'Config certificatePinningConfiguration is not supported by the Web package. This config will be ignored.')); + + expect(calls[calls.length - 3].methodName, 'warn'); + expect( + jsAnyToDart(calls[calls.length - 3].methodArguments[0]), + equals( + 'Config encryptionEnabled is not supported by the Web package. This config will be ignored.')); + + expect(calls[calls.length - 2].methodName, 'warn'); + expect( + jsAnyToDart(calls[calls.length - 2].methodArguments[0]), + equals( + 'Config eventsPerPush is not supported by the Web package. This config will be ignored.')); + + expect(calls[calls.length - 1].methodName, 'warn'); + expect( + jsAnyToDart(calls[calls.length - 1].methodArguments[0]), + equals( + 'Config persistentAttributesEnabled is not supported by the Web package. This config will be ignored.')); }); test('init with config: SyncConfig.flagSets', () async { @@ -209,9 +239,9 @@ void main() { sdkConfiguration: SplitConfiguration( syncConfig: SyncConfig.flagSets(['flag_set_1', 'flag_set_2']))); - expect(methodName, 'SplitFactory'); + expect(calls.last.methodName, 'SplitFactory'); expect( - jsObjectToMap(methodArguments[0]), + jsObjectToMap(calls.last.methodArguments[0]), equals({ 'core': { 'authorizationKey': 'api-key', From 3f64157b0ec9dd5afce4bb4bbaa0371938b15f0c Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 23 Dec 2025 17:04:41 -0300 Subject: [PATCH 19/55] Implement factory.client and client.getTreatment methods --- splitio_web/lib/splitio_web.dart | 104 +++++++++++++++++- splitio_web/lib/src/js_interop.dart | 6 + splitio_web/test/splitio_web_test.dart | 93 ++++++++++++++-- .../test/utils/js_interop_test_utils.dart | 2 +- 4 files changed, 196 insertions(+), 9 deletions(-) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index 78dfbbe..af7f5f7 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -25,6 +25,8 @@ class SplitioWeb extends SplitioPlatform { String? _trafficType; bool _impressionListener = false; + final Map _clients = {}; + @override Future init({ required String apiKey, @@ -107,7 +109,8 @@ class SplitioWeb extends SplitioPlatform { }.toJS; script.onerror = (Event event) { - completer.completeError(Exception('Failed to load Split SDK')); + completer.completeError( + Exception('Failed to load Split SDK, with error: $event')); }.toJS; document.head!.appendChild(script); @@ -311,4 +314,103 @@ class SplitioWeb extends SplitioPlatform { } return matchingKey.toJS; } + + static String _buildKeyString(String matchingKey, String? bucketingKey) { + return bucketingKey == null ? matchingKey : '${matchingKey}_$bucketingKey'; + } + + @override + Future getClient({ + required String matchingKey, + required String? bucketingKey, + }) async { + await this._initFuture; + + final key = _buildKeyString(matchingKey, bucketingKey); + + if (_clients.containsKey(key)) { + return; + } + + final client = this._factory.client.callAsFunction( + null, _buildKey(matchingKey, bucketingKey)) as JS_IBrowserClient; + + _clients[key] = client; + } + + Future _getClient({ + required String matchingKey, + required String? bucketingKey, + }) async { + await getClient(matchingKey: matchingKey, bucketingKey: bucketingKey); + + final key = _buildKeyString(matchingKey, bucketingKey); + + return _clients[key]!; + } + + JSAny? _convertValue(dynamic value, bool attributes) { + if (value is bool) return value.toJS; + if (value is num) return value.toJS; // covers int + double + if (value is String) return value.toJS; + + // properties do not support lists and sets + if (attributes) { + if (value is List) return value.jsify(); + if (value is Set) return value.jsify(); + } + + return null; + } + + JSObject _convertMap(Map dartMap, bool areAttributes) { + final jsMap = JSObject(); + + dartMap.forEach((key, value) { + final jsValue = _convertValue(value, areAttributes); + + if (jsValue != null) { + jsMap.setProperty(key.toJS, jsValue); + } else { + this._factory.settings.log.warn.callAsFunction( + null, + 'Invalid ${areAttributes ? 'attribute' : 'property'} value: $value, for key: $key, will be ignored' + .toJS); + } + }); + + return jsMap; + } + + JSObject _convertEvaluationOptions(EvaluationOptions evaluationOptions) { + final jsEvalOptions = JSObject(); + + if (evaluationOptions.properties.isNotEmpty) { + jsEvalOptions.setProperty( + 'properties'.toJS, _convertMap(evaluationOptions.properties, false)); + } + + return jsEvalOptions; + } + + @override + Future getTreatment({ + required String matchingKey, + required String? bucketingKey, + required String splitName, + Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty(), + }) async { + final client = await _getClient( + matchingKey: matchingKey, + bucketingKey: bucketingKey, + ); + + final result = client.getTreatment.callAsFunction( + null, + splitName.toJS, + _convertMap(attributes, true), + _convertEvaluationOptions(evaluationOptions)) as JSString; + return result.toDart; + } } diff --git a/splitio_web/lib/src/js_interop.dart b/splitio_web/lib/src/js_interop.dart index fd65946..75889c8 100644 --- a/splitio_web/lib/src/js_interop.dart +++ b/splitio_web/lib/src/js_interop.dart @@ -10,8 +10,14 @@ extension type JS_ISettings._(JSObject _) implements JSObject { external JS_Logger log; } +@JS() +extension type JS_IBrowserClient._(JSObject _) implements JSObject { + external JSFunction getTreatment; +} + @JS() extension type JS_IBrowserSDK._(JSObject _) implements JSObject { + external JSFunction client; external JS_ISettings settings; } diff --git a/splitio_web/test/splitio_web_test.dart b/splitio_web/test/splitio_web_test.dart index c575765..d109f73 100644 --- a/splitio_web/test/splitio_web_test.dart +++ b/splitio_web/test/splitio_web_test.dart @@ -16,17 +16,32 @@ extension on web.Window { } void main() { - final List<({String methodName, List methodArguments})> calls = []; + final List<({String methodName, List methodArguments})> calls = []; + + final mockClient = JSObject(); + mockClient['getTreatment'] = + (JSAny? flagName, JSAny? attributes, JSAny? evaluationOptions) { + calls.add(( + methodName: 'getTreatment', + methodArguments: [flagName, attributes, evaluationOptions] + )); + return 'on'.toJS; + }.toJS; final mockLog = JSObject(); mockLog['warn'] = (JSAny? arg1) { calls.add((methodName: 'warn', methodArguments: [arg1])); }.toJS; + final mockSettings = JSObject(); mockSettings['log'] = mockLog; final mockFactory = JSObject(); mockFactory['settings'] = mockSettings; + mockFactory['client'] = (JSAny? splitKey) { + calls.add((methodName: 'client', methodArguments: [splitKey])); + return mockClient; + }.toJS; final mockSplitio = JSObject(); mockSplitio['SplitFactory'] = (JSAny? arg1) { @@ -34,8 +49,72 @@ void main() { return mockFactory; }.toJS; + SplitioWeb _platform = SplitioWeb(); + setUp(() { - (web.window as JSObject).setProperty('splitio'.toJS, mockSplitio); + (web.window as JSObject)['splitio'] = mockSplitio; + + _platform.init( + apiKey: 'apiKey', + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key'); + }); + + group('evaluation', () { + test('getTreatment without attributes', () async { + final result = await _platform.getTreatment( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + splitName: 'split'); + + expect(result, 'on'); + expect(calls.last.methodName, 'getTreatment'); + expect(calls.last.methodArguments.map(jsAnyToDart), ['split', {}, {}]); + }); + + test('getTreatment with attributes', () async { + final result = await _platform.getTreatment( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + splitName: 'split', + attributes: { + 'attrBool': true, + 'attrString': 'value', + 'attrInt': 1, + 'attrDouble': 1.1, + 'attrList': ['value1', 100, false], + 'attrSet': {'value3', 100, true}, + 'attrNull': null, // ignored + 'attrInvalid': {'value5': true} // ignored + }); + + expect(result, 'on'); + expect(calls.last.methodName, 'getTreatment'); + expect(calls.last.methodArguments.map(jsAnyToDart), [ + 'split', + { + 'attrBool': true, + 'attrString': 'value', + 'attrInt': 1, + 'attrDouble': 1.1, + 'attrList': ['value1', 100, false], + 'attrSet': ['value3', 100, true] + }, + {} + ]); + + // assert warnings + expect(calls[calls.length - 2].methodName, 'warn'); + expect( + jsAnyToDart(calls[calls.length - 2].methodArguments[0]), + equals( + 'Invalid attribute value: {value5: true}, for key: attrInvalid, will be ignored')); + expect(calls[calls.length - 3].methodName, 'warn'); + expect( + jsAnyToDart(calls[calls.length - 3].methodArguments[0]), + equals( + 'Invalid attribute value: null, for key: attrNull, will be ignored')); + }); }); group('initialization', () { @@ -47,7 +126,7 @@ void main() { expect(calls.last.methodName, 'SplitFactory'); expect( - jsObjectToMap(calls.last.methodArguments[0]), + jsAnyToDart(calls.last.methodArguments[0]), equals({ 'core': { 'authorizationKey': 'api-key', @@ -66,7 +145,7 @@ void main() { expect(calls.last.methodName, 'SplitFactory'); expect( - jsObjectToMap(calls.last.methodArguments[0]), + jsAnyToDart(calls.last.methodArguments[0]), equals({ 'core': { 'authorizationKey': 'api-key', @@ -89,7 +168,7 @@ void main() { expect(calls.last.methodName, 'SplitFactory'); expect( - jsObjectToMap(calls.last.methodArguments[0]), + jsAnyToDart(calls.last.methodArguments[0]), equals({ 'core': { 'authorizationKey': 'api-key', @@ -152,7 +231,7 @@ void main() { expect(calls[calls.length - 5].methodName, 'SplitFactory'); expect( - jsObjectToMap(calls[calls.length - 5].methodArguments[0]), + jsAnyToDart(calls[calls.length - 5].methodArguments[0]), equals({ 'core': { 'authorizationKey': 'api-key', @@ -241,7 +320,7 @@ void main() { expect(calls.last.methodName, 'SplitFactory'); expect( - jsObjectToMap(calls.last.methodArguments[0]), + jsAnyToDart(calls.last.methodArguments[0]), equals({ 'core': { 'authorizationKey': 'api-key', diff --git a/splitio_web/test/utils/js_interop_test_utils.dart b/splitio_web/test/utils/js_interop_test_utils.dart index 153e627..bbe78d8 100644 --- a/splitio_web/test/utils/js_interop_test_utils.dart +++ b/splitio_web/test/utils/js_interop_test_utils.dart @@ -25,7 +25,7 @@ dynamic jsAnyToDart(JSAny? value) { } else if (value is JSString) { return value.toDart; } else if (value is JSNumber) { - return value.toDartInt; + return value.toDartDouble; } else if (value is JSBoolean) { return value.toDart; } else { From 3d04068a90d18f1776627fbc310e502d0bb797fc Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 23 Dec 2025 18:04:21 -0300 Subject: [PATCH 20/55] Add test with evaluation options --- splitio_web/test/splitio_web_test.dart | 62 ++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/splitio_web/test/splitio_web_test.dart b/splitio_web/test/splitio_web_test.dart index d109f73..97b9d6f 100644 --- a/splitio_web/test/splitio_web_test.dart +++ b/splitio_web/test/splitio_web_test.dart @@ -6,6 +6,7 @@ import 'package:splitio_web/splitio_web.dart'; import 'package:splitio_web/src/js_interop.dart'; import 'package:splitio_platform_interface/split_certificate_pinning_configuration.dart'; import 'package:splitio_platform_interface/split_configuration.dart'; +import 'package:splitio_platform_interface/split_evaluation_options.dart'; import 'package:splitio_platform_interface/split_sync_config.dart'; import 'package:splitio_platform_interface/split_rollout_cache_configuration.dart'; import 'utils/js_interop_test_utils.dart'; @@ -84,8 +85,8 @@ void main() { 'attrDouble': 1.1, 'attrList': ['value1', 100, false], 'attrSet': {'value3', 100, true}, - 'attrNull': null, // ignored - 'attrInvalid': {'value5': true} // ignored + 'attrNull': null, // not valid attribute value + 'attrMap': {'value5': true} // not valid attribute value }); expect(result, 'on'); @@ -108,13 +109,68 @@ void main() { expect( jsAnyToDart(calls[calls.length - 2].methodArguments[0]), equals( - 'Invalid attribute value: {value5: true}, for key: attrInvalid, will be ignored')); + 'Invalid attribute value: {value5: true}, for key: attrMap, will be ignored')); expect(calls[calls.length - 3].methodName, 'warn'); expect( jsAnyToDart(calls[calls.length - 3].methodArguments[0]), equals( 'Invalid attribute value: null, for key: attrNull, will be ignored')); }); + + test('getTreatment with evaluation properties', () async { + final result = await _platform.getTreatment( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + splitName: 'split', + evaluationOptions: EvaluationOptions({ + 'propBool': true, + 'propString': 'value', + 'propInt': 1, + 'propDouble': 1.1, + 'propList': ['value1', 100, false], // not valid property value + 'propSet': {'value3', 100, true}, // not valid property value + 'propNull': null, // not valid property value + 'propMap': {'value5': true} // not valid property value + })); + + expect(result, 'on'); + expect(calls.last.methodName, 'getTreatment'); + expect(calls.last.methodArguments.map(jsAnyToDart), [ + 'split', + {}, + { + 'properties': { + 'propBool': true, + 'propString': 'value', + 'propInt': 1, + 'propDouble': 1.1, + } + } + ]); + + // assert warnings + expect(calls[calls.length - 2].methodName, 'warn'); + expect( + jsAnyToDart(calls[calls.length - 2].methodArguments[0]), + equals( + 'Invalid property value: {value5: true}, for key: propMap, will be ignored')); + expect(calls[calls.length - 3].methodName, 'warn'); + expect( + jsAnyToDart(calls[calls.length - 3].methodArguments[0]), + equals( + 'Invalid property value: null, for key: propNull, will be ignored')); + expect(calls[calls.length - 4].methodName, 'warn'); + expect( + jsAnyToDart(calls[calls.length - 4].methodArguments[0]), + equals( + 'Invalid property value: {value3, 100, true}, for key: propSet, will be ignored')); + expect(calls[calls.length - 5].methodName, 'warn'); + expect( + jsAnyToDart(calls[calls.length - 5].methodArguments[0]), + equals( + 'Invalid property value: [value1, 100, false], for key: propList, will be ignored')); + }); + }); group('initialization', () { From 029e9f181c1f7ac836a19db52be3e0a23c1f9c7f Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 26 Dec 2025 11:09:41 -0300 Subject: [PATCH 21/55] Rename parameter from `areAttributes` to `isAttributes` for consistency --- splitio_web/lib/splitio_web.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index af7f5f7..b7bcdf2 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -349,13 +349,13 @@ class SplitioWeb extends SplitioPlatform { return _clients[key]!; } - JSAny? _convertValue(dynamic value, bool attributes) { + JSAny? _convertValue(dynamic value, bool isAttributes) { if (value is bool) return value.toJS; if (value is num) return value.toJS; // covers int + double if (value is String) return value.toJS; // properties do not support lists and sets - if (attributes) { + if (isAttributes) { if (value is List) return value.jsify(); if (value is Set) return value.jsify(); } @@ -363,18 +363,18 @@ class SplitioWeb extends SplitioPlatform { return null; } - JSObject _convertMap(Map dartMap, bool areAttributes) { + JSObject _convertMap(Map dartMap, bool isAttributes) { final jsMap = JSObject(); dartMap.forEach((key, value) { - final jsValue = _convertValue(value, areAttributes); + final jsValue = _convertValue(value, isAttributes); if (jsValue != null) { jsMap.setProperty(key.toJS, jsValue); } else { this._factory.settings.log.warn.callAsFunction( null, - 'Invalid ${areAttributes ? 'attribute' : 'property'} value: $value, for key: $key, will be ignored' + 'Invalid ${isAttributes ? 'attribute' : 'property'} value: $value, for key: $key, will be ignored' .toJS); } }); From 62797f97633892f62665cd7bf3334fa1c4c08060 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 26 Dec 2025 13:05:10 -0300 Subject: [PATCH 22/55] Move conversion utils to production code, for reusability --- splitio_web/lib/src/js_interop.dart | 38 +++++++++++++++++++ splitio_web/test/splitio_web_test.dart | 1 - .../test/utils/js_interop_test_utils.dart | 34 ----------------- 3 files changed, 38 insertions(+), 35 deletions(-) delete mode 100644 splitio_web/test/utils/js_interop_test_utils.dart diff --git a/splitio_web/lib/src/js_interop.dart b/splitio_web/lib/src/js_interop.dart index 312f50e..94f24ba 100644 --- a/splitio_web/lib/src/js_interop.dart +++ b/splitio_web/lib/src/js_interop.dart @@ -1,5 +1,7 @@ import 'dart:js_interop'; +// JS SDK types + @JS() extension type JS_IBrowserSDK._(JSObject _) implements JSObject { } @@ -13,3 +15,39 @@ extension type JS_BrowserSDKPackage._(JSObject _) implements JSObject { external JSFunction? WarnLogger; external JSFunction? ErrorLogger; } + +// Conversion utils: JS to Dart types + +@JS('Object.keys') +external JSArray _objectKeys(JSObject obj); + +@JS('Reflect.get') +external JSAny? _reflectGet(JSObject target, JSAny propertyKey); + +List jsArrayToList(JSArray obj) { + return obj.toDart.map(jsAnyToDart).toList(); +} + +Map jsObjectToMap(JSObject obj) { + return { + for (final jsKey in _objectKeys(obj).toDart) + // @TODO _reflectGet (js_interop) vs obj.getProperty (js_interop_unsafe) + jsKey.toDart: jsAnyToDart(_reflectGet(obj, jsKey)), + }; +} + +dynamic jsAnyToDart(JSAny? value) { + if (value is JSArray) { + return jsArrayToList(value); + } else if (value is JSObject) { + return jsObjectToMap(value); + } else if (value is JSString) { + return value.toDart; + } else if (value is JSNumber) { + return value.toDartDouble; + } else if (value is JSBoolean) { + return value.toDart; + } else { + return value; // JS null and undefined are null in Dart + } +} diff --git a/splitio_web/test/splitio_web_test.dart b/splitio_web/test/splitio_web_test.dart index 5c3dafe..0d658dd 100644 --- a/splitio_web/test/splitio_web_test.dart +++ b/splitio_web/test/splitio_web_test.dart @@ -7,7 +7,6 @@ import 'package:splitio_web/src/js_interop.dart'; import 'package:splitio_platform_interface/split_configuration.dart'; import 'package:splitio_platform_interface/split_sync_config.dart'; import 'package:splitio_platform_interface/split_rollout_cache_configuration.dart'; -import 'utils/js_interop_test_utils.dart'; extension on web.Window { @JS() diff --git a/splitio_web/test/utils/js_interop_test_utils.dart b/splitio_web/test/utils/js_interop_test_utils.dart deleted file mode 100644 index 153e627..0000000 --- a/splitio_web/test/utils/js_interop_test_utils.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'dart:js_interop'; - -@JS('Object.keys') -external JSArray _objectKeys(JSObject obj); - -@JS('Reflect.get') -external JSAny? _reflectGet(JSObject target, JSAny propertyKey); - -List jsArrayToList(JSArray obj) { - return obj.toDart.map(jsAnyToDart).toList(); -} - -Map jsObjectToMap(JSObject obj) { - return { - for (final jsKey in _objectKeys(obj).toDart) - jsKey.toDart: jsAnyToDart(_reflectGet(obj, jsKey)), - }; -} - -dynamic jsAnyToDart(JSAny? value) { - if (value is JSArray) { - return jsArrayToList(value); - } else if (value is JSObject) { - return jsObjectToMap(value); - } else if (value is JSString) { - return value.toDart; - } else if (value is JSNumber) { - return value.toDartInt; - } else if (value is JSBoolean) { - return value.toDart; - } else { - return value; // JS null and undefined are null in Dart - } -} From c4206590eb717461ea35b5b46a7e882ae4a1fc55 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 26 Dec 2025 13:19:41 -0300 Subject: [PATCH 23/55] Add getClient method tests --- splitio_web/test/splitio_web_test.dart | 31 +++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/splitio_web/test/splitio_web_test.dart b/splitio_web/test/splitio_web_test.dart index 1e4c4eb..60a124f 100644 --- a/splitio_web/test/splitio_web_test.dart +++ b/splitio_web/test/splitio_web_test.dart @@ -61,7 +61,7 @@ void main() { }); group('evaluation', () { - test('getTreatment without attributes', () async { + test('getTreatment', () async { final result = await _platform.getTreatment( matchingKey: 'matching-key', bucketingKey: 'bucketing-key', @@ -401,4 +401,33 @@ void main() { })); }); }); + + group('client', () { + test('get client with no keys', () async { + await _platform.getClient( + matchingKey: 'matching-key', bucketingKey: null); + + expect(calls.last.methodName, 'client'); + expect(calls.last.methodArguments.map(jsAnyToDart), ['matching-key']); + }); + + test('get client with new matching key', () async { + await _platform.getClient( + matchingKey: 'new-matching-key', bucketingKey: null); + + expect(calls.last.methodName, 'client'); + expect(calls.last.methodArguments.map(jsAnyToDart), ['new-matching-key']); + }); + + test('get client with new matching key and bucketing key', () async { + await _platform.getClient( + matchingKey: 'new-matching-key', bucketingKey: 'bucketing-key'); + + expect(calls.last.methodName, 'client'); + expect(calls.last.methodArguments.map(jsAnyToDart), [ + {'matchingKey': 'new-matching-key', 'bucketingKey': 'bucketing-key'} + ]); + }); + }); + } From 4abc03b38753d1a1fdb81a32b0323e140a466b27 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 26 Dec 2025 13:27:38 -0300 Subject: [PATCH 24/55] Add getTreatments[WithConfig] methods --- splitio_web/lib/splitio_web.dart | 64 +++++++++++ splitio_web/lib/src/js_interop.dart | 23 ++++ splitio_web/test/splitio_web_test.dart | 148 ++++++++++++++++++++++++- 3 files changed, 234 insertions(+), 1 deletion(-) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index b7bcdf2..9bd8e8d 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -413,4 +413,68 @@ class SplitioWeb extends SplitioPlatform { _convertEvaluationOptions(evaluationOptions)) as JSString; return result.toDart; } + + @override + Future> getTreatments({ + required String matchingKey, + required String? bucketingKey, + required List splitNames, + Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty(), + }) async { + final client = await _getClient( + matchingKey: matchingKey, + bucketingKey: bucketingKey, + ); + + final result = client.getTreatments.callAsFunction( + null, + splitNames.jsify(), + _convertMap(attributes, true), + _convertEvaluationOptions(evaluationOptions)) as JSObject; + return jsTreatmentsToMap(result); + } + + @override + Future getTreatmentWithConfig({ + required String matchingKey, + required String? bucketingKey, + required String splitName, + Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty(), + }) async { + await this._initFuture; + final client = await _getClient( + matchingKey: matchingKey, + bucketingKey: bucketingKey, + ); + + final result = client.getTreatmentWithConfig.callAsFunction( + null, + splitName.toJS, + _convertMap(attributes, true), + _convertEvaluationOptions(evaluationOptions)) as JSObject; + return jsTreatmentWithConfigToSplitResult(result); + } + + @override + Future> getTreatmentsWithConfig({ + required String matchingKey, + required String? bucketingKey, + required List splitNames, + Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty(), + }) async { + final client = await _getClient( + matchingKey: matchingKey, + bucketingKey: bucketingKey, + ); + + final result = client.getTreatmentsWithConfig.callAsFunction( + null, + splitNames.jsify(), + _convertMap(attributes, true), + _convertEvaluationOptions(evaluationOptions)) as JSObject; + return jsTreatmentsWithConfigToMap(result); + } } diff --git a/splitio_web/lib/src/js_interop.dart b/splitio_web/lib/src/js_interop.dart index 94cfb58..6e0e89d 100644 --- a/splitio_web/lib/src/js_interop.dart +++ b/splitio_web/lib/src/js_interop.dart @@ -1,4 +1,5 @@ import 'dart:js_interop'; +import 'package:splitio_platform_interface/split_result.dart'; // JS SDK types @@ -15,6 +16,13 @@ extension type JS_ISettings._(JSObject _) implements JSObject { @JS() extension type JS_IBrowserClient._(JSObject _) implements JSObject { external JSFunction getTreatment; + external JSFunction getTreatments; + external JSFunction getTreatmentWithConfig; + external JSFunction getTreatmentsWithConfig; + external JSFunction getTreatmentsByFlagSet; + external JSFunction getTreatmentsByFlagSets; + external JSFunction getTreatmentWithConfigByFlagSet; + external JSFunction getTreatmentsWithConfigByFlagSets; } @JS() @@ -68,3 +76,18 @@ dynamic jsAnyToDart(JSAny? value) { return value; // JS null and undefined are null in Dart } } + +// Conversion utils: JS SDK to Flutter SDK types + +Map jsTreatmentsToMap(JSObject obj) { + return jsObjectToMap(obj).map((k, v) => MapEntry(k, v as String)); +} + +Map jsTreatmentsWithConfigToMap(JSObject obj) { + return jsObjectToMap(obj).map((k, v) => MapEntry(k, SplitResult(v['treatment'] as String, v['config'] as String?))); +} + +SplitResult jsTreatmentWithConfigToSplitResult(JSObject obj) { + final config = _reflectGet(obj, 'config'.toJS); + return SplitResult((_reflectGet(obj, 'treatment'.toJS) as JSString).toDart, (config is JSString) ? config.toDart : null); +} diff --git a/splitio_web/test/splitio_web_test.dart b/splitio_web/test/splitio_web_test.dart index 60a124f..6e1b68e 100644 --- a/splitio_web/test/splitio_web_test.dart +++ b/splitio_web/test/splitio_web_test.dart @@ -8,6 +8,7 @@ import 'package:splitio_platform_interface/split_certificate_pinning_configurati import 'package:splitio_platform_interface/split_configuration.dart'; import 'package:splitio_platform_interface/split_evaluation_options.dart'; import 'package:splitio_platform_interface/split_sync_config.dart'; +import 'package:splitio_platform_interface/split_result.dart'; import 'package:splitio_platform_interface/split_rollout_cache_configuration.dart'; extension on web.Window { @@ -27,6 +28,52 @@ void main() { )); return 'on'.toJS; }.toJS; + mockClient['getTreatments'] = + (JSAny? flagNames, JSAny? attributes, JSAny? evaluationOptions) { + calls.add(( + methodName: 'getTreatments', + methodArguments: [flagNames, attributes, evaluationOptions] + )); + if (flagNames is JSArray) { + return flagNames.toDart.fold(JSObject(), (previousValue, element) { + if (element is JSString) { + previousValue.setProperty(element, 'on'.toJS); + } + return previousValue; + }); + } + return JSObject(); + }.toJS; + mockClient['getTreatmentWithConfig'] = + (JSAny? flagName, JSAny? attributes, JSAny? evaluationOptions) { + calls.add(( + methodName: 'getTreatmentWithConfig', + methodArguments: [flagName, attributes, evaluationOptions] + )); + final result = JSObject(); + result.setProperty('treatment'.toJS, 'on'.toJS); + result.setProperty('config'.toJS, 'some-config'.toJS); + return result; + }.toJS; + mockClient['getTreatmentsWithConfig'] = + (JSAny? flagNames, JSAny? attributes, JSAny? evaluationOptions) { + calls.add(( + methodName: 'getTreatmentsWithConfig', + methodArguments: [flagNames, attributes, evaluationOptions] + )); + if (flagNames is JSArray) { + return flagNames.toDart.fold(JSObject(), (previousValue, element) { + if (element is JSString) { + final result = JSObject(); + result.setProperty('treatment'.toJS, 'on'.toJS); + result.setProperty('config'.toJS, 'some-config'.toJS); + previousValue.setProperty(element, result); + } + return previousValue; + }); + } + return JSObject(); + }.toJS; final mockLog = JSObject(); mockLog['warn'] = (JSAny? arg1) { @@ -170,6 +217,106 @@ void main() { 'Invalid property value: [value1, 100, false], for key: propList, will be ignored')); }); + test('getTreatments without attributes', () async { + final result = await _platform.getTreatments( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + splitNames: ['split1', 'split2']); + + expect(result, {'split1': 'on', 'split2': 'on'}); + expect(calls.last.methodName, 'getTreatments'); + expect(calls.last.methodArguments.map(jsAnyToDart), [ + ['split1', 'split2'], + {}, + {} + ]); + }); + + test('getTreatments with attributes and evaluation properties', () async { + final result = await _platform.getTreatments( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + splitNames: ['split1', 'split2'], + attributes: {'attr1': true}); + + expect(result, {'split1': 'on', 'split2': 'on'}); + expect(calls.last.methodName, 'getTreatments'); + expect(calls.last.methodArguments.map(jsAnyToDart), [ + ['split1', 'split2'], + {'attr1': true}, + {} + ]); + }); + + test('getTreatmentWithConfig with attributes', () async { + final result = await _platform.getTreatmentWithConfig( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + splitName: 'split1', + attributes: {'attr1': true}); + + expect(result.toString(), SplitResult('on', 'some-config').toString()); + expect(calls.last.methodName, 'getTreatmentWithConfig'); + expect(calls.last.methodArguments.map(jsAnyToDart), [ + 'split1', + {'attr1': true}, + {} + ]); + }); + + test('getTreatmentWithConfig without attributes', () async { + final result = await _platform.getTreatmentWithConfig( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + splitName: 'split1'); + + expect(result.toString(), SplitResult('on', 'some-config').toString()); + expect(calls.last.methodName, 'getTreatmentWithConfig'); + expect(calls.last.methodArguments.map(jsAnyToDart), ['split1', {}, {}]); + }); + + test('getTreatmentsWithConfig without attributes', () async { + final result = await _platform.getTreatmentsWithConfig( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + splitNames: ['split1', 'split2']); + + expect(result, predicate>((result) { + return result.length == 2 && + result['split1'].toString() == + SplitResult('on', 'some-config').toString() && + result['split2'].toString() == + SplitResult('on', 'some-config').toString(); + })); + expect(calls.last.methodName, 'getTreatmentsWithConfig'); + expect(calls.last.methodArguments.map(jsAnyToDart), [ + ['split1', 'split2'], + {}, + {} + ]); + }); + + test('getTreatmentsWithConfig with attributes', () async { + final result = await _platform.getTreatmentsWithConfig( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + splitNames: ['split1', 'split2'], + attributes: {'attr1': true}); + + expect(result, predicate>((result) { + return result.length == 2 && + result['split1'].toString() == + SplitResult('on', 'some-config').toString() && + result['split2'].toString() == + SplitResult('on', 'some-config').toString(); + })); + expect(calls.last.methodName, 'getTreatmentsWithConfig'); + expect(calls.last.methodArguments.map(jsAnyToDart), [ + ['split1', 'split2'], + {'attr1': true}, + {} + ]); + }); }); group('initialization', () { @@ -429,5 +576,4 @@ void main() { ]); }); }); - } From a0233a59114fc6f8a54ea5e57140fbb5170b889f Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 26 Dec 2025 14:27:19 -0300 Subject: [PATCH 25/55] Add getTreatments[WithConfig]ByFlagSet[s] methods --- splitio_web/lib/splitio_web.dart | 76 ++++++++++ splitio_web/lib/src/js_interop.dart | 2 +- splitio_web/test/splitio_web_test.dart | 194 +++++++++++++++++++++++++ 3 files changed, 271 insertions(+), 1 deletion(-) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index 9bd8e8d..58c1a1a 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -477,4 +477,80 @@ class SplitioWeb extends SplitioPlatform { _convertEvaluationOptions(evaluationOptions)) as JSObject; return jsTreatmentsWithConfigToMap(result); } + + Future> getTreatmentsByFlagSet( + {required String matchingKey, + required String? bucketingKey, + required String flagSet, + Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) async { + final client = await _getClient( + matchingKey: matchingKey, + bucketingKey: bucketingKey, + ); + + final result = client.getTreatmentsByFlagSet.callAsFunction( + null, + flagSet.toJS, + _convertMap(attributes, true), + _convertEvaluationOptions(evaluationOptions)) as JSObject; + return jsTreatmentsToMap(result); + } + + Future> getTreatmentsByFlagSets( + {required String matchingKey, + required String? bucketingKey, + required List flagSets, + Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) async { + final client = await _getClient( + matchingKey: matchingKey, + bucketingKey: bucketingKey, + ); + + final result = client.getTreatmentsByFlagSets.callAsFunction( + null, + flagSets.jsify(), + _convertMap(attributes, true), + _convertEvaluationOptions(evaluationOptions)) as JSObject; + return jsTreatmentsToMap(result); + } + + Future> getTreatmentsWithConfigByFlagSet( + {required String matchingKey, + required String? bucketingKey, + required String flagSet, + Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) async { + final client = await _getClient( + matchingKey: matchingKey, + bucketingKey: bucketingKey, + ); + + final result = client.getTreatmentsWithConfigByFlagSet.callAsFunction( + null, + flagSet.toJS, + _convertMap(attributes, true), + _convertEvaluationOptions(evaluationOptions)) as JSObject; + return jsTreatmentsWithConfigToMap(result); + } + + Future> getTreatmentsWithConfigByFlagSets( + {required String matchingKey, + required String? bucketingKey, + required List flagSets, + Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) async { + final client = await _getClient( + matchingKey: matchingKey, + bucketingKey: bucketingKey, + ); + + final result = client.getTreatmentsWithConfigByFlagSets.callAsFunction( + null, + flagSets.jsify(), + _convertMap(attributes, true), + _convertEvaluationOptions(evaluationOptions)) as JSObject; + return jsTreatmentsWithConfigToMap(result); + } } diff --git a/splitio_web/lib/src/js_interop.dart b/splitio_web/lib/src/js_interop.dart index 6e0e89d..8856933 100644 --- a/splitio_web/lib/src/js_interop.dart +++ b/splitio_web/lib/src/js_interop.dart @@ -21,7 +21,7 @@ extension type JS_IBrowserClient._(JSObject _) implements JSObject { external JSFunction getTreatmentsWithConfig; external JSFunction getTreatmentsByFlagSet; external JSFunction getTreatmentsByFlagSets; - external JSFunction getTreatmentWithConfigByFlagSet; + external JSFunction getTreatmentsWithConfigByFlagSet; external JSFunction getTreatmentsWithConfigByFlagSets; } diff --git a/splitio_web/test/splitio_web_test.dart b/splitio_web/test/splitio_web_test.dart index 6e1b68e..8af0a9e 100644 --- a/splitio_web/test/splitio_web_test.dart +++ b/splitio_web/test/splitio_web_test.dart @@ -74,6 +74,60 @@ void main() { } return JSObject(); }.toJS; + mockClient['getTreatmentsByFlagSet'] = + (JSAny? flagSetName, JSAny? attributes, JSAny? evaluationOptions) { + calls.add(( + methodName: 'getTreatmentsByFlagSet', + methodArguments: [flagSetName, attributes, evaluationOptions] + )); + final result = JSObject(); + result.setProperty('split1'.toJS, 'on'.toJS); + result.setProperty('split2'.toJS, 'on'.toJS); + return result; + }.toJS; + mockClient['getTreatmentsByFlagSets'] = + (JSAny? flagSetNames, JSAny? attributes, JSAny? evaluationOptions) { + calls.add(( + methodName: 'getTreatmentsByFlagSets', + methodArguments: [flagSetNames, attributes, evaluationOptions] + )); + final result = JSObject(); + result.setProperty('split1'.toJS, 'on'.toJS); + result.setProperty('split2'.toJS, 'on'.toJS); + return result; + }.toJS; + mockClient['getTreatmentsWithConfigByFlagSet'] = + (JSAny? flagSetName, JSAny? attributes, JSAny? evaluationOptions) { + calls.add(( + methodName: 'getTreatmentsWithConfigByFlagSet', + methodArguments: [flagSetName, attributes, evaluationOptions] + )); + + final treatmentWithConfig = JSObject(); + treatmentWithConfig.setProperty('treatment'.toJS, 'on'.toJS); + treatmentWithConfig.setProperty('config'.toJS, 'some-config'.toJS); + + final result = JSObject(); + result.setProperty('split1'.toJS, treatmentWithConfig); + result.setProperty('split2'.toJS, treatmentWithConfig); + return result; + }.toJS; + mockClient['getTreatmentsWithConfigByFlagSets'] = + (JSAny? flagSetNames, JSAny? attributes, JSAny? evaluationOptions) { + calls.add(( + methodName: 'getTreatmentsWithConfigByFlagSets', + methodArguments: [flagSetNames, attributes, evaluationOptions] + )); + + final treatmentWithConfig = JSObject(); + treatmentWithConfig.setProperty('treatment'.toJS, 'on'.toJS); + treatmentWithConfig.setProperty('config'.toJS, 'some-config'.toJS); + + final result = JSObject(); + result.setProperty('split1'.toJS, treatmentWithConfig); + result.setProperty('split2'.toJS, treatmentWithConfig); + return result; + }.toJS; final mockLog = JSObject(); mockLog['warn'] = (JSAny? arg1) { @@ -317,6 +371,146 @@ void main() { {} ]); }); + + test('getTreatmentsByFlagSet without attributes', () async { + final result = await _platform.getTreatmentsByFlagSet( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSet: 'set_1'); + + expect(result, {'split1': 'on', 'split2': 'on'}); + expect(calls.last.methodName, 'getTreatmentsByFlagSet'); + expect(calls.last.methodArguments.map(jsAnyToDart), ['set_1', {}, {}]); + }); + + test('getTreatmentsByFlagSet with attributes', () async { + final result = await _platform.getTreatmentsByFlagSet( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSet: 'set_1', + attributes: {'attr1': true}); + + expect(result, {'split1': 'on', 'split2': 'on'}); + expect(calls.last.methodName, 'getTreatmentsByFlagSet'); + expect(calls.last.methodArguments.map(jsAnyToDart), [ + 'set_1', + {'attr1': true}, + {} + ]); + }); + + test('getTreatmentsByFlagSets without attributes', () async { + final result = await _platform.getTreatmentsByFlagSets( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSets: ['set_1', 'set_2']); + + expect(result, {'split1': 'on', 'split2': 'on'}); + expect(calls.last.methodName, 'getTreatmentsByFlagSets'); + expect(calls.last.methodArguments.map(jsAnyToDart), [ + ['set_1', 'set_2'], + {}, + {} + ]); + }); + + test('getTreatmentsByFlagSets with attributes', () async { + final result = await _platform.getTreatmentsByFlagSets( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSets: ['set_1', 'set_2'], + attributes: {'attr1': true}); + + expect(result, {'split1': 'on', 'split2': 'on'}); + expect(calls.last.methodName, 'getTreatmentsByFlagSets'); + expect(calls.last.methodArguments.map(jsAnyToDart), [ + ['set_1', 'set_2'], + {'attr1': true}, + {} + ]); + }); + + test('getTreatmentsWithConfigByFlagSet without attributes', () async { + final result = await _platform.getTreatmentsWithConfigByFlagSet( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSet: 'set_1'); + + expect(result, predicate>((result) { + return result.length == 2 && + result['split1'].toString() == + SplitResult('on', 'some-config').toString() && + result['split2'].toString() == + SplitResult('on', 'some-config').toString(); + })); + expect(calls.last.methodName, 'getTreatmentsWithConfigByFlagSet'); + expect(calls.last.methodArguments.map(jsAnyToDart), ['set_1', {}, {}]); + }); + + test('getTreatmentsWithConfigByFlagSet with attributes', () async { + final result = await _platform.getTreatmentsWithConfigByFlagSet( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSet: 'set_1', + attributes: {'attr1': true}); + + expect(result, predicate>((result) { + return result.length == 2 && + result['split1'].toString() == + SplitResult('on', 'some-config').toString() && + result['split2'].toString() == + SplitResult('on', 'some-config').toString(); + })); + expect(calls.last.methodName, 'getTreatmentsWithConfigByFlagSet'); + expect(calls.last.methodArguments.map(jsAnyToDart), [ + 'set_1', + {'attr1': true}, + {} + ]); + }); + + test('getTreatmentsWithConfigByFlagSets without attributes', () async { + final result = await _platform.getTreatmentsWithConfigByFlagSets( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSets: ['set_1', 'set_2']); + + expect(result, predicate>((result) { + return result.length == 2 && + result['split1'].toString() == + SplitResult('on', 'some-config').toString() && + result['split2'].toString() == + SplitResult('on', 'some-config').toString(); + })); + expect(calls.last.methodName, 'getTreatmentsWithConfigByFlagSets'); + expect(calls.last.methodArguments.map(jsAnyToDart), [ + ['set_1', 'set_2'], + {}, + {} + ]); + }); + + test('getTreatmentsWithConfigByFlagSets with attributes', () async { + final result = await _platform.getTreatmentsWithConfigByFlagSets( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSets: ['set_1', 'set_2'], + attributes: {'attr1': true}); + + expect(result, predicate>((result) { + return result.length == 2 && + result['split1'].toString() == + SplitResult('on', 'some-config').toString() && + result['split2'].toString() == + SplitResult('on', 'some-config').toString(); + })); + expect(calls.last.methodName, 'getTreatmentsWithConfigByFlagSets'); + expect(calls.last.methodArguments.map(jsAnyToDart), [ + ['set_1', 'set_2'], + {'attr1': true}, + {} + ]); + }); }); group('initialization', () { From 712e97599e0fb2fab338efa7a42964d4274d986c Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 26 Dec 2025 16:23:33 -0300 Subject: [PATCH 26/55] Linting --- splitio_web/lib/splitio_web.dart | 20 ++++++++++++++++---- splitio_web/lib/src/js_interop.dart | 6 ++++-- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index 58c1a1a..d9e1aea 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -411,6 +411,7 @@ class SplitioWeb extends SplitioPlatform { splitName.toJS, _convertMap(attributes, true), _convertEvaluationOptions(evaluationOptions)) as JSString; + return result.toDart; } @@ -432,6 +433,7 @@ class SplitioWeb extends SplitioPlatform { splitNames.jsify(), _convertMap(attributes, true), _convertEvaluationOptions(evaluationOptions)) as JSObject; + return jsTreatmentsToMap(result); } @@ -454,6 +456,7 @@ class SplitioWeb extends SplitioPlatform { splitName.toJS, _convertMap(attributes, true), _convertEvaluationOptions(evaluationOptions)) as JSObject; + return jsTreatmentWithConfigToSplitResult(result); } @@ -475,6 +478,7 @@ class SplitioWeb extends SplitioPlatform { splitNames.jsify(), _convertMap(attributes, true), _convertEvaluationOptions(evaluationOptions)) as JSObject; + return jsTreatmentsWithConfigToMap(result); } @@ -483,7 +487,8 @@ class SplitioWeb extends SplitioPlatform { required String? bucketingKey, required String flagSet, Map attributes = const {}, - EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) async { + EvaluationOptions evaluationOptions = + const EvaluationOptions.empty()}) async { final client = await _getClient( matchingKey: matchingKey, bucketingKey: bucketingKey, @@ -494,6 +499,7 @@ class SplitioWeb extends SplitioPlatform { flagSet.toJS, _convertMap(attributes, true), _convertEvaluationOptions(evaluationOptions)) as JSObject; + return jsTreatmentsToMap(result); } @@ -502,7 +508,8 @@ class SplitioWeb extends SplitioPlatform { required String? bucketingKey, required List flagSets, Map attributes = const {}, - EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) async { + EvaluationOptions evaluationOptions = + const EvaluationOptions.empty()}) async { final client = await _getClient( matchingKey: matchingKey, bucketingKey: bucketingKey, @@ -513,6 +520,7 @@ class SplitioWeb extends SplitioPlatform { flagSets.jsify(), _convertMap(attributes, true), _convertEvaluationOptions(evaluationOptions)) as JSObject; + return jsTreatmentsToMap(result); } @@ -521,7 +529,8 @@ class SplitioWeb extends SplitioPlatform { required String? bucketingKey, required String flagSet, Map attributes = const {}, - EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) async { + EvaluationOptions evaluationOptions = + const EvaluationOptions.empty()}) async { final client = await _getClient( matchingKey: matchingKey, bucketingKey: bucketingKey, @@ -532,6 +541,7 @@ class SplitioWeb extends SplitioPlatform { flagSet.toJS, _convertMap(attributes, true), _convertEvaluationOptions(evaluationOptions)) as JSObject; + return jsTreatmentsWithConfigToMap(result); } @@ -540,7 +550,8 @@ class SplitioWeb extends SplitioPlatform { required String? bucketingKey, required List flagSets, Map attributes = const {}, - EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) async { + EvaluationOptions evaluationOptions = + const EvaluationOptions.empty()}) async { final client = await _getClient( matchingKey: matchingKey, bucketingKey: bucketingKey, @@ -551,6 +562,7 @@ class SplitioWeb extends SplitioPlatform { flagSets.jsify(), _convertMap(attributes, true), _convertEvaluationOptions(evaluationOptions)) as JSObject; + return jsTreatmentsWithConfigToMap(result); } } diff --git a/splitio_web/lib/src/js_interop.dart b/splitio_web/lib/src/js_interop.dart index 8856933..6ec78e3 100644 --- a/splitio_web/lib/src/js_interop.dart +++ b/splitio_web/lib/src/js_interop.dart @@ -84,10 +84,12 @@ Map jsTreatmentsToMap(JSObject obj) { } Map jsTreatmentsWithConfigToMap(JSObject obj) { - return jsObjectToMap(obj).map((k, v) => MapEntry(k, SplitResult(v['treatment'] as String, v['config'] as String?))); + return jsObjectToMap(obj).map((k, v) => MapEntry( + k, SplitResult(v['treatment'] as String, v['config'] as String?))); } SplitResult jsTreatmentWithConfigToSplitResult(JSObject obj) { final config = _reflectGet(obj, 'config'.toJS); - return SplitResult((_reflectGet(obj, 'treatment'.toJS) as JSString).toDart, (config is JSString) ? config.toDart : null); + return SplitResult((_reflectGet(obj, 'treatment'.toJS) as JSString).toDart, + (config is JSString) ? config.toDart : null); } From 17420a85d2ef673ec802cab507f771b55b155e46 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 26 Dec 2025 16:40:02 -0300 Subject: [PATCH 27/55] Connect client track method --- splitio_web/lib/splitio_web.dart | 26 +++++++++ splitio_web/lib/src/js_interop.dart | 1 + splitio_web/test/splitio_web_test.dart | 76 ++++++++++++++++++++++++++ 3 files changed, 103 insertions(+) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index d9e1aea..2fe20f8 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -565,4 +565,30 @@ class SplitioWeb extends SplitioPlatform { return jsTreatmentsWithConfigToMap(result); } + + Future track( + {required String matchingKey, + required String? bucketingKey, + required String eventType, + String? trafficType, + double? value, + Map properties = const {}}) async { + final client = await _getClient( + matchingKey: matchingKey, + bucketingKey: bucketingKey, + ); + + final result = client.track.callAsFunction( + null, + trafficType != null + ? trafficType.toJS + : this._trafficType != null + ? this._trafficType!.toJS + : null, + eventType.toJS, + value != null ? value.toJS : null, + _convertMap(properties, false)) as JSBoolean; + + return result.toDart; + } } diff --git a/splitio_web/lib/src/js_interop.dart b/splitio_web/lib/src/js_interop.dart index 6ec78e3..a8838b4 100644 --- a/splitio_web/lib/src/js_interop.dart +++ b/splitio_web/lib/src/js_interop.dart @@ -23,6 +23,7 @@ extension type JS_IBrowserClient._(JSObject _) implements JSObject { external JSFunction getTreatmentsByFlagSets; external JSFunction getTreatmentsWithConfigByFlagSet; external JSFunction getTreatmentsWithConfigByFlagSets; + external JSFunction track; } @JS() diff --git a/splitio_web/test/splitio_web_test.dart b/splitio_web/test/splitio_web_test.dart index 8af0a9e..20ab0e1 100644 --- a/splitio_web/test/splitio_web_test.dart +++ b/splitio_web/test/splitio_web_test.dart @@ -128,6 +128,14 @@ void main() { result.setProperty('split2'.toJS, treatmentWithConfig); return result; }.toJS; + mockClient['track'] = + (JSAny? trafficType, JSAny? eventType, JSAny? value, JSAny? properties) { + calls.add(( + methodName: 'track', + methodArguments: [trafficType, eventType, value, properties] + )); + return trafficType != null ? true.toJS : false.toJS; + }.toJS; final mockLog = JSObject(); mockLog['warn'] = (JSAny? arg1) { @@ -513,6 +521,74 @@ void main() { }); }); + group('track', () { + test('track with traffic type, value & properties', () async { + final result = await _platform.track( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + eventType: 'my_event', + trafficType: 'my_traffic_type', + value: 25.10, + properties: { + 'propBool': true, + 'propString': 'value', + 'propInt': 1, + 'propDouble': 1.1, + 'propList': ['value1', 100, false], // not valid property value + 'propSet': {'value3', 100, true}, // not valid property value + 'propNull': null, // not valid property value + 'propMap': {'value5': true} // not valid property value + }); + + expect(result, true); + expect(calls.last.methodName, 'track'); + expect(calls.last.methodArguments.map(jsAnyToDart), [ + 'my_traffic_type', + 'my_event', + 25.10, + { + 'propBool': true, + 'propString': 'value', + 'propInt': 1, + 'propDouble': 1.1, + } + ]); + }); + + test('track with value, and no traffic type in config', () async { + final result = await _platform.track( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + eventType: 'my_event', + value: 25.20); + + expect(result, false); // false because no traffic type is provided + expect(calls.last.methodName, 'track'); + expect(calls.last.methodArguments.map(jsAnyToDart), + [null, 'my_event', 25.20, {}]); + }); + + test('track without value, and traffic type in config', () async { + SplitioWeb _platform = SplitioWeb(); + await _platform.init( + apiKey: 'api-key', + matchingKey: 'matching-key', + bucketingKey: null, + sdkConfiguration: + SplitConfiguration(trafficType: 'my_traffic_type_in_config')); + + final result = await _platform.track( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + eventType: 'my_event'); + + expect(result, true); + expect(calls.last.methodName, 'track'); + expect(calls.last.methodArguments.map(jsAnyToDart), + ['my_traffic_type_in_config', 'my_event', null, {}]); + }); + }); + group('initialization', () { test('init with matching key only', () async { SplitioWeb _platform = SplitioWeb(); From ec4be9bb84c029ca3b54d3ae266f3307a54a033f Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 26 Dec 2025 17:07:47 -0300 Subject: [PATCH 28/55] Add @override annotations --- splitio_web/lib/splitio_web.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index d9e1aea..764d5d3 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -482,6 +482,7 @@ class SplitioWeb extends SplitioPlatform { return jsTreatmentsWithConfigToMap(result); } + @override Future> getTreatmentsByFlagSet( {required String matchingKey, required String? bucketingKey, @@ -503,6 +504,7 @@ class SplitioWeb extends SplitioPlatform { return jsTreatmentsToMap(result); } + @override Future> getTreatmentsByFlagSets( {required String matchingKey, required String? bucketingKey, @@ -524,6 +526,7 @@ class SplitioWeb extends SplitioPlatform { return jsTreatmentsToMap(result); } + @override Future> getTreatmentsWithConfigByFlagSet( {required String matchingKey, required String? bucketingKey, @@ -545,6 +548,7 @@ class SplitioWeb extends SplitioPlatform { return jsTreatmentsWithConfigToMap(result); } + @override Future> getTreatmentsWithConfigByFlagSets( {required String matchingKey, required String? bucketingKey, From 8190a2268f8080dd4a57e70a81ac895d7bc8927d Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Mon, 29 Dec 2025 17:20:27 -0300 Subject: [PATCH 29/55] Rename isAttributes parameter to isAttribute for clarity --- splitio_web/lib/splitio_web.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index 5e00f5c..dda1bea 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -349,13 +349,13 @@ class SplitioWeb extends SplitioPlatform { return _clients[key]!; } - JSAny? _convertValue(dynamic value, bool isAttributes) { + JSAny? _convertValue(dynamic value, bool isAttribute) { if (value is bool) return value.toJS; if (value is num) return value.toJS; // covers int + double if (value is String) return value.toJS; // properties do not support lists and sets - if (isAttributes) { + if (isAttribute) { if (value is List) return value.jsify(); if (value is Set) return value.jsify(); } @@ -363,18 +363,18 @@ class SplitioWeb extends SplitioPlatform { return null; } - JSObject _convertMap(Map dartMap, bool isAttributes) { + JSObject _convertMap(Map dartMap, bool isAttribute) { final jsMap = JSObject(); dartMap.forEach((key, value) { - final jsValue = _convertValue(value, isAttributes); + final jsValue = _convertValue(value, isAttribute); if (jsValue != null) { jsMap.setProperty(key.toJS, jsValue); } else { this._factory.settings.log.warn.callAsFunction( null, - 'Invalid ${isAttributes ? 'attribute' : 'property'} value: $value, for key: $key, will be ignored' + 'Invalid ${isAttribute ? 'attribute' : 'property'} value: $value, for key: $key, will be ignored' .toJS); } }); From cc384d91a77e04d3c0de5fa30b8f71690d16a852 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Mon, 29 Dec 2025 17:22:28 -0300 Subject: [PATCH 30/55] Add client attribute, flush and destroy methods --- splitio_web/lib/splitio_web.dart | 116 ++++++++++++++++ splitio_web/lib/src/js_interop.dart | 8 ++ splitio_web/test/splitio_web_test.dart | 124 +++++++++++++++++- .../test/utils/js_interop_test_utils.dart | 45 +++++++ 4 files changed, 286 insertions(+), 7 deletions(-) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index dda1bea..0619ba7 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -596,4 +596,120 @@ class SplitioWeb extends SplitioPlatform { return result.toDart; } + + @override + Future> getAllAttributes( + {required String matchingKey, required String? bucketingKey}) async { + final client = await _getClient( + matchingKey: matchingKey, + bucketingKey: bucketingKey, + ); + + final result = client.getAttributes.callAsFunction(null) as JSObject; + + return jsObjectToMap(result); + } + + @override + Future setAttribute( + {required String matchingKey, + required String? bucketingKey, + required String attributeName, + required dynamic value}) async { + final client = await _getClient( + matchingKey: matchingKey, + bucketingKey: bucketingKey, + ); + + final result = client.setAttribute.callAsFunction( + null, attributeName.toJS, _convertValue(value, true)) as JSBoolean; + + return result.toDart; + } + + @override + Future setAttributes( + {required String matchingKey, + required String? bucketingKey, + required Map attributes}) async { + final client = await _getClient( + matchingKey: matchingKey, + bucketingKey: bucketingKey, + ); + + final result = client.setAttributes + .callAsFunction(null, _convertMap(attributes, true)) as JSBoolean; + + return result.toDart; + } + + @override + Future getAttribute( + {required String matchingKey, + required String? bucketingKey, + required String attributeName}) async { + final client = await _getClient( + matchingKey: matchingKey, + bucketingKey: bucketingKey, + ); + + final result = client.getAttribute.callAsFunction(null, attributeName.toJS); + + return jsAnyToDart(result); + } + + @override + Future removeAttribute( + {required String matchingKey, + required String? bucketingKey, + required String attributeName}) async { + final client = await _getClient( + matchingKey: matchingKey, + bucketingKey: bucketingKey, + ); + + final result = client.removeAttribute + .callAsFunction(null, attributeName.toJS) as JSBoolean; + + return result.toDart; + } + + @override + Future clearAttributes( + {required String matchingKey, required String? bucketingKey}) async { + final client = await _getClient( + matchingKey: matchingKey, + bucketingKey: bucketingKey, + ); + + final result = client.clearAttributes.callAsFunction(null) as JSBoolean; + + return result.toDart; + } + + @override + Future flush( + {required String matchingKey, required String? bucketingKey}) async { + final client = await _getClient( + matchingKey: matchingKey, + bucketingKey: bucketingKey, + ); + + final result = client.flush.callAsFunction(null) as JSPromise; + + return result.toDart; + } + + @override + Future destroy( + {required String matchingKey, required String? bucketingKey}) async { + final client = await _getClient( + matchingKey: matchingKey, + bucketingKey: bucketingKey, + ); + + final result = client.destroy.callAsFunction(null) as JSPromise; + + return result.toDart; + } } diff --git a/splitio_web/lib/src/js_interop.dart b/splitio_web/lib/src/js_interop.dart index a8838b4..a0e3426 100644 --- a/splitio_web/lib/src/js_interop.dart +++ b/splitio_web/lib/src/js_interop.dart @@ -24,6 +24,14 @@ extension type JS_IBrowserClient._(JSObject _) implements JSObject { external JSFunction getTreatmentsWithConfigByFlagSet; external JSFunction getTreatmentsWithConfigByFlagSets; external JSFunction track; + external JSFunction setAttribute; + external JSFunction getAttribute; + external JSFunction removeAttribute; + external JSFunction setAttributes; + external JSFunction getAttributes; + external JSFunction clearAttributes; + external JSFunction flush; + external JSFunction destroy; } @JS() diff --git a/splitio_web/test/splitio_web_test.dart b/splitio_web/test/splitio_web_test.dart index df48f8a..77453e4 100644 --- a/splitio_web/test/splitio_web_test.dart +++ b/splitio_web/test/splitio_web_test.dart @@ -18,7 +18,6 @@ extension on web.Window { } void main() { - SplitioWeb _platform = SplitioWeb(); final mock = SplitioMock(); @@ -40,7 +39,8 @@ void main() { expect(result, 'on'); expect(mock.calls.last.methodName, 'getTreatment'); - expect(mock.calls.last.methodArguments.map(jsAnyToDart), ['split', {}, {}]); + expect( + mock.calls.last.methodArguments.map(jsAnyToDart), ['split', {}, {}]); }); test('getTreatment with attributes', () async { @@ -196,7 +196,8 @@ void main() { expect(result.toString(), SplitResult('on', 'some-config').toString()); expect(mock.calls.last.methodName, 'getTreatmentWithConfig'); - expect(mock.calls.last.methodArguments.map(jsAnyToDart), ['split1', {}, {}]); + expect( + mock.calls.last.methodArguments.map(jsAnyToDart), ['split1', {}, {}]); }); test('getTreatmentsWithConfig without attributes', () async { @@ -250,7 +251,8 @@ void main() { expect(result, {'split1': 'on', 'split2': 'on'}); expect(mock.calls.last.methodName, 'getTreatmentsByFlagSet'); - expect(mock.calls.last.methodArguments.map(jsAnyToDart), ['set_1', {}, {}]); + expect( + mock.calls.last.methodArguments.map(jsAnyToDart), ['set_1', {}, {}]); }); test('getTreatmentsByFlagSet with attributes', () async { @@ -314,7 +316,8 @@ void main() { SplitResult('on', 'some-config').toString(); })); expect(mock.calls.last.methodName, 'getTreatmentsWithConfigByFlagSet'); - expect(mock.calls.last.methodArguments.map(jsAnyToDart), ['set_1', {}, {}]); + expect( + mock.calls.last.methodArguments.map(jsAnyToDart), ['set_1', {}, {}]); }); test('getTreatmentsWithConfigByFlagSet with attributes', () async { @@ -451,6 +454,111 @@ void main() { }); }); + group('other client methods: attributes, destroy, flush', () { + test('get single attribute', () async { + final result = await _platform.getAttribute( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + attributeName: 'attribute-name'); + + expect(result, 'attr-value'); + expect(mock.calls.last.methodName, 'getAttribute'); + expect( + mock.calls.last.methodArguments.map(jsAnyToDart), ['attribute-name']); + }); + + test('get all attributes', () async { + final result = await _platform.getAllAttributes( + matchingKey: 'matching-key', bucketingKey: 'bucketing-key'); + + expect( + result, + equals({ + 'attrBool': true, + 'attrString': 'value', + 'attrInt': 1, + 'attrDouble': 1.1, + 'attrList': ['value1', 100, false], + })); + expect(mock.calls.last.methodName, 'getAttributes'); + expect(mock.calls.last.methodArguments, []); + }); + + test('set attribute', () async { + final result = await _platform.setAttribute( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + attributeName: 'my_attr', + value: 'attr_value'); + + expect(result, true); + expect(mock.calls.last.methodName, 'setAttribute'); + expect(mock.calls.last.methodArguments.map(jsAnyToDart), + ['my_attr', 'attr_value']); + }); + + test('set multiple attributes', () async { + final result = await _platform.setAttributes( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + attributes: { + 'bool_attr': true, + 'number_attr': 25.56, + 'string_attr': 'attr-value', + 'list_attr': ['one', true], + 'attrNull': null, // not valid. ignored + 'attrMap': {'value5': true} // not valid. ignored + }); + + expect(result, true); + expect(mock.calls.last.methodName, 'setAttributes'); + expect(mock.calls.last.methodArguments.map(jsAnyToDart), [ + { + 'bool_attr': true, + 'number_attr': 25.56, + 'string_attr': 'attr-value', + 'list_attr': ['one', true], + } + ]); + }); + + test('remove attribute', () async { + final result = await _platform.removeAttribute( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + attributeName: 'attr-name'); + + expect(result, true); + expect(mock.calls.last.methodName, 'removeAttribute'); + expect(mock.calls.last.methodArguments.map(jsAnyToDart), ['attr-name']); + }); + + test('clear attributes', () async { + final result = await _platform.clearAttributes( + matchingKey: 'matching-key', bucketingKey: 'bucketing-key'); + + expect(result, true); + expect(mock.calls.last.methodName, 'clearAttributes'); + expect(mock.calls.last.methodArguments, []); + }); + + test('flush', () async { + await _platform.flush( + matchingKey: 'matching-key', bucketingKey: 'bucketing-key'); + + expect(mock.calls.last.methodName, 'flush'); + expect(mock.calls.last.methodArguments, []); + }); + + test('destroy', () async { + await _platform.destroy( + matchingKey: 'matching-key', bucketingKey: 'bucketing-key'); + + expect(mock.calls.last.methodName, 'destroy'); + expect(mock.calls.last.methodArguments, []); + }); + }); + group('initialization', () { test('init with matching key only', () async { SplitioWeb _platform = SplitioWeb(); @@ -687,7 +795,8 @@ void main() { matchingKey: 'matching-key', bucketingKey: null); expect(mock.calls.last.methodName, 'client'); - expect(mock.calls.last.methodArguments.map(jsAnyToDart), ['matching-key']); + expect( + mock.calls.last.methodArguments.map(jsAnyToDart), ['matching-key']); }); test('get client with new matching key', () async { @@ -695,7 +804,8 @@ void main() { matchingKey: 'new-matching-key', bucketingKey: null); expect(mock.calls.last.methodName, 'client'); - expect(mock.calls.last.methodArguments.map(jsAnyToDart), ['new-matching-key']); + expect(mock.calls.last.methodArguments.map(jsAnyToDart), + ['new-matching-key']); }); test('get client with new matching key and bucketing key', () async { diff --git a/splitio_web/test/utils/js_interop_test_utils.dart b/splitio_web/test/utils/js_interop_test_utils.dart index cd90c15..cfb1fcd 100644 --- a/splitio_web/test/utils/js_interop_test_utils.dart +++ b/splitio_web/test/utils/js_interop_test_utils.dart @@ -1,6 +1,9 @@ import 'dart:js_interop'; import 'dart:js_interop_unsafe'; +@JS('Promise.resolve') +external JSPromise _promiseResolve(); + class SplitioMock { final List<({String methodName, List methodArguments})> calls = []; final JSObject splitio = JSObject(); @@ -123,6 +126,48 @@ class SplitioMock { )); return trafficType != null ? true.toJS : false.toJS; }.toJS; + mockClient['setAttribute'] = (JSAny? attributeName, JSAny? attributeValue) { + calls.add(( + methodName: 'setAttribute', + methodArguments: [attributeName, attributeValue] + )); + return true.toJS; + }.toJS; + mockClient['getAttribute'] = (JSAny? attributeName) { + calls.add((methodName: 'getAttribute', methodArguments: [attributeName])); + return 'attr-value'.toJS; + }.toJS; + mockClient['removeAttribute'] = (JSAny? attributeName) { + calls.add( + (methodName: 'removeAttribute', methodArguments: [attributeName])); + return true.toJS; + }.toJS; + mockClient['setAttributes'] = (JSAny? attributes) { + calls.add((methodName: 'setAttributes', methodArguments: [attributes])); + return true.toJS; + }.toJS; + mockClient['getAttributes'] = () { + calls.add((methodName: 'getAttributes', methodArguments: [])); + return { + 'attrBool': true, + 'attrString': 'value', + 'attrInt': 1, + 'attrDouble': 1.1, + 'attrList': ['value1', 100, false], + }.jsify(); + }.toJS; + mockClient['clearAttributes'] = () { + calls.add((methodName: 'clearAttributes', methodArguments: [])); + return true.toJS; + }.toJS; + mockClient['flush'] = () { + calls.add((methodName: 'flush', methodArguments: [])); + return _promiseResolve(); + }.toJS; + mockClient['destroy'] = () { + calls.add((methodName: 'destroy', methodArguments: [])); + return _promiseResolve(); + }.toJS; final mockLog = JSObject(); mockLog['warn'] = (JSAny? arg1) { From 4b73644babd22a7993cf7faedb60e25503efd34b Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 2 Jan 2026 14:00:20 -0300 Subject: [PATCH 31/55] Connect user consent methods --- splitio_web/lib/splitio_web.dart | 24 +++++++++++++ splitio_web/lib/src/js_interop.dart | 9 ++++- splitio_web/test/splitio_web_test.dart | 35 ++++++++++++++++--- .../test/utils/js_interop_test_utils.dart | 13 +++++++ 4 files changed, 76 insertions(+), 5 deletions(-) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index 0619ba7..128691f 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -712,4 +712,28 @@ class SplitioWeb extends SplitioPlatform { return result.toDart; } + + @override + Future getUserConsent() async { + await this._initFuture; + + final userConsentStatus = + _factory.UserConsent.getStatus.callAsFunction(null) as JSString; + + switch (userConsentStatus.toDart) { + case 'GRANTED': + return UserConsent.granted; + case 'DECLINED': + return UserConsent.declined; + default: + return UserConsent.unknown; + } + } + + @override + Future setUserConsent(bool enabled) async { + await this._initFuture; + + _factory.UserConsent.setStatus.callAsFunction(null, enabled.toJS); + } } diff --git a/splitio_web/lib/src/js_interop.dart b/splitio_web/lib/src/js_interop.dart index a0e3426..c8b9aa8 100644 --- a/splitio_web/lib/src/js_interop.dart +++ b/splitio_web/lib/src/js_interop.dart @@ -1,5 +1,5 @@ import 'dart:js_interop'; -import 'package:splitio_platform_interface/split_result.dart'; +import 'package:splitio_platform_interface/splitio_platform_interface.dart'; // JS SDK types @@ -13,6 +13,12 @@ extension type JS_ISettings._(JSObject _) implements JSObject { external JS_Logger log; } +@JS() +extension type JS_IUserConsentAPI._(JSObject _) implements JSObject { + external JSFunction setStatus; + external JSFunction getStatus; +} + @JS() extension type JS_IBrowserClient._(JSObject _) implements JSObject { external JSFunction getTreatment; @@ -38,6 +44,7 @@ extension type JS_IBrowserClient._(JSObject _) implements JSObject { extension type JS_IBrowserSDK._(JSObject _) implements JSObject { external JSFunction client; external JS_ISettings settings; + external JS_IUserConsentAPI UserConsent; } @JS() diff --git a/splitio_web/test/splitio_web_test.dart b/splitio_web/test/splitio_web_test.dart index 77453e4..ccd7f04 100644 --- a/splitio_web/test/splitio_web_test.dart +++ b/splitio_web/test/splitio_web_test.dart @@ -481,7 +481,6 @@ void main() { 'attrList': ['value1', 100, false], })); expect(mock.calls.last.methodName, 'getAttributes'); - expect(mock.calls.last.methodArguments, []); }); test('set attribute', () async { @@ -539,7 +538,6 @@ void main() { expect(result, true); expect(mock.calls.last.methodName, 'clearAttributes'); - expect(mock.calls.last.methodArguments, []); }); test('flush', () async { @@ -547,7 +545,6 @@ void main() { matchingKey: 'matching-key', bucketingKey: 'bucketing-key'); expect(mock.calls.last.methodName, 'flush'); - expect(mock.calls.last.methodArguments, []); }); test('destroy', () async { @@ -555,7 +552,6 @@ void main() { matchingKey: 'matching-key', bucketingKey: 'bucketing-key'); expect(mock.calls.last.methodName, 'destroy'); - expect(mock.calls.last.methodArguments, []); }); }); @@ -818,4 +814,35 @@ void main() { ]); }); }); + + group('userConsent', () { + test('get user consent', () async { + UserConsent userConsent = await _platform.getUserConsent(); + + expect(userConsent, UserConsent.unknown); + expect(mock.calls.last.methodName, 'getStatus'); + }); + + test('set user consent enabled', () async { + await _platform.setUserConsent(true); + + expect(mock.calls.last.methodName, 'setStatus'); + expect(mock.calls.last.methodArguments.map(jsAnyToDart), [true]); + + UserConsent userConsent = await _platform.getUserConsent(); + + expect(userConsent, UserConsent.granted); + }); + + test('set user consent disabled', () async { + await _platform.setUserConsent(false); + + expect(mock.calls.last.methodName, 'setStatus'); + expect(mock.calls.last.methodArguments.map(jsAnyToDart), [false]); + + UserConsent userConsent = await _platform.getUserConsent(); + + expect(userConsent, UserConsent.declined); + }); + }); } diff --git a/splitio_web/test/utils/js_interop_test_utils.dart b/splitio_web/test/utils/js_interop_test_utils.dart index cfb1fcd..07e7ede 100644 --- a/splitio_web/test/utils/js_interop_test_utils.dart +++ b/splitio_web/test/utils/js_interop_test_utils.dart @@ -7,6 +7,7 @@ external JSPromise _promiseResolve(); class SplitioMock { final List<({String methodName, List methodArguments})> calls = []; final JSObject splitio = JSObject(); + JSString _userConsent = 'UNKNOWN'.toJS; SplitioMock() { final mockClient = JSObject(); @@ -174,6 +175,17 @@ class SplitioMock { calls.add((methodName: 'warn', methodArguments: [arg1])); }.toJS; + final mockUserConsent = JSObject(); + mockUserConsent['setStatus'] = (JSBoolean arg1) { + _userConsent = arg1.toDart ? 'GRANTED'.toJS : 'DECLINED'.toJS; + calls.add((methodName: 'setStatus', methodArguments: [arg1])); + return true.toJS; + }.toJS; + mockUserConsent['getStatus'] = () { + calls.add((methodName: 'getStatus', methodArguments: [])); + return _userConsent; + }.toJS; + final mockSettings = JSObject(); mockSettings['log'] = mockLog; @@ -183,6 +195,7 @@ class SplitioMock { calls.add((methodName: 'client', methodArguments: [splitKey])); return mockClient; }.toJS; + mockFactory['UserConsent'] = mockUserConsent; splitio['SplitFactory'] = (JSAny? arg1) { calls.add((methodName: 'SplitFactory', methodArguments: [arg1])); From d916abe6b2403aa0cb063f8a32afedd87c2dd619 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 2 Jan 2026 14:19:23 -0300 Subject: [PATCH 32/55] Connect manager methods (split, splits, splitNames) --- splitio_web/lib/splitio_web.dart | 34 +++++++++ splitio_web/lib/src/js_interop.dart | 36 ++++++++++ splitio_web/test/splitio_web_test.dart | 71 +++++++++++++++++++ .../test/utils/js_interop_test_utils.dart | 45 ++++++++++++ 4 files changed, 186 insertions(+) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index 128691f..d0a4850 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -349,6 +349,12 @@ class SplitioWeb extends SplitioPlatform { return _clients[key]!; } + Future _getManager() async { + await this._initFuture; + + return _factory.manager.callAsFunction(null) as JS_IBrowserManager; + } + JSAny? _convertValue(dynamic value, bool isAttribute) { if (value is bool) return value.toJS; if (value is num) return value.toJS; // covers int + double @@ -713,6 +719,34 @@ class SplitioWeb extends SplitioPlatform { return result.toDart; } + @override + Future split({required String splitName}) async { + final manager = await _getManager(); + + final result = + manager.split.callAsFunction(null, splitName.toJS) as JSObject?; + + return result != null ? jsObjectToSplitView(result) : null; + } + + @override + Future> splits() async { + final manager = await _getManager(); + + final result = manager.splits.callAsFunction(null) as JSArray; + + return result.toDart.map(jsObjectToSplitView).toList(); + } + + @override + Future> splitNames() async { + final manager = await _getManager(); + + final result = manager.names.callAsFunction(null) as JSArray; + + return jsArrayToList(result).cast(); + } + @override Future getUserConsent() async { await this._initFuture; diff --git a/splitio_web/lib/src/js_interop.dart b/splitio_web/lib/src/js_interop.dart index c8b9aa8..27cdc7a 100644 --- a/splitio_web/lib/src/js_interop.dart +++ b/splitio_web/lib/src/js_interop.dart @@ -40,9 +40,17 @@ extension type JS_IBrowserClient._(JSObject _) implements JSObject { external JSFunction destroy; } +@JS() +extension type JS_IBrowserManager._(JSObject _) implements JSObject { + external JSFunction names; + external JSFunction split; + external JSFunction splits; +} + @JS() extension type JS_IBrowserSDK._(JSObject _) implements JSObject { external JSFunction client; + external JSFunction manager; external JS_ISettings settings; external JS_IUserConsentAPI UserConsent; } @@ -109,3 +117,31 @@ SplitResult jsTreatmentWithConfigToSplitResult(JSObject obj) { return SplitResult((_reflectGet(obj, 'treatment'.toJS) as JSString).toDart, (config is JSString) ? config.toDart : null); } + +Prerequisite jsObjectToPrerequisite(JSObject obj) { + return Prerequisite( + (_reflectGet(obj, 'flagName'.toJS) as JSString).toDart, + jsArrayToList(_reflectGet(obj, 'treatments'.toJS) as JSArray) + .toSet().cast(), + ); +} + +SplitView jsObjectToSplitView(JSObject obj) { + return SplitView( + (_reflectGet(obj, 'name'.toJS) as JSString).toDart, + (_reflectGet(obj, 'trafficType'.toJS) as JSString).toDart, + (_reflectGet(obj, 'killed'.toJS) as JSBoolean).toDart, + jsArrayToList(_reflectGet(obj, 'treatments'.toJS) as JSArray) + .cast(), + (_reflectGet(obj, 'changeNumber'.toJS) as JSNumber).toDartInt, + jsObjectToMap(_reflectGet(obj, 'configs'.toJS) as JSObject) + .cast(), + (_reflectGet(obj, 'defaultTreatment'.toJS) as JSString).toDart, + jsArrayToList(_reflectGet(obj, 'sets'.toJS) as JSArray) + .cast(), + (_reflectGet(obj, 'impressionsDisabled'.toJS) as JSBoolean).toDart, + (_reflectGet(obj, 'prerequisites'.toJS) as JSArray) + .toDart + .map(jsObjectToPrerequisite) + .toSet()); +} diff --git a/splitio_web/test/splitio_web_test.dart b/splitio_web/test/splitio_web_test.dart index ccd7f04..5d53bac 100644 --- a/splitio_web/test/splitio_web_test.dart +++ b/splitio_web/test/splitio_web_test.dart @@ -1,5 +1,6 @@ import 'dart:js_interop'; import 'dart:js_interop_unsafe'; +import 'package:splitio_platform_interface/splitio_platform_interface.dart'; import 'package:web/web.dart' as web; import 'package:flutter_test/flutter_test.dart'; import 'package:splitio_web/splitio_web.dart'; @@ -815,6 +816,76 @@ void main() { }); }); + group('manager', () { + test('get split names', () async { + final names = await _platform.splitNames(); + + expect(names, ['split1', 'split2']); + expect(mock.calls.last.methodName, 'names'); + }); + + test('get splits', () async { + final splits = await _platform.splits(); + + expect(splits.map((splitView) => splitView.toString()), [ + SplitView( + 'split1', + 'user', + false, + ['on', 'off'], + 1478881219393, + {'on': '"color": "green"'}, + 'off', + ['set_a'], + false, + { + Prerequisite('some_flag', {'on'}) + }).toString(), + SplitView( + 'split2', + 'user', + false, + ['on', 'off'], + 1478881219393, + {'on': '"color": "green"'}, + 'off', + ['set_a'], + false, + { + Prerequisite('some_flag', {'on'}) + }).toString(), + ]); + expect(mock.calls.last.methodName, 'splits'); + }); + + test('get split', () async { + SplitView? split = await _platform.split(splitName: 'inexistent_split'); + + expect(split, null); + + split = await _platform.split(splitName: 'split1'); + + expect( + split.toString(), + SplitView( + 'split1', + 'user', + false, + ['on', 'off'], + 1478881219393, + {'on': '"color": "green"'}, + 'off', + ['set_a'], + false, + { + Prerequisite('some_flag', {'on'}) + }).toString(), + ); + expect(mock.calls.last.methodName, 'split'); + expect(mock.calls.last.methodArguments.map(jsAnyToDart), ['split1']); + }); + }); + group('userConsent', () { test('get user consent', () async { UserConsent userConsent = await _platform.getUserConsent(); diff --git a/splitio_web/test/utils/js_interop_test_utils.dart b/splitio_web/test/utils/js_interop_test_utils.dart index 07e7ede..10252d9 100644 --- a/splitio_web/test/utils/js_interop_test_utils.dart +++ b/splitio_web/test/utils/js_interop_test_utils.dart @@ -9,7 +9,48 @@ class SplitioMock { final JSObject splitio = JSObject(); JSString _userConsent = 'UNKNOWN'.toJS; + JSObject _createSplitViewJSObject(JSString splitName) { + return { + "name": splitName.toDart, + "trafficType": "user", + "killed": false, + "treatments": ["on", "off"], + "changeNumber": 1478881219393, + "configs": {"on": "\"color\": \"green\""}, + "defaultTreatment": "off", + "sets": ["set_a"], + "impressionsDisabled": false, + "prerequisites": [ + { + "flagName": "some_flag", + "treatments": ["on"] + } + ] + }.jsify() as JSObject; + } + SplitioMock() { + final mockManager = JSObject(); + mockManager['split'] = (JSString splitName) { + calls.add((methodName: 'split', methodArguments: [splitName])); + + if (splitName.toDart == 'inexistent_split') { + return null; + } + return _createSplitViewJSObject(splitName); + }.toJS; + mockManager['splits'] = () { + calls.add((methodName: 'splits', methodArguments: [])); + return [ + _createSplitViewJSObject('split1'.toJS), + _createSplitViewJSObject('split2'.toJS), + ].jsify(); + }.toJS; + mockManager['names'] = () { + calls.add((methodName: 'names', methodArguments: [])); + return ['split1'.toJS, 'split2'.toJS].jsify(); + }.toJS; + final mockClient = JSObject(); mockClient['getTreatment'] = (JSAny? flagName, JSAny? attributes, JSAny? evaluationOptions) { @@ -195,6 +236,10 @@ class SplitioMock { calls.add((methodName: 'client', methodArguments: [splitKey])); return mockClient; }.toJS; + mockFactory['manager'] = () { + calls.add((methodName: 'manager', methodArguments: [])); + return mockManager; + }.toJS; mockFactory['UserConsent'] = mockUserConsent; splitio['SplitFactory'] = (JSAny? arg1) { From c206b97465b4d691e7ffddd4edc3597795c82d9c Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 2 Jan 2026 16:37:28 -0300 Subject: [PATCH 33/55] Connect event methods (onReady, onReadyFromCache, onTimeout, onUpdated) --- splitio_web/lib/splitio_web.dart | 125 ++++++++++++++++-- splitio_web/lib/src/js_interop.dart | 33 ++++- splitio_web/test/splitio_web_test.dart | 65 +++++++++ .../test/utils/js_interop_test_utils.dart | 50 ++++++- 4 files changed, 258 insertions(+), 15 deletions(-) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index d0a4850..863da4f 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -129,7 +129,7 @@ class SplitioWeb extends SplitioPlatform { final core = JSObject(); core.setProperty('authorizationKey'.toJS, apiKey.toJS); - core.setProperty('key'.toJS, _buildKey(matchingKey, bucketingKey)); + core.setProperty('key'.toJS, buildJsKey(matchingKey, bucketingKey)); config.setProperty('core'.toJS, core); if (configuration != null) { @@ -305,16 +305,6 @@ class SplitioWeb extends SplitioPlatform { return config; } - static JSAny _buildKey(String matchingKey, String? bucketingKey) { - if (bucketingKey != null) { - final splitKey = JSObject(); - splitKey.setProperty('matchingKey'.toJS, matchingKey.toJS); - splitKey.setProperty('bucketingKey'.toJS, bucketingKey.toJS); - return splitKey; - } - return matchingKey.toJS; - } - static String _buildKeyString(String matchingKey, String? bucketingKey) { return bucketingKey == null ? matchingKey : '${matchingKey}_$bucketingKey'; } @@ -333,7 +323,7 @@ class SplitioWeb extends SplitioPlatform { } final client = this._factory.client.callAsFunction( - null, _buildKey(matchingKey, bucketingKey)) as JS_IBrowserClient; + null, buildJsKey(matchingKey, bucketingKey)) as JS_IBrowserClient; _clients[key] = client; } @@ -770,4 +760,115 @@ class SplitioWeb extends SplitioPlatform { _factory.UserConsent.setStatus.callAsFunction(null, enabled.toJS); } + + @override + Future? onReady( + {required String matchingKey, required String? bucketingKey}) async { + final client = await _getClient( + matchingKey: matchingKey, + bucketingKey: bucketingKey, + ); + + if ((client.getStatus.callAsFunction(null) as JS_ReadinessStatus) + .isReady + .toDart) { + return; + } else { + final completer = Completer(); + + client.on.callAsFunction( + null, + client.Event.SDK_READY, + () { + completer.complete(); + }.toJS); + + return completer.future; + } + } + + @override + Future? onReadyFromCache( + {required String matchingKey, required String? bucketingKey}) async { + final client = await _getClient( + matchingKey: matchingKey, + bucketingKey: bucketingKey, + ); + + if ((client.getStatus.callAsFunction(null) as JS_ReadinessStatus) + .isReadyFromCache + .toDart) { + return; + } else { + final completer = Completer(); + + client.on.callAsFunction( + null, + client.Event.SDK_READY_FROM_CACHE, + () { + completer.complete(); + }.toJS); + + return completer.future; + } + } + + @override + Future? onTimeout( + {required String matchingKey, required String? bucketingKey}) async { + final client = await _getClient( + matchingKey: matchingKey, + bucketingKey: bucketingKey, + ); + + if ((client.getStatus.callAsFunction(null) as JS_ReadinessStatus) + .hasTimedout + .toDart) { + return; + } else { + final completer = Completer(); + + client.on.callAsFunction( + null, + client.Event.SDK_READY_TIMED_OUT, + () { + completer.complete(); + }.toJS); + + return completer.future; + } + } + + @override + Stream? onUpdated( + {required String matchingKey, required String? bucketingKey}) { + late final StreamController controller; + final JSFunction jsCallback = (() { + if (!controller.isClosed) { + controller.add(null); + } + }).toJS; + JS_IBrowserClient? client; + + controller = StreamController( + onListen: () async { + client = await _getClient( + matchingKey: matchingKey, + bucketingKey: bucketingKey, + ); + client!.on.callAsFunction(null, client!.Event.SDK_UPDATE, jsCallback); + }, + onCancel: () async { + if (client != null) { + client!.off + .callAsFunction(null, client!.Event.SDK_UPDATE, jsCallback); + } + if (!controller.isClosed) { + await controller.close(); + } + }, + ); + + return controller.stream; + } } diff --git a/splitio_web/lib/src/js_interop.dart b/splitio_web/lib/src/js_interop.dart index 27cdc7a..e34f5de 100644 --- a/splitio_web/lib/src/js_interop.dart +++ b/splitio_web/lib/src/js_interop.dart @@ -19,6 +19,21 @@ extension type JS_IUserConsentAPI._(JSObject _) implements JSObject { external JSFunction getStatus; } +@JS() +extension type JS_EventConsts._(JSObject _) implements JSObject { + external JSString SDK_READY; + external JSString SDK_READY_FROM_CACHE; + external JSString SDK_READY_TIMED_OUT; + external JSString SDK_UPDATE; +} + +@JS() +extension type JS_ReadinessStatus._(JSObject _) implements JSObject { + external JSBoolean isReady; + external JSBoolean isReadyFromCache; + external JSBoolean hasTimedout; +} + @JS() extension type JS_IBrowserClient._(JSObject _) implements JSObject { external JSFunction getTreatment; @@ -38,6 +53,11 @@ extension type JS_IBrowserClient._(JSObject _) implements JSObject { external JSFunction clearAttributes; external JSFunction flush; external JSFunction destroy; + external JSFunction on; + external JSFunction off; + external JSFunction emit; + external JS_EventConsts Event; + external JSFunction getStatus; } @JS() @@ -122,7 +142,8 @@ Prerequisite jsObjectToPrerequisite(JSObject obj) { return Prerequisite( (_reflectGet(obj, 'flagName'.toJS) as JSString).toDart, jsArrayToList(_reflectGet(obj, 'treatments'.toJS) as JSArray) - .toSet().cast(), + .toSet() + .cast(), ); } @@ -145,3 +166,13 @@ SplitView jsObjectToSplitView(JSObject obj) { .map(jsObjectToPrerequisite) .toSet()); } + +JSAny buildJsKey(String matchingKey, String? bucketingKey) { + if (bucketingKey != null) { + return { + 'matchingKey': matchingKey, + 'bucketingKey': bucketingKey, + }.jsify()!; + } + return matchingKey.toJS; +} diff --git a/splitio_web/test/splitio_web_test.dart b/splitio_web/test/splitio_web_test.dart index 5d53bac..e3656de 100644 --- a/splitio_web/test/splitio_web_test.dart +++ b/splitio_web/test/splitio_web_test.dart @@ -886,6 +886,71 @@ void main() { }); }); + group('events', () { + test('onReady (SDK_READY event is emitted after onReady is called)', () { + Future? onReady = _platform + .onReady(matchingKey: 'matching-key', bucketingKey: 'bucketing-key') + ?.then((value) => true); + + // Emit SDK_READY event + final mockClient = mock.mockFactory.client + .callAsFunction(null, buildJsKey('matching-key', 'bucketing-key')) + as JS_IBrowserClient; + mockClient.emit.callAsFunction(null, mockClient.Event.SDK_READY); + + expect(onReady, completion(equals(true))); + }); + + test( + 'onReadyFromCache (SDK_READY_FROM_CACHE event is emitted before onReadyFromCache is called)', + () { + // Emit SDK_READY_FROM_CACHE event + final mockClient = mock.mockFactory.client + .callAsFunction(null, buildJsKey('matching-key', 'bucketing-key')) + as JS_IBrowserClient; + mockClient.emit + .callAsFunction(null, mockClient.Event.SDK_READY_FROM_CACHE); + + Future? onReadyFromCache = _platform + .onReadyFromCache( + matchingKey: 'matching-key', bucketingKey: 'bucketing-key') + ?.then((value) => true); + + expect(onReadyFromCache, completion(equals(true))); + }); + + test('onTimeout', () { + Future? onTimeout = _platform + .onTimeout(matchingKey: 'matching-key', bucketingKey: 'bucketing-key') + ?.then((value) => true); + + // Emit SDK_READY_TIMED_OUT event + final mockClient = mock.mockFactory.client + .callAsFunction(null, buildJsKey('matching-key', 'bucketing-key')) + as JS_IBrowserClient; + mockClient.emit + .callAsFunction(null, mockClient.Event.SDK_READY_TIMED_OUT); + + expect(onTimeout, completion(equals(true))); + }); + + test('onUpdated', () async { + Future? onUpdated = _platform + .onUpdated(matchingKey: 'matching-key', bucketingKey: 'bucketing-key') + ?.first + .then((value) => true); + + // Emit SDK_UPDATE event in next event loop iteration because the JS listener registration inside `onUpdated` is async + await Future.delayed(Duration.zero); + final mockClient = mock.mockFactory.client + .callAsFunction(null, buildJsKey('matching-key', 'bucketing-key')) + as JS_IBrowserClient; + mockClient.emit.callAsFunction(null, mockClient.Event.SDK_UPDATE); + + expect(onUpdated, completion(equals(true))); + }); + }); + group('userConsent', () { test('get user consent', () async { UserConsent userConsent = await _platform.getUserConsent(); diff --git a/splitio_web/test/utils/js_interop_test_utils.dart b/splitio_web/test/utils/js_interop_test_utils.dart index 10252d9..084440a 100644 --- a/splitio_web/test/utils/js_interop_test_utils.dart +++ b/splitio_web/test/utils/js_interop_test_utils.dart @@ -1,13 +1,25 @@ import 'dart:js_interop'; import 'dart:js_interop_unsafe'; +import 'package:splitio_web/src/js_interop.dart'; @JS('Promise.resolve') external JSPromise _promiseResolve(); class SplitioMock { - final List<({String methodName, List methodArguments})> calls = []; + // JS Browser SDK API mock final JSObject splitio = JSObject(); + + // Test utils + final List<({String methodName, List methodArguments})> calls = []; + final JS_IBrowserSDK mockFactory = JSObject() as JS_IBrowserSDK; + JSString _userConsent = 'UNKNOWN'.toJS; + JS_ReadinessStatus _readinessStatus = { + 'isReady': false, + 'isReadyFromCache': false, + 'hasTimedout': false, + }.jsify() as JS_ReadinessStatus; + Map> _eventListeners = {}; JSObject _createSplitViewJSObject(JSString splitName) { return { @@ -51,7 +63,42 @@ class SplitioMock { return ['split1'.toJS, 'split2'.toJS].jsify(); }.toJS; + final mockEvents = { + 'SDK_READY': 'init::ready', + 'SDK_READY_FROM_CACHE': 'init::cache-ready', + 'SDK_READY_TIMED_OUT': 'init::timeout', + 'SDK_UPDATE': 'state::update' + }.jsify() as JS_EventConsts; + final mockClient = JSObject(); + mockClient['Event'] = mockEvents; + mockClient['on'] = (JSString event, JSFunction listener) { + calls.add((methodName: 'on', methodArguments: [event, listener])); + _eventListeners[event] ??= Set(); + _eventListeners[event]!.add(listener); + }.toJS; + mockClient['off'] = (JSString event, JSFunction listener) { + calls.add((methodName: 'off', methodArguments: [event, listener])); + _eventListeners[event] ??= Set(); + _eventListeners[event]!.remove(listener); + }.toJS; + mockClient['emit'] = (JSString event) { + calls.add((methodName: 'emit', methodArguments: [event])); + _eventListeners[event]?.forEach((listener) { + listener.callAsFunction(null, event); + }); + if (event == mockEvents.SDK_READY) { + _readinessStatus.isReady = true.toJS; + } else if (event == mockEvents.SDK_READY_FROM_CACHE) { + _readinessStatus.isReadyFromCache = true.toJS; + } else if (event == mockEvents.SDK_READY_TIMED_OUT) { + _readinessStatus.hasTimedout = true.toJS; + } + }.toJS; + mockClient['getStatus'] = () { + calls.add((methodName: 'getStatus', methodArguments: [])); + return _readinessStatus; + }.toJS; mockClient['getTreatment'] = (JSAny? flagName, JSAny? attributes, JSAny? evaluationOptions) { calls.add(( @@ -230,7 +277,6 @@ class SplitioMock { final mockSettings = JSObject(); mockSettings['log'] = mockLog; - final mockFactory = JSObject(); mockFactory['settings'] = mockSettings; mockFactory['client'] = (JSAny? splitKey) { calls.add((methodName: 'client', methodArguments: [splitKey])); From 2502987e7135011b7c32d11819562f3f47773475 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 6 Jan 2026 12:34:26 -0300 Subject: [PATCH 34/55] Connect impression listener --- splitio_web/lib/splitio_web.dart | 27 ++++++++--- splitio_web/lib/src/js_interop.dart | 46 +++++++++++++++++++ splitio_web/test/splitio_web_test.dart | 42 ++++++++++++++++- .../test/utils/js_interop_test_utils.dart | 16 ++++--- 4 files changed, 117 insertions(+), 14 deletions(-) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index 863da4f..ec84e2c 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -23,7 +23,8 @@ class SplitioWeb extends SplitioPlatform { late JS_IBrowserSDK _factory; String? _trafficType; - bool _impressionListener = false; + final StreamController _impressionsStreamController = + StreamController(); final Map _clients = {}; @@ -64,11 +65,6 @@ class SplitioWeb extends SplitioPlatform { this._trafficType = sdkConfiguration.configurationMap['trafficType']; } - if (sdkConfiguration.configurationMap['impressionListener'] is bool) { - this._impressionListener = - sdkConfiguration.configurationMap['impressionListener']; - } - // Log warnings regarding unsupported configs. Not done in _buildConfig to reuse the factory logger final unsupportedConfigs = [ 'certificatePinningConfiguration', @@ -123,7 +119,7 @@ class SplitioWeb extends SplitioPlatform { } // Map SplitConfiguration to JS equivalent object - static JSObject _buildConfig(String apiKey, String matchingKey, + JSObject _buildConfig(String apiKey, String matchingKey, String? bucketingKey, SplitConfiguration? configuration) { final config = JSObject(); @@ -300,6 +296,17 @@ class SplitioWeb extends SplitioPlatform { } else { config.setProperty('storage'.toJS, storageOptions); // JS SDK } + + if (configuration.configurationMap['impressionListener'] is bool) { + final JSFunction logImpression = ((JS_ImpressionData data) { + _impressionsStreamController.add(jsImpressionDataToImpression(data)); + }).toJS; + + final JSObject impressionListener = JSObject(); + impressionListener.setProperty('logImpression'.toJS, logImpression); + + config.setProperty('impressionListener'.toJS, impressionListener); + } } return config; @@ -851,6 +858,7 @@ class SplitioWeb extends SplitioPlatform { JS_IBrowserClient? client; controller = StreamController( + // @TODO: implement onPause and onResume onListen: () async { client = await _getClient( matchingKey: matchingKey, @@ -871,4 +879,9 @@ class SplitioWeb extends SplitioPlatform { return controller.stream; } + + @override + Stream impressionsStream() { + return _impressionsStreamController.stream; + } } diff --git a/splitio_web/lib/src/js_interop.dart b/splitio_web/lib/src/js_interop.dart index e34f5de..1bb6367 100644 --- a/splitio_web/lib/src/js_interop.dart +++ b/splitio_web/lib/src/js_interop.dart @@ -3,14 +3,42 @@ import 'package:splitio_platform_interface/splitio_platform_interface.dart'; // JS SDK types +@JS() +extension type JS_ImpressionDTO._(JSObject _) implements JSObject { + external JSString feature; + external JSString keyName; + external JSString treatment; + external JSNumber time; + external JSString? bucketingKey; + external JSString label; + external JSNumber changeNumber; + external JSNumber? pt; + external JSString? properties; +} + +@JS() +extension type JS_ImpressionData._(JSObject _) implements JSObject { + external JS_ImpressionDTO impression; + external JSObject? attributes; + external JSAny ip; // string | false + external JSAny hostname; // string | false + external JSString sdkLanguageVersion; +} + @JS() extension type JS_Logger._(JSObject _) implements JSObject { external JSFunction warn; } +@JS() +extension type JS_IImpressionListener._(JSObject _) implements JSObject { + external JSFunction logImpression; +} + @JS() extension type JS_ISettings._(JSObject _) implements JSObject { external JS_Logger log; + external JS_IImpressionListener? impressionListener; } @JS() @@ -93,6 +121,9 @@ external JSArray _objectKeys(JSObject obj); @JS('Reflect.get') external JSAny? _reflectGet(JSObject target, JSAny propertyKey); +@JS('JSON.parse') +external JSObject _jsonParse(JSString obj); + List jsArrayToList(JSArray obj) { return obj.toDart.map(jsAnyToDart).toList(); } @@ -147,6 +178,7 @@ Prerequisite jsObjectToPrerequisite(JSObject obj) { ); } +// @TODO: JS_SplitView SplitView jsObjectToSplitView(JSObject obj) { return SplitView( (_reflectGet(obj, 'name'.toJS) as JSString).toDart, @@ -167,6 +199,20 @@ SplitView jsObjectToSplitView(JSObject obj) { .toSet()); } +Impression jsImpressionDataToImpression(JS_ImpressionData obj) { + return Impression( + obj.impression.keyName.toDart, + obj.impression.bucketingKey != null ? obj.impression.bucketingKey!.toDart : null, + obj.impression.feature.toDart, + obj.impression.treatment.toDart, + obj.impression.time.toDartInt, + obj.impression.label.toDart, + obj.impression.changeNumber.toDartInt, + obj.attributes != null ? jsObjectToMap(obj.attributes!) : {}, + obj.impression.properties != null ? jsObjectToMap(_jsonParse(obj.impression.properties!)) : null, + ); +} + JSAny buildJsKey(String matchingKey, String? bucketingKey) { if (bucketingKey != null) { return { diff --git a/splitio_web/test/splitio_web_test.dart b/splitio_web/test/splitio_web_test.dart index e3656de..7be95a7 100644 --- a/splitio_web/test/splitio_web_test.dart +++ b/splitio_web/test/splitio_web_test.dart @@ -719,7 +719,8 @@ void main() { 'type': 'LOCALSTORAGE', 'expirationDays': 100, 'clearOnInit': true - } + }, + 'impressionListener': {'logImpression': {}} })); expect(mock.calls[mock.calls.length - 4].methodName, 'warn'); @@ -951,6 +952,45 @@ void main() { }); }); + test('impressions', () async { + SplitioWeb _platform = SplitioWeb(); + await _platform.init( + apiKey: 'api-key', + matchingKey: 'matching-key', + bucketingKey: null, + sdkConfiguration: SplitConfiguration(impressionListener: true)); + + _platform.impressionsStream().listen( + expectAsync1((impression) { + expect(impression.key, 'key'); + expect(impression.bucketingKey, null); + expect(impression.split, 'split'); + expect(impression.treatment, 'treatment'); + expect(impression.time, 3000); + expect(impression.appliedRule, 'appliedRule'); + expect(impression.changeNumber, 200); + expect(impression.attributes, {}); + expect(impression.properties, {'a': 1}); + }), + ); + + mock.mockFactory.settings.impressionListener!.logImpression.callAsFunction(null, { + 'impression': { + 'feature': 'split', + 'keyName': 'key', + 'treatment': 'treatment', + 'time': 3000, + 'label': 'appliedRule', + 'changeNumber': 200, + 'properties': '{"a": 1}', + }, + 'attributes': {}, + 'ip': false, + 'hostname': false, + 'sdkLanguageVersion': 'browserjs-1.0.0', + }.jsify() as JS_ImpressionData); + }); + group('userConsent', () { test('get user consent', () async { UserConsent userConsent = await _platform.getUserConsent(); diff --git a/splitio_web/test/utils/js_interop_test_utils.dart b/splitio_web/test/utils/js_interop_test_utils.dart index 084440a..ce0a7d4 100644 --- a/splitio_web/test/utils/js_interop_test_utils.dart +++ b/splitio_web/test/utils/js_interop_test_utils.dart @@ -5,6 +5,9 @@ import 'package:splitio_web/src/js_interop.dart'; @JS('Promise.resolve') external JSPromise _promiseResolve(); +@JS('Object.assign') +external JSObject _objectAssign(JSObject target, JSObject source); + class SplitioMock { // JS Browser SDK API mock final JSObject splitio = JSObject(); @@ -274,10 +277,6 @@ class SplitioMock { return _userConsent; }.toJS; - final mockSettings = JSObject(); - mockSettings['log'] = mockLog; - - mockFactory['settings'] = mockSettings; mockFactory['client'] = (JSAny? splitKey) { calls.add((methodName: 'client', methodArguments: [splitKey])); return mockClient; @@ -288,8 +287,13 @@ class SplitioMock { }.toJS; mockFactory['UserConsent'] = mockUserConsent; - splitio['SplitFactory'] = (JSAny? arg1) { - calls.add((methodName: 'SplitFactory', methodArguments: [arg1])); + splitio['SplitFactory'] = (JSObject config) { + calls.add((methodName: 'SplitFactory', methodArguments: [config])); + + final mockSettings = _objectAssign(JSObject(), config); + mockSettings['log'] = mockLog; + mockFactory['settings'] = mockSettings; + return mockFactory; }.toJS; } From 0a292df35923399168e2e4650509fc2e309d55c0 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 6 Jan 2026 14:57:35 -0300 Subject: [PATCH 35/55] Handle stream onPause and onResume --- splitio_web/lib/splitio_web.dart | 33 ++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index 863da4f..68bedc7 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -849,24 +849,25 @@ class SplitioWeb extends SplitioPlatform { } }).toJS; JS_IBrowserClient? client; + void attackListener() async { + client = await _getClient( + matchingKey: matchingKey, + bucketingKey: bucketingKey, + ); + client!.on.callAsFunction(null, client!.Event.SDK_UPDATE, jsCallback); + } + + void detachListener() { + if (client != null) { + client!.off.callAsFunction(null, client!.Event.SDK_UPDATE, jsCallback); + } + } controller = StreamController( - onListen: () async { - client = await _getClient( - matchingKey: matchingKey, - bucketingKey: bucketingKey, - ); - client!.on.callAsFunction(null, client!.Event.SDK_UPDATE, jsCallback); - }, - onCancel: () async { - if (client != null) { - client!.off - .callAsFunction(null, client!.Event.SDK_UPDATE, jsCallback); - } - if (!controller.isClosed) { - await controller.close(); - } - }, + onListen: attackListener, + onPause: detachListener, + onResume: attackListener, + onCancel: detachListener, ); return controller.stream; From ec6fc2e332d788f99ad27dcd62800b4131ea4cc6 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 6 Jan 2026 15:13:08 -0300 Subject: [PATCH 36/55] Make stream callbacks synchronous and return null if client doesn't exist --- splitio_web/lib/splitio_web.dart | 20 ++++++++++---------- splitio_web/test/splitio_web_test.dart | 16 +++++++++------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index 68bedc7..8f4e512 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -842,25 +842,25 @@ class SplitioWeb extends SplitioPlatform { @override Stream? onUpdated( {required String matchingKey, required String? bucketingKey}) { + final client = _clients[_buildKeyString(matchingKey, bucketingKey)]; + + if (client == null) { + return null; + } + late final StreamController controller; final JSFunction jsCallback = (() { if (!controller.isClosed) { controller.add(null); } }).toJS; - JS_IBrowserClient? client; - void attackListener() async { - client = await _getClient( - matchingKey: matchingKey, - bucketingKey: bucketingKey, - ); - client!.on.callAsFunction(null, client!.Event.SDK_UPDATE, jsCallback); + + void attackListener() { + client.on.callAsFunction(null, client.Event.SDK_UPDATE, jsCallback); } void detachListener() { - if (client != null) { - client!.off.callAsFunction(null, client!.Event.SDK_UPDATE, jsCallback); - } + client.off.callAsFunction(null, client.Event.SDK_UPDATE, jsCallback); } controller = StreamController( diff --git a/splitio_web/test/splitio_web_test.dart b/splitio_web/test/splitio_web_test.dart index e3656de..74ca891 100644 --- a/splitio_web/test/splitio_web_test.dart +++ b/splitio_web/test/splitio_web_test.dart @@ -935,19 +935,21 @@ void main() { }); test('onUpdated', () async { - Future? onUpdated = _platform - .onUpdated(matchingKey: 'matching-key', bucketingKey: 'bucketing-key') - ?.first - .then((value) => true); + // Precondition: client is initialized before onUpdated is called + await _platform.getClient( + matchingKey: 'matching-key', bucketingKey: 'bucketing-key'); - // Emit SDK_UPDATE event in next event loop iteration because the JS listener registration inside `onUpdated` is async - await Future.delayed(Duration.zero); + final stream = _platform.onUpdated( + matchingKey: 'matching-key', bucketingKey: 'bucketing-key')!; + stream.listen(expectAsync1((_) {}, count: 2)); + + // Emit SDK_UPDATE event final mockClient = mock.mockFactory.client .callAsFunction(null, buildJsKey('matching-key', 'bucketing-key')) as JS_IBrowserClient; mockClient.emit.callAsFunction(null, mockClient.Event.SDK_UPDATE); - expect(onUpdated, completion(equals(true))); + mockClient.emit.callAsFunction(null, mockClient.Event.SDK_UPDATE); }); }); From 55ad0aef4fead515304533d1c7ea9c891cf97d59 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 6 Jan 2026 16:05:23 -0300 Subject: [PATCH 37/55] Close stream controller on onCancel callback --- splitio_web/lib/splitio_web.dart | 27 ++++++++++++++------------ splitio_web/test/splitio_web_test.dart | 24 ++++++++++++++++++----- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index 8f4e512..298bf4f 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -855,19 +855,22 @@ class SplitioWeb extends SplitioPlatform { } }).toJS; - void attackListener() { - client.on.callAsFunction(null, client.Event.SDK_UPDATE, jsCallback); - } - - void detachListener() { - client.off.callAsFunction(null, client.Event.SDK_UPDATE, jsCallback); - } - controller = StreamController( - onListen: attackListener, - onPause: detachListener, - onResume: attackListener, - onCancel: detachListener, + onListen: () { + client.on.callAsFunction(null, client.Event.SDK_UPDATE, jsCallback); + }, + onPause: () { + client.off.callAsFunction(null, client.Event.SDK_UPDATE, jsCallback); + }, + onResume: () { + client.on.callAsFunction(null, client.Event.SDK_UPDATE, jsCallback); + }, + onCancel: () async { + client.off.callAsFunction(null, client.Event.SDK_UPDATE, jsCallback); + if (!controller.isClosed) { + await controller.close(); + } + }, ); return controller.stream; diff --git a/splitio_web/test/splitio_web_test.dart b/splitio_web/test/splitio_web_test.dart index 74ca891..674949a 100644 --- a/splitio_web/test/splitio_web_test.dart +++ b/splitio_web/test/splitio_web_test.dart @@ -938,18 +938,32 @@ void main() { // Precondition: client is initialized before onUpdated is called await _platform.getClient( matchingKey: 'matching-key', bucketingKey: 'bucketing-key'); + final mockClient = mock.mockFactory.client + .callAsFunction(null, buildJsKey('matching-key', 'bucketing-key')) + as JS_IBrowserClient; final stream = _platform.onUpdated( matchingKey: 'matching-key', bucketingKey: 'bucketing-key')!; - stream.listen(expectAsync1((_) {}, count: 2)); + final subscription = stream.listen(expectAsync1((_) {}, count: 3)); - // Emit SDK_UPDATE event - final mockClient = mock.mockFactory.client - .callAsFunction(null, buildJsKey('matching-key', 'bucketing-key')) - as JS_IBrowserClient; + // Emit SDK_UPDATE events. Should be received mockClient.emit.callAsFunction(null, mockClient.Event.SDK_UPDATE); mockClient.emit.callAsFunction(null, mockClient.Event.SDK_UPDATE); + + await Future.delayed(const Duration(milliseconds: 100)); // let events deliver + + // Pause subscription and emit SDK_UPDATE event. Should not be received + subscription.pause(); + mockClient.emit.callAsFunction(null, mockClient.Event.SDK_UPDATE); + await Future.delayed(const Duration(milliseconds: 100)); + + // Resume subscription and emit SDK_UPDATE event. Should be received + subscription.resume(); + mockClient.emit.callAsFunction(null, mockClient.Event.SDK_UPDATE); + await Future.delayed(const Duration(milliseconds: 100)); + + await subscription.cancel(); }); }); From bc5fded3aba766b929f37cf7119e6d70f3cae496 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 8 Jan 2026 12:12:26 -0300 Subject: [PATCH 38/55] Fix logger configuration and JS interop calls --- splitio_web/lib/splitio_web.dart | 53 ++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index fe6a9af..e66fddb 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -245,16 +245,24 @@ class SplitioWeb extends SplitioPlatform { final logLevel = configuration.configurationMap['logLevel']; if (logLevel is String) { - final logger = logLevel == SplitLogLevel.verbose.toString() || - logLevel == SplitLogLevel.debug.toString() - ? window.splitio!.DebugLogger?.callAsFunction(null) - : logLevel == SplitLogLevel.info.toString() - ? window.splitio!.InfoLogger?.callAsFunction(null) - : logLevel == SplitLogLevel.warning.toString() - ? window.splitio!.WarnLogger?.callAsFunction(null) - : logLevel == SplitLogLevel.error.toString() - ? window.splitio!.ErrorLogger?.callAsFunction(null) - : null; + JSAny? logger; + switch (SplitLogLevel.values.firstWhere((e) => e.name == logLevel)) { + case SplitLogLevel.verbose: + case SplitLogLevel.debug: + logger = window.splitio!.DebugLogger?.callAsFunction(); + break; + case SplitLogLevel.info: + logger = window.splitio!.InfoLogger?.callAsFunction(); + break; + case SplitLogLevel.warning: + logger = window.splitio!.WarnLogger?.callAsFunction(); + break; + case SplitLogLevel.error: + logger = window.splitio!.ErrorLogger?.callAsFunction(); + break; + default: + break; + } if (logger != null) { config.setProperty('debug'.toJS, logger); // Browser SDK } else { @@ -263,7 +271,11 @@ class SplitioWeb extends SplitioPlatform { } } else if (configuration.configurationMap['enableDebug'] == true) { config.setProperty( - 'debug'.toJS, window.splitio!.DebugLogger?.callAsFunction(null)); + 'debug'.toJS, + window.splitio!.DebugLogger != null + ? window.splitio!.DebugLogger!.callAsFunction() // Browser SDK + : 'DEBUG'.toJS // JS SDK + ); } if (configuration.configurationMap['readyTimeout'] != null) { @@ -698,9 +710,10 @@ class SplitioWeb extends SplitioPlatform { bucketingKey: bucketingKey, ); - final result = client.flush.callAsFunction(null) as JSPromise; + final result = client.flush.callAsFunction(null) as JSPromise; - return result.toDart; + // @TODO remove wrapping to Future when JS SDK `flush` type is fixed + return result.toDart.then((_) {}); } @override @@ -784,7 +797,7 @@ class SplitioWeb extends SplitioPlatform { final completer = Completer(); client.on.callAsFunction( - null, + client, client.Event.SDK_READY, () { completer.complete(); @@ -810,7 +823,7 @@ class SplitioWeb extends SplitioPlatform { final completer = Completer(); client.on.callAsFunction( - null, + client, client.Event.SDK_READY_FROM_CACHE, () { completer.complete(); @@ -836,7 +849,7 @@ class SplitioWeb extends SplitioPlatform { final completer = Completer(); client.on.callAsFunction( - null, + client, client.Event.SDK_READY_TIMED_OUT, () { completer.complete(); @@ -864,16 +877,16 @@ class SplitioWeb extends SplitioPlatform { controller = StreamController( onListen: () { - client.on.callAsFunction(null, client.Event.SDK_UPDATE, jsCallback); + client.on.callAsFunction(client, client.Event.SDK_UPDATE, jsCallback); }, onPause: () { - client.off.callAsFunction(null, client.Event.SDK_UPDATE, jsCallback); + client.off.callAsFunction(client, client.Event.SDK_UPDATE, jsCallback); }, onResume: () { - client.on.callAsFunction(null, client.Event.SDK_UPDATE, jsCallback); + client.on.callAsFunction(client, client.Event.SDK_UPDATE, jsCallback); }, onCancel: () async { - client.off.callAsFunction(null, client.Event.SDK_UPDATE, jsCallback); + client.off.callAsFunction(client, client.Event.SDK_UPDATE, jsCallback); if (!controller.isClosed) { await controller.close(); } From dcfbb4269c1dd9f89483848179cc342ad552ca41 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 8 Jan 2026 15:57:53 -0300 Subject: [PATCH 39/55] Simplify JS interop by removing callAsFunction calls --- splitio_web/lib/splitio_web.dart | 99 +++++++++++--------------- splitio_web/lib/src/js_interop.dart | 58 ++++++++------- splitio_web/test/splitio_web_test.dart | 52 ++++++-------- 3 files changed, 95 insertions(+), 114 deletions(-) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index e66fddb..8c924ee 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -57,8 +57,7 @@ class SplitioWeb extends SplitioPlatform { _buildConfig(apiKey, matchingKey, bucketingKey, sdkConfiguration); // Create factory instance - this._factory = window.splitio!.SplitFactory.callAsFunction(null, config) - as JS_IBrowserSDK; + this._factory = window.splitio!.SplitFactory(config); if (sdkConfiguration != null) { if (sdkConfiguration.configurationMap['trafficType'] is String) { @@ -74,8 +73,7 @@ class SplitioWeb extends SplitioPlatform { ]; for (final configName in unsupportedConfigs) { if (sdkConfiguration.configurationMap[configName] != null) { - this._factory.settings.log.warn.callAsFunction( - this._factory.settings.log, + this._factory.settings.log.warn( 'Config $configName is not supported by the Web package. This config will be ignored.' .toJS); } @@ -341,8 +339,7 @@ class SplitioWeb extends SplitioPlatform { return; } - final client = this._factory.client.callAsFunction( - null, buildJsKey(matchingKey, bucketingKey)) as JS_IBrowserClient; + final client = this._factory.client(buildJsKey(matchingKey, bucketingKey)); _clients[key] = client; } @@ -361,7 +358,7 @@ class SplitioWeb extends SplitioPlatform { Future _getManager() async { await this._initFuture; - return _factory.manager.callAsFunction(null) as JS_IBrowserManager; + return _factory.manager(); } JSAny? _convertValue(dynamic value, bool isAttribute) { @@ -387,8 +384,7 @@ class SplitioWeb extends SplitioPlatform { if (jsValue != null) { jsMap.setProperty(key.toJS, jsValue); } else { - this._factory.settings.log.warn.callAsFunction( - null, + this._factory.settings.log.warn( 'Invalid ${isAttribute ? 'attribute' : 'property'} value: $value, for key: $key, will be ignored' .toJS); } @@ -421,11 +417,10 @@ class SplitioWeb extends SplitioPlatform { bucketingKey: bucketingKey, ); - final result = client.getTreatment.callAsFunction( - null, + final result = client.getTreatment( splitName.toJS, _convertMap(attributes, true), - _convertEvaluationOptions(evaluationOptions)) as JSString; + _convertEvaluationOptions(evaluationOptions)); return result.toDart; } @@ -443,11 +438,10 @@ class SplitioWeb extends SplitioPlatform { bucketingKey: bucketingKey, ); - final result = client.getTreatments.callAsFunction( - null, - splitNames.jsify(), + final result = client.getTreatments( + splitNames.jsify() as JSArray, _convertMap(attributes, true), - _convertEvaluationOptions(evaluationOptions)) as JSObject; + _convertEvaluationOptions(evaluationOptions)); return jsTreatmentsToMap(result); } @@ -466,11 +460,10 @@ class SplitioWeb extends SplitioPlatform { bucketingKey: bucketingKey, ); - final result = client.getTreatmentWithConfig.callAsFunction( - null, + final result = client.getTreatmentWithConfig( splitName.toJS, _convertMap(attributes, true), - _convertEvaluationOptions(evaluationOptions)) as JSObject; + _convertEvaluationOptions(evaluationOptions)); return jsTreatmentWithConfigToSplitResult(result); } @@ -488,11 +481,10 @@ class SplitioWeb extends SplitioPlatform { bucketingKey: bucketingKey, ); - final result = client.getTreatmentsWithConfig.callAsFunction( - null, - splitNames.jsify(), + final result = client.getTreatmentsWithConfig( + splitNames.jsify() as JSArray, _convertMap(attributes, true), - _convertEvaluationOptions(evaluationOptions)) as JSObject; + _convertEvaluationOptions(evaluationOptions)); return jsTreatmentsWithConfigToMap(result); } @@ -510,11 +502,10 @@ class SplitioWeb extends SplitioPlatform { bucketingKey: bucketingKey, ); - final result = client.getTreatmentsByFlagSet.callAsFunction( - null, + final result = client.getTreatmentsByFlagSet( flagSet.toJS, _convertMap(attributes, true), - _convertEvaluationOptions(evaluationOptions)) as JSObject; + _convertEvaluationOptions(evaluationOptions)); return jsTreatmentsToMap(result); } @@ -532,11 +523,10 @@ class SplitioWeb extends SplitioPlatform { bucketingKey: bucketingKey, ); - final result = client.getTreatmentsByFlagSets.callAsFunction( - null, - flagSets.jsify(), + final result = client.getTreatmentsByFlagSets( + flagSets.jsify() as JSArray, _convertMap(attributes, true), - _convertEvaluationOptions(evaluationOptions)) as JSObject; + _convertEvaluationOptions(evaluationOptions)); return jsTreatmentsToMap(result); } @@ -554,11 +544,10 @@ class SplitioWeb extends SplitioPlatform { bucketingKey: bucketingKey, ); - final result = client.getTreatmentsWithConfigByFlagSet.callAsFunction( - null, + final result = client.getTreatmentsWithConfigByFlagSet( flagSet.toJS, _convertMap(attributes, true), - _convertEvaluationOptions(evaluationOptions)) as JSObject; + _convertEvaluationOptions(evaluationOptions)); return jsTreatmentsWithConfigToMap(result); } @@ -576,11 +565,10 @@ class SplitioWeb extends SplitioPlatform { bucketingKey: bucketingKey, ); - final result = client.getTreatmentsWithConfigByFlagSets.callAsFunction( - null, - flagSets.jsify(), + final result = client.getTreatmentsWithConfigByFlagSets( + flagSets.jsify() as JSArray, _convertMap(attributes, true), - _convertEvaluationOptions(evaluationOptions)) as JSObject; + _convertEvaluationOptions(evaluationOptions)); return jsTreatmentsWithConfigToMap(result); } @@ -598,8 +586,7 @@ class SplitioWeb extends SplitioPlatform { bucketingKey: bucketingKey, ); - final result = client.track.callAsFunction( - null, + final result = client.track( trafficType != null ? trafficType.toJS : this._trafficType != null @@ -607,7 +594,7 @@ class SplitioWeb extends SplitioPlatform { : null, eventType.toJS, value != null ? value.toJS : null, - _convertMap(properties, false)) as JSBoolean; + _convertMap(properties, false)); return result.toDart; } @@ -620,7 +607,7 @@ class SplitioWeb extends SplitioPlatform { bucketingKey: bucketingKey, ); - final result = client.getAttributes.callAsFunction(null) as JSObject; + final result = client.getAttributes(); return jsObjectToMap(result); } @@ -636,8 +623,8 @@ class SplitioWeb extends SplitioPlatform { bucketingKey: bucketingKey, ); - final result = client.setAttribute.callAsFunction( - null, attributeName.toJS, _convertValue(value, true)) as JSBoolean; + final result = + client.setAttribute(attributeName.toJS, _convertValue(value, true)); return result.toDart; } @@ -652,8 +639,7 @@ class SplitioWeb extends SplitioPlatform { bucketingKey: bucketingKey, ); - final result = client.setAttributes - .callAsFunction(null, _convertMap(attributes, true)) as JSBoolean; + final result = client.setAttributes(_convertMap(attributes, true)); return result.toDart; } @@ -668,7 +654,7 @@ class SplitioWeb extends SplitioPlatform { bucketingKey: bucketingKey, ); - final result = client.getAttribute.callAsFunction(null, attributeName.toJS); + final result = client.getAttribute(attributeName.toJS); return jsAnyToDart(result); } @@ -683,8 +669,7 @@ class SplitioWeb extends SplitioPlatform { bucketingKey: bucketingKey, ); - final result = client.removeAttribute - .callAsFunction(null, attributeName.toJS) as JSBoolean; + final result = client.removeAttribute(attributeName.toJS); return result.toDart; } @@ -697,7 +682,7 @@ class SplitioWeb extends SplitioPlatform { bucketingKey: bucketingKey, ); - final result = client.clearAttributes.callAsFunction(null) as JSBoolean; + final result = client.clearAttributes(); return result.toDart; } @@ -710,7 +695,7 @@ class SplitioWeb extends SplitioPlatform { bucketingKey: bucketingKey, ); - final result = client.flush.callAsFunction(null) as JSPromise; + final result = client.flush(); // @TODO remove wrapping to Future when JS SDK `flush` type is fixed return result.toDart.then((_) {}); @@ -724,7 +709,7 @@ class SplitioWeb extends SplitioPlatform { bucketingKey: bucketingKey, ); - final result = client.destroy.callAsFunction(null) as JSPromise; + final result = client.destroy(); return result.toDart; } @@ -733,8 +718,7 @@ class SplitioWeb extends SplitioPlatform { Future split({required String splitName}) async { final manager = await _getManager(); - final result = - manager.split.callAsFunction(null, splitName.toJS) as JSObject?; + final result = manager.split(splitName.toJS); return result != null ? jsObjectToSplitView(result) : null; } @@ -743,7 +727,7 @@ class SplitioWeb extends SplitioPlatform { Future> splits() async { final manager = await _getManager(); - final result = manager.splits.callAsFunction(null) as JSArray; + final result = manager.splits(); return result.toDart.map(jsObjectToSplitView).toList(); } @@ -752,7 +736,7 @@ class SplitioWeb extends SplitioPlatform { Future> splitNames() async { final manager = await _getManager(); - final result = manager.names.callAsFunction(null) as JSArray; + final result = manager.names(); return jsArrayToList(result).cast(); } @@ -761,8 +745,7 @@ class SplitioWeb extends SplitioPlatform { Future getUserConsent() async { await this._initFuture; - final userConsentStatus = - _factory.UserConsent.getStatus.callAsFunction(null) as JSString; + final userConsentStatus = _factory.UserConsent.getStatus(); switch (userConsentStatus.toDart) { case 'GRANTED': @@ -778,7 +761,7 @@ class SplitioWeb extends SplitioPlatform { Future setUserConsent(bool enabled) async { await this._initFuture; - _factory.UserConsent.setStatus.callAsFunction(null, enabled.toJS); + _factory.UserConsent.setStatus(enabled.toJS); } @override diff --git a/splitio_web/lib/src/js_interop.dart b/splitio_web/lib/src/js_interop.dart index f0f364c..a69b398 100644 --- a/splitio_web/lib/src/js_interop.dart +++ b/splitio_web/lib/src/js_interop.dart @@ -27,12 +27,15 @@ extension type JS_ImpressionData._(JSObject _) implements JSObject { @JS() extension type JS_Logger._(JSObject _) implements JSObject { - external JSFunction warn; + external JSAny? debug(JSString message); + external JSAny? info(JSString message); + external JSAny? warn(JSString message); + external JSAny? error(JSString message); } @JS() extension type JS_IImpressionListener._(JSObject _) implements JSObject { - external JSFunction logImpression; + external JSVoid logImpression(JS_ImpressionData impression); } @JS() @@ -43,8 +46,8 @@ extension type JS_ISettings._(JSObject _) implements JSObject { @JS() extension type JS_IUserConsentAPI._(JSObject _) implements JSObject { - external JSFunction setStatus; - external JSFunction getStatus; + external JSBoolean setStatus(JSBoolean userConsent); + external JSString getStatus(); } @JS() @@ -64,23 +67,24 @@ extension type JS_ReadinessStatus._(JSObject _) implements JSObject { @JS() extension type JS_IBrowserClient._(JSObject _) implements JSObject { - external JSFunction getTreatment; - external JSFunction getTreatments; - external JSFunction getTreatmentWithConfig; - external JSFunction getTreatmentsWithConfig; - external JSFunction getTreatmentsByFlagSet; - external JSFunction getTreatmentsByFlagSets; - external JSFunction getTreatmentsWithConfigByFlagSet; - external JSFunction getTreatmentsWithConfigByFlagSets; - external JSFunction track; - external JSFunction setAttribute; - external JSFunction getAttribute; - external JSFunction removeAttribute; - external JSFunction setAttributes; - external JSFunction getAttributes; - external JSFunction clearAttributes; - external JSFunction flush; - external JSFunction destroy; + external JSString getTreatment( + JSString flagName, JSObject attributes, JSObject evaluationOptions); + external JSObject getTreatments(JSArray flagNames, JSObject attributes, JSObject evaluationOptions); + external JSObject getTreatmentWithConfig(JSString flagName, JSObject attributes, JSObject evaluationOptions); + external JSObject getTreatmentsWithConfig(JSArray flagNames, JSObject attributes, JSObject evaluationOptions); + external JSObject getTreatmentsByFlagSet(JSString flagSetName, JSObject attributes, JSObject evaluationOptions); + external JSObject getTreatmentsByFlagSets(JSArray flagSetNames, JSObject attributes, JSObject evaluationOptions); + external JSObject getTreatmentsWithConfigByFlagSet(JSString flagSetName, JSObject attributes, JSObject evaluationOptions); + external JSObject getTreatmentsWithConfigByFlagSets(JSArray flagSetNames, JSObject attributes, JSObject evaluationOptions); + external JSBoolean track(JSString? trafficType, JSString eventType, JSNumber? value, JSObject? attributes); + external JSBoolean setAttribute(JSString attributeName, JSAny? attributeValue); + external JSAny getAttribute(JSString attributeName); + external JSBoolean removeAttribute(JSString attributeName); + external JSBoolean setAttributes(JSObject attributes); + external JSObject getAttributes(); + external JSBoolean clearAttributes(); + external JSPromise flush(); + external JSPromise destroy(); external JSFunction on; external JSFunction off; external JSFunction emit; @@ -90,22 +94,22 @@ extension type JS_IBrowserClient._(JSObject _) implements JSObject { @JS() extension type JS_IBrowserManager._(JSObject _) implements JSObject { - external JSFunction names; - external JSFunction split; - external JSFunction splits; + external JSArray names(); + external JSObject? split(JSString name); + external JSArray splits(); } @JS() extension type JS_IBrowserSDK._(JSObject _) implements JSObject { - external JSFunction client; - external JSFunction manager; + external JS_IBrowserClient client(JSAny? key); + external JS_IBrowserManager manager(); external JS_ISettings settings; external JS_IUserConsentAPI UserConsent; } @JS() extension type JS_BrowserSDKPackage._(JSObject _) implements JSObject { - external JSFunction SplitFactory; + external JS_IBrowserSDK SplitFactory(JSObject config); external JSFunction? InLocalStorage; external JSFunction? DebugLogger; external JSFunction? InfoLogger; diff --git a/splitio_web/test/splitio_web_test.dart b/splitio_web/test/splitio_web_test.dart index fe08e52..9c50d2e 100644 --- a/splitio_web/test/splitio_web_test.dart +++ b/splitio_web/test/splitio_web_test.dart @@ -894,9 +894,8 @@ void main() { ?.then((value) => true); // Emit SDK_READY event - final mockClient = mock.mockFactory.client - .callAsFunction(null, buildJsKey('matching-key', 'bucketing-key')) - as JS_IBrowserClient; + final mockClient = + mock.mockFactory.client(buildJsKey('matching-key', 'bucketing-key')); mockClient.emit.callAsFunction(null, mockClient.Event.SDK_READY); expect(onReady, completion(equals(true))); @@ -906,9 +905,8 @@ void main() { 'onReadyFromCache (SDK_READY_FROM_CACHE event is emitted before onReadyFromCache is called)', () { // Emit SDK_READY_FROM_CACHE event - final mockClient = mock.mockFactory.client - .callAsFunction(null, buildJsKey('matching-key', 'bucketing-key')) - as JS_IBrowserClient; + final mockClient = + mock.mockFactory.client(buildJsKey('matching-key', 'bucketing-key')); mockClient.emit .callAsFunction(null, mockClient.Event.SDK_READY_FROM_CACHE); @@ -926,9 +924,8 @@ void main() { ?.then((value) => true); // Emit SDK_READY_TIMED_OUT event - final mockClient = mock.mockFactory.client - .callAsFunction(null, buildJsKey('matching-key', 'bucketing-key')) - as JS_IBrowserClient; + final mockClient = + mock.mockFactory.client(buildJsKey('matching-key', 'bucketing-key')); mockClient.emit .callAsFunction(null, mockClient.Event.SDK_READY_TIMED_OUT); @@ -939,9 +936,8 @@ void main() { // Precondition: client is initialized before onUpdated is called await _platform.getClient( matchingKey: 'matching-key', bucketingKey: 'bucketing-key'); - final mockClient = mock.mockFactory.client - .callAsFunction(null, buildJsKey('matching-key', 'bucketing-key')) - as JS_IBrowserClient; + final mockClient = + mock.mockFactory.client(buildJsKey('matching-key', 'bucketing-key')); final stream = _platform.onUpdated( matchingKey: 'matching-key', bucketingKey: 'bucketing-key')!; @@ -991,23 +987,21 @@ void main() { }), ); - mock.mockFactory.settings.impressionListener!.logImpression.callAsFunction( - null, - { - 'impression': { - 'feature': 'split', - 'keyName': 'key', - 'treatment': 'treatment', - 'time': 3000, - 'label': 'appliedRule', - 'changeNumber': 200, - 'properties': '{"a": 1}', - }, - 'attributes': {}, - 'ip': false, - 'hostname': false, - 'sdkLanguageVersion': 'browserjs-1.0.0', - }.jsify() as JS_ImpressionData); + mock.mockFactory.settings.impressionListener!.logImpression({ + 'impression': { + 'feature': 'split', + 'keyName': 'key', + 'treatment': 'treatment', + 'time': 3000, + 'label': 'appliedRule', + 'changeNumber': 200, + 'properties': '{"a": 1}', + }, + 'attributes': {}, + 'ip': false, + 'hostname': false, + 'sdkLanguageVersion': 'browserjs-1.0.0', + }.jsify() as JS_ImpressionData); }); group('userConsent', () { From 9175406bd0c8f62c2cc17eddb43872859e62e951 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 8 Jan 2026 16:15:10 -0300 Subject: [PATCH 40/55] Fix flush return type and simplify promise handling --- splitio_web/lib/splitio_web.dart | 9 ++------- splitio_web/lib/src/js_interop.dart | 2 +- splitio_web/web/split-browser-1.6.0.full.min.js | 2 +- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index 8c924ee..370876b 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -695,10 +695,7 @@ class SplitioWeb extends SplitioPlatform { bucketingKey: bucketingKey, ); - final result = client.flush(); - - // @TODO remove wrapping to Future when JS SDK `flush` type is fixed - return result.toDart.then((_) {}); + return client.flush().toDart; } @override @@ -709,9 +706,7 @@ class SplitioWeb extends SplitioPlatform { bucketingKey: bucketingKey, ); - final result = client.destroy(); - - return result.toDart; + return client.destroy().toDart; } @override diff --git a/splitio_web/lib/src/js_interop.dart b/splitio_web/lib/src/js_interop.dart index a69b398..0917b1c 100644 --- a/splitio_web/lib/src/js_interop.dart +++ b/splitio_web/lib/src/js_interop.dart @@ -83,7 +83,7 @@ extension type JS_IBrowserClient._(JSObject _) implements JSObject { external JSBoolean setAttributes(JSObject attributes); external JSObject getAttributes(); external JSBoolean clearAttributes(); - external JSPromise flush(); + external JSPromise flush(); external JSPromise destroy(); external JSFunction on; external JSFunction off; diff --git a/splitio_web/web/split-browser-1.6.0.full.min.js b/splitio_web/web/split-browser-1.6.0.full.min.js index e8b2dae..d6b1c75 100644 --- a/splitio_web/web/split-browser-1.6.0.full.min.js +++ b/splitio_web/web/split-browser-1.6.0.full.min.js @@ -1,4 +1,4 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).splitio=t()}(this,(function(){"use strict";var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},e(t,n)};function t(t,n){if("function"!=typeof n&&null!==n)throw new TypeError("Class extends value "+String(n)+" is not a constructor or null");function r(){this.constructor=t}e(t,n),t.prototype=null===n?Object.create(n):(r.prototype=n.prototype,new r)}function n(e,t){var n;if(c(e))for(var r=Object.keys(e),i=0;i0)return t;e.error(vn,[n,r])}else e.error(mn,[n,r]);return!1}function Qn(e,t,n,r,i){if(void 0===r&&(r="feature flag names"),void 0===i&&(i="feature flag name"),Array.isArray(t)&&t.length>0){var s=[];if(t.forEach((function(t){var r=Hn(e,t,n,i);r&&s.push(r)})),s.length)return m(s)}return e.error(yn,[n,r]),!1}var Vn=Object.assign||function(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");e=Object(e);for(var t=1;t0}))}function er(e,t,n){var r,i,s={validFilters:[],queryString:null,groupedFilters:{bySet:[],byName:[],byPrefix:[]}};return t?Ie(n)?(e.warn(jt),s):Array.isArray(t)&&0!==t.length?(s.validFilters=t.filter((function(t,n){return t&&(r=t.type,Jn.some((function(e){return e.type===r})))&&Array.isArray(t.values)?(s.groupedFilters[t.type]=s.groupedFilters[t.type].concat(t.values),!0):(e.warn(qt,[n]),!1);var r})),Jn.forEach((function(t){var n=t.type,r=t.maxLength;s.groupedFilters[n].length>0&&(s.groupedFilters[n]=function(e,t,n,r){var i=Qn(e,n,wn,t+" filter",t+" filter value");if(i){if("bySet"===t&&(i=Zn(e,i,wn)),i.length>r)throw new Error(r+" unique values can be specified at most for '"+t+"' filter. You passed "+i.length+". Please consider reducing the amount or using other filter.");i.sort()}return i||[]}(e,n,s.groupedFilters[n],r))})),Xn(s.validFilters,"bySet")&&((Xn(s.validFilters,"byName")||Xn(s.validFilters,"byPrefix"))&&e.error(kn),Vn(s.groupedFilters,{byName:[],byPrefix:[]})),s.queryString=(r=s.groupedFilters,i=[],Jn.forEach((function(e){var t=e.type,n=e.queryParam,s=r[t];s.length>0&&i.push(n+s.map((function(e){return encodeURIComponent(e)})).join(","))})),i.length>0?"&"+i.join("&"):null),e.debug(Ve,[s.queryString]),s):(e.warn(zt),s):s}var tr,nr=250;function rr(e,t,n,r){if(null==t)return e.error(pn,[n,r]),!1;if(a(t))return e.warn(Mt,[n,r,t]),d(t);if(l(t)){if((t=t.trim()).length>0&&t.length<=nr)return t;0===t.length?e.error(vn,[n,r]):t.length>nr&&e.error(gn,[n,r])}else e.error(mn,[n,r]);return!1}function ir(e,t,n){if(c(t)){var r=rr(e,t.matchingKey,n,"matchingKey"),i=rr(e,t.bucketingKey,n,"bucketingKey");return r&&i?{matchingKey:r,bucketingKey:i}:(e.error(dn,[n]),!1)}return rr(e,t,n,"key")}function sr(e,t,n,r){var i=n.splits,s=n.rbSegments,o=n.segments,a=n.largeSegments,u=t.splitChanges,c=u.ff,l=u.rbs;e.debug("storage: set feature flags and segments"+(r?" for key "+r:"")),i&&c&&(i.clear(),i.update(c.d,[],c.t)),s&&l&&(s.clear(),s.update(l.d,[],l.t));var f=t.segmentChanges;if(r){var h=t.memberships&&t.memberships[r];!h&&f&&(h={ms:{k:f.filter((function(e){return e.added.indexOf(r)>-1})).map((function(e){return{n:e.name}}))}}),h&&(h.ms&&o.resetSegments(h.ms),h.ls&&a&&a.resetSegments(h.ls))}else f&&(o.clear(),f.forEach((function(e){o.update(e.name,e.added,e.removed,e.till)})))}!function(e){e.FlagName="Invalid flag name (max 100 chars, no spaces)",e.Treatment="Invalid treatment (max 100 chars and must match pattern)"}(tr||(tr={}));var or=/^[0-9]+[.a-zA-Z0-9_-]*$|^[a-zA-Z]+[a-zA-Z0-9_-]*$/;function ar(e){var t=c(e)?e.treatment:e;return!(!l(t)||t.length>100)&&or.test(t)}function ur(e,t){if(void 0!==t){if(ar(t))return t;e.error("Fallback treatments - Discarded fallback: "+tr.Treatment)}}function cr(e,t){var n={};return c(t)?(Object.keys(t).forEach((function(r){var i,s=t[r];(i=r).length<=100&&!i.includes(" ")?ar(s)?n[r]=s:e.error("Fallback treatments - Discarded treatment for flag '"+r+"': "+tr.Treatment):e.error("Fallback treatments - Discarded flag '"+r+"': "+tr.FlagName)})),n):n}var lr={mode:k,core:{authorizationKey:void 0,key:void 0,labelsEnabled:!0,IPAddressesEnabled:void 0},scheduler:{featuresRefreshRate:60,segmentsRefreshRate:60,telemetryRefreshRate:3600,impressionsRefreshRate:300,offlineRefreshRate:15,eventsPushRate:60,eventsQueueSize:500,impressionsQueueSize:3e4,pushRetryBackoffBase:1},urls:{sdk:"https://sdk.split.io/api",events:"https://events.split.io/api",auth:"https://auth.split.io/api",streaming:"https://streaming.split.io",telemetry:"https://telemetry.split.io/api"},storage:void 0,debug:void 0,impressionListener:void 0,version:void 0,integrations:void 0,streamingEnabled:!0,sync:{splitFilters:void 0,impressionsMode:E,enabled:!0,flagSpecVersion:Ce},log:void 0};function fr(e){return Math.round(1e3*e)}function hr(e,t){var n=t.defaults,r=t.runtime,i=t.storage,o=t.integrations,a=t.logger,u=t.consent,l=t.flagSpec,p=h({},lr,n,e);p.features=s(e,"features");var g=a(p);p.log=g;var d=p.sync;d.impressionsMode=function(e,t){return t=f(t),[b,E,T].indexOf(t)>-1?t:(e.error(Sn,["impressionsMode",[b,E,T],E]),E)}(g,d.impressionsMode);var m,v,y,S=p.scheduler,_=p.startup;S.featuresRefreshRate=fr(S.featuresRefreshRate),S.segmentsRefreshRate=fr(S.segmentsRefreshRate),S.offlineRefreshRate=fr(S.offlineRefreshRate),S.eventsPushRate=fr(S.eventsPushRate),S.telemetryRefreshRate=fr((m="telemetryRefreshRate",(v=S.telemetryRefreshRate)>=(y=60)?v:(g.error(Tn,[m,y]),y))),void 0===s(e,"scheduler.impressionsRefreshRate")&&d.impressionsMode===b&&(S.impressionsRefreshRate=60),S.impressionsRefreshRate=fr(S.impressionsRefreshRate),S.metricsRefreshRate&&g.warn("`metricsRefreshRate` will be deprecated soon. For configuring telemetry rates, update `telemetryRefreshRate` value in configs"),_.requestTimeoutBeforeReady=fr(_.requestTimeoutBeforeReady),_.readyTimeout=fr(_.readyTimeout),_.eventsFirstPushWindow=fr(_.eventsFirstPushWindow),p.mode=function(e,t){if("localhost"===e)return R;if(-1===[k,C,w,N].indexOf(t))throw Error("Invalid mode provided");return t}(p.core.authorizationKey,p.mode),i&&(p.storage=i(p)),p.initialRolloutPlan&&(p.initialRolloutPlan=function(e,t){var n=t.mode,r=t.initialRolloutPlan;if(!Ie(n))return c(r)&&c(r.splitChanges)?r:void e.error("storage: invalid rollout plan provided");e.warn("storage: initial rollout plan is ignored in consumer mode")}(g,p));var A=p.core.key;t.acceptKey?p.mode===R&&void 0===A?p.core.key="localhost_key":p.core.key=ir(g,A,_n):(void 0!==A&&g.warn("Provided `key` is ignored in server-side SDK."),p.core.key=void 0),p.runtime=r(p),o&&(p.integrations=o(p)),!1!==p.streamingEnabled&&(p.streamingEnabled=!0,S.pushRetryBackoffBase=fr(S.pushRetryBackoffBase)),!1!==d.enabled&&(d.enabled=!0);var I=er(g,d.splitFilters,p.mode);return d.splitFilters=I.validFilters,d.__splitFiltersValidation=I,d.flagSpecVersion=l?l(p):Ce,p.userConsent=u?u(p):void 0,p.fallbackTreatments=p.fallbackTreatments?function(e,t){if(c(t))return{global:ur(e,t.global),byFlag:cr(e,t.byFlag)};e.error("Fallback treatments - Discarded configuration: it must be an object with optional `global` and `byFlag` properties")}(g,p.fallbackTreatments):void 0,p}function pr(e){return null!==e&&"object"==typeof e&&"function"==typeof e.debug&&"function"==typeof e.info&&"function"==typeof e.warn&&"function"==typeof e.error}var gr={DEBUG:"DEBUG",INFO:"INFO",WARN:"WARN",ERROR:"ERROR",NONE:"NONE"},dr={DEBUG:1,INFO:2,WARN:3,ERROR:4,NONE:5},mr={debug:function(e){console.log("[DEBUG] "+e)},info:function(e){console.log("[INFO] "+e)},warn:function(e){console.log("[WARN] "+e)},error:function(e){console.log("[ERROR] "+e)}};function vr(e){return!!n(gr,(function(t){return e===t}))}function yr(e,t){void 0===e&&(e=""),void 0===t&&(t=[]);var n=0;return e.replace(/%s/g,(function(){var e=t[n++];if(c(e)||Array.isArray(e))try{e=JSON.stringify(e)}catch(e){}return e}))}var Sr,br={prefix:"splitio",logLevel:gr.NONE},Er=function(){function e(e,t){this.options=Vn({},br,e),this.codes=t||new Map,this.logLevel=dr[this.options.logLevel]}return e.prototype.setLogLevel=function(e){this.options.logLevel=e,this.logLevel=dr[e]},e.prototype.setLogger=function(e){if(e){if(pr(e))return void(this.logger=e);this.error("Invalid `logger` instance. It must be an object with `debug`, `info`, `warn` and `error` methods. Defaulting to `console.log`")}this.logger=void 0},e.prototype.debug=function(e,t){this._shouldLog(dr.DEBUG)&&this._log("debug",e,t)},e.prototype.info=function(e,t){this._shouldLog(dr.INFO)&&this._log("info",e,t)},e.prototype.warn=function(e,t){this._shouldLog(dr.WARN)&&this._log("warn",e,t)},e.prototype.error=function(e,t){this._shouldLog(dr.ERROR)&&this._log("error",e,t)},e.prototype._log=function(e,t,n){if("number"==typeof t){var r=this.codes.get(t);t=r?yr(r,n):"Message code "+t+(n?", with args: "+n.toString():"")}else n&&(t=yr(t,n));if(this.options.prefix&&(t=this.options.prefix+" => "+t),this.logger)try{return void this.logger[e](t)}catch(e){}mr[e](t)},e.prototype._shouldLog=function(e){return e>=this.logLevel},e}();try{var Tr=localStorage.getItem("splitio_debug")||"";Sr=/^(enabled?|on)/i.test(Tr)?gr.DEBUG:vr(Tr)?Tr:gr.NONE}catch(Ei){}var Rr={startup:{requestTimeoutBeforeReady:5,retriesOnFailureBeforeReady:1,readyTimeout:10,eventsFirstPushWindow:10},userConsent:O,version:"browserjs-1.6.0",debug:Sr};var kr=function(){function e(){}return e.prototype.update=function(e,t,n){var r=this;this.setChangeNumber(n);var i=e.map((function(e){return r.addSplit(e)})).some((function(e){return e}));return t.map((function(e){return r.removeSplit(e.name)})).some((function(e){return e}))||i},e.prototype.getSplits=function(e){var t=this,n={};return e.forEach((function(e){n[e]=t.getSplit(e)})),n},e.prototype.getAll=function(){var e=this;return this.getSplitNames().map((function(t){return e.getSplit(t)}))},e.prototype.killLocally=function(e,t,n){var r=this.getSplit(e);if(r&&(!r.changeNumber||r.changeNumber0)}function wr(e){return e.splits.usesSegments()||e.rbSegments.usesSegments()}var Nr=function(e){function n(t){var n=e.call(this)||this;return n.splitsCache={},n.ttCache={},n.changeNumber=-1,n.segmentsCount=0,n.flagSetsCache={},n.flagSetsFilter=t?t.groupedFilters.bySet:[],n}return t(n,e),n.prototype.clear=function(){this.splitsCache={},this.ttCache={},this.changeNumber=-1,this.segmentsCount=0,this.flagSetsCache={}},n.prototype.addSplit=function(e){var t=e.name,n=this.getSplit(t);if(n){var r=n.trafficTypeName;this.ttCache[r]--,this.ttCache[r]||delete this.ttCache[r],this.removeFromFlagSets(n.name,n.sets),Cr(n)&&this.segmentsCount--}this.splitsCache[t]=e;var i=e.trafficTypeName;return this.ttCache[i]=(this.ttCache[i]||0)+1,this.addToFlagSets(e),Cr(e)&&this.segmentsCount++,!0},n.prototype.removeSplit=function(e){var t=this.getSplit(e);if(!t)return!1;delete this.splitsCache[e];var n=t.trafficTypeName;return this.ttCache[n]--,this.ttCache[n]||delete this.ttCache[n],this.removeFromFlagSets(t.name,t.sets),Cr(t)&&this.segmentsCount--,!0},n.prototype.getSplit=function(e){return this.splitsCache[e]||null},n.prototype.setChangeNumber=function(e){return this.changeNumber=e,!0},n.prototype.getChangeNumber=function(){return this.changeNumber},n.prototype.getSplitNames=function(){return Object.keys(this.splitsCache)},n.prototype.trafficTypeExists=function(e){return a(this.ttCache[e])&&this.ttCache[e]>0},n.prototype.usesSegments=function(){return-1===this.getChangeNumber()||this.segmentsCount>0},n.prototype.getNamesByFlagSets=function(e){var t=this;return e.map((function(e){return t.flagSetsCache[e]||new Set}))},n.prototype.addToFlagSets=function(e){var t=this;e.sets&&e.sets.forEach((function(n){t.flagSetsFilter.length>0&&!t.flagSetsFilter.some((function(e){return e===n}))||(t.flagSetsCache[n]||(t.flagSetsCache[n]=new Set([])),t.flagSetsCache[n].add(e.name))}))},n.prototype.removeFromFlagSets=function(e,t){var n=this;t&&t.forEach((function(t){n.removeNames(t,e)}))},n.prototype.removeNames=function(e,t){this.flagSetsCache[e]&&(this.flagSetsCache[e].delete(t),0===this.flagSetsCache[e].size&&delete this.flagSetsCache[e])},n}(kr),_r=function(){function e(){}return e.prototype.clear=function(){this.resetSegments({})},e.prototype.registerSegments=function(){return!1},e.prototype.update=function(){return!1},e.prototype.resetSegments=function(e){var t=this;this.setChangeNumber(e.cn);var n=e,r=n.added,i=n.removed;if(r&&i){var s=!1;return r.forEach((function(e){s=t.addSegment(e)||s})),i.forEach((function(e){s=t.removeSegment(e)||s})),s}var o=(e.k||[]).map((function(e){return e.n})).sort(),a=this.getRegisteredSegments().sort();if(!o.length&&!a.length)return!1;for(var u=0;u0&&this.queue.length>=this.maxQueue&&this.onFullQueue&&this.onFullQueue()},e.prototype.clear=function(){this.queue=[]},e.prototype.pop=function(e){var t=this.queue;return this.clear(),e?e.concat(t):t},e.prototype.isEmpty=function(){return 0===this.queue.length},e}(),Or=function(){function e(e){void 0===e&&(e=0),this.name="events",this.maxQueue=e,this.queue=[],this.queueByteSize=0}return e.prototype.setOnFullQueueCb=function(e){this.onFullQueue=e},e.prototype.track=function(e,t){return void 0===t&&(t=0),this.queueByteSize+=t,this.queue.push(e),this._checkForFlush(),!0},e.prototype.clear=function(){this.queue=[],this.queueByteSize=0},e.prototype.pop=function(e){var t=this.queue;return this.clear(),e?e.concat(t):t},e.prototype.isEmpty=function(){return 0===this.queue.length},e.prototype._checkForFlush=function(){(this.queueByteSize>5242880||this.maxQueue>0&&this.queue.length>=this.maxQueue)&&this.onFullQueue&&this.onFullQueue()},e}(),Fr=36e5;function Lr(e){return e-e%Fr}var Dr=function(){function e(e){void 0===e&&(e=3e4),this.name="impression counts",this.cache={},this.cacheSize=0,this.maxStorage=e}return e.prototype._makeKey=function(e,t){return e+"::"+Lr(t)},e.prototype.track=function(e,t,n){var r=this._makeKey(e,t),i=this.cache[r];this.cache[r]=i?i+n:n,this.onFullQueue&&(this.cacheSize=this.cacheSize+n,this.cacheSize>=this.maxStorage&&this.onFullQueue())},e.prototype.pop=function(e){var t=this.cache;return this.clear(),e?(Object.keys(t).forEach((function(n){e[n]?e[n]+=t[n]:e[n]=t[n]})),e):t},e.prototype.clear=function(){this.cache={},this.cacheSize=0},e.prototype.isEmpty=function(){return 0===Object.keys(this.cache).length},e}();function Mr(e){return c(e)?e.matchingKey:e}function Kr(e){return c(e)?{matchingKey:e.matchingKey,bucketingKey:e.bucketingKey}:{matchingKey:e,bucketingKey:e}}function xr(e){return!e.core.key}function Pr(e){var t=Math.min(22,Math.max(0,Math.ceil(Math.log(e)/Math.log(1.5))));return u(t)?0:t}var Ur=.001;function Br(e){var t=e.settings;return t.mode!==R&&(xr(t)||Math.random()<=Ur)}var jr=function(){function e(e,t,n){this.splits=e,this.segments=t,this.largeSegments=n,this.name="telemetry stats",this.e=!0,this.notReadyUsage=0,this.impressionStats=[0,0,0],this.eventStats=[0,0],this.lastSync={},this.httpErrors={},this.httpLatencies={},this.authRejections=0,this.tokenRefreshes=0,this.streamingEvents=[],this.tags=[],this.exceptions={},this.latencies={},this.updatesFromSSE={}}return e.prototype.isEmpty=function(){return this.e},e.prototype.clear=function(){},e.prototype.pop=function(){return this.e=!0,{lS:this.getLastSynchronization(),mL:this.popLatencies(),mE:this.popExceptions(),hE:this.popHttpErrors(),hL:this.popHttpLatencies(),tR:this.popTokenRefreshes(),aR:this.popAuthRejections(),iQ:this.getImpressionStats(H),iDe:this.getImpressionStats(V),iDr:this.getImpressionStats(Q),spC:this.splits&&this.splits.getSplitNames().length,seC:this.segments&&this.segments.getRegisteredSegments().length,skC:this.segments&&this.segments.getKeysCount(),lsC:this.largeSegments&&this.largeSegments.getRegisteredSegments().length,lskC:this.largeSegments&&this.largeSegments.getKeysCount(),sL:this.getSessionLength(),eQ:this.getEventStats(H),eD:this.getEventStats(Q),sE:this.popStreamingEvents(),t:this.popTags(),ufs:this.popUpdatesFromSSE()}},e.prototype.getTimeUntilReady=function(){return this.timeUntilReady},e.prototype.recordTimeUntilReady=function(e){this.timeUntilReady=e},e.prototype.getTimeUntilReadyFromCache=function(){return this.timeUntilReadyFromCache},e.prototype.recordTimeUntilReadyFromCache=function(e){this.timeUntilReadyFromCache=e},e.prototype.getNonReadyUsage=function(){return this.notReadyUsage},e.prototype.recordNonReadyUsage=function(){this.notReadyUsage++},e.prototype.getImpressionStats=function(e){return this.impressionStats[e]},e.prototype.recordImpressionStats=function(e,t){this.impressionStats[e]+=t,this.e=!1},e.prototype.getEventStats=function(e){return this.eventStats[e]},e.prototype.recordEventStats=function(e,t){this.eventStats[e]+=t,this.e=!1},e.prototype.getLastSynchronization=function(){return this.lastSync},e.prototype.recordSuccessfulSync=function(e,t){this.lastSync[e]=t,this.e=!1},e.prototype.popHttpErrors=function(){var e=this.httpErrors;return this.httpErrors={},e},e.prototype.recordHttpError=function(e,t){var n=this.httpErrors[e]=this.httpErrors[e]||{};n[t]=(n[t]||0)+1,this.e=!1},e.prototype.popHttpLatencies=function(){var e=this.httpLatencies;return this.httpLatencies={},e},e.prototype.recordHttpLatency=function(e,t){(this.httpLatencies[e]=this.httpLatencies[e]||[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0])[Pr(t)]++,this.e=!1},e.prototype.popAuthRejections=function(){var e=this.authRejections;return this.authRejections=0,e},e.prototype.recordAuthRejections=function(){this.authRejections++,this.e=!1},e.prototype.popTokenRefreshes=function(){var e=this.tokenRefreshes;return this.tokenRefreshes=0,e},e.prototype.recordTokenRefreshes=function(){this.tokenRefreshes++,this.e=!1},e.prototype.popStreamingEvents=function(){return this.streamingEvents.splice(0)},e.prototype.recordStreamingEvents=function(e){this.streamingEvents.length<20&&this.streamingEvents.push(e),this.e=!1},e.prototype.popTags=function(){return this.tags.splice(0)},e.prototype.addTag=function(e){this.tags.length<10&&this.tags.push(e),this.e=!1},e.prototype.getSessionLength=function(){return this.sessionLength},e.prototype.recordSessionLength=function(e){this.sessionLength=e,this.e=!1},e.prototype.popExceptions=function(){var e=this.exceptions;return this.exceptions={},e},e.prototype.recordException=function(e){this.exceptions[e]=(this.exceptions[e]||0)+1,this.e=!1},e.prototype.popLatencies=function(){var e=this.latencies;return this.latencies={},e},e.prototype.recordLatency=function(e,t){(this.latencies[e]=this.latencies[e]||[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0])[Pr(t)]++,this.e=!1},e.prototype.popUpdatesFromSSE=function(){var e=this.updatesFromSSE;return this.updatesFromSSE={},e},e.prototype.recordUpdatesFromSSE=function(e){this.updatesFromSSE[e]=(this.updatesFromSSE[e]||0)+1,this.e=!1},e}();function qr(e){if(Array.from)return Array.from(e);var t=[];return e.forEach((function(e){t.push(e)})),t}var zr=function(){function e(e){void 0===e&&(e=3e4),this.name="unique keys",this.uniqueTrackerSize=0,this.uniqueKeysTracker={},this.maxStorage=e}return e.prototype.setOnFullQueueCb=function(e){this.onFullQueue=e},e.prototype.track=function(e,t){this.uniqueKeysTracker[e]||(this.uniqueKeysTracker[e]=new Set);var n=this.uniqueKeysTracker[e];n.has(t)||(n.add(t),this.uniqueTrackerSize++),this.uniqueTrackerSize>=this.maxStorage&&this.onFullQueue&&this.onFullQueue()},e.prototype.clear=function(){this.uniqueTrackerSize=0,this.uniqueKeysTracker={}},e.prototype.pop=function(){var e=this.uniqueKeysTracker;return this.clear(),this.fromUniqueKeysCollector(e)},e.prototype.isEmpty=function(){return 0===Object.keys(this.uniqueKeysTracker).length},e.prototype.fromUniqueKeysCollector=function(e){for(var t=[],n=Object.keys(e),r=0;r0},e}();function Gr(e){var t=e.settings,n=t.scheduler,r=n.impressionsQueueSize,i=n.eventsQueueSize,s=t.sync.__splitFiltersValidation,o=new Nr(s),a=new Wr,u=new Ar,c={splits:o,rbSegments:a,segments:u,largeSegments:new Ar,impressions:new Ir(r),impressionCounts:new Dr,events:new Or(i),telemetry:Br(e)?new jr(o,u):void 0,uniqueKeys:new zr,destroy:function(){},shared:function(){return{splits:this.splits,rbSegments:this.rbSegments,segments:new Ar,largeSegments:new Ar,impressions:this.impressions,impressionCounts:this.impressionCounts,events:this.events,telemetry:this.telemetry,uniqueKeys:this.uniqueKeys,destroy:function(){}}}};if(e.settings.mode===R){var l=function(){return!0};c.impressions.track=l,c.events.track=l,c.impressionCounts.track=l,c.uniqueKeys.track=l}return c}function Hr(e){var t=Gr(e);return t.validateCache=function(){return Promise.resolve(!0)},t}Gr.type=_,Hr.type=_;var Qr=gr.NONE;var Vr=[F,O,L];var Jr={defaults:Rr,acceptKey:!0,runtime:function(){return{ip:!1,hostname:!1}},storage:function(e){var t=e.storage,n=void 0===t?Gr:t,r=e.log,i=e.mode;if("function"==typeof n&&-1!==[_,A,I].indexOf(n.type)||(n=Gr,r.error(324)),i===R&&n.type===A)return Hr;if(-1===[R,k].indexOf(i)){if(n.type!==I)throw new Error("A PluggableStorage instance is required on consumer mode")}else n.type===I&&(n=Gr,r.error(324,[" It requires consumer mode."]));return n},integrations:function(e){return function(e,t,n){var r=e.integrations,i=e.log;if(!Array.isArray(r)||0===r.length)return[];var s=r.filter(t),o=r.length-s.length;return o&&i.warn(Bt,[o,n||""]),s}(e,(function(e){return"function"==typeof e}),"Integration items must be functions that initialize the integrations")},logger:function(e){var t,n=e.debug,r=e.logger,i=Qr;if(void 0!==n){if(function(e){return pr(e)&&"function"==typeof e.setLogLevel}(n))return n.setLogger(r),n;i="boolean"==typeof(t=e.debug)?t?gr.DEBUG:gr.NONE:"string"==typeof t&&vr(t)?t:void 0}var s=new Er({logLevel:i||Qr});return s.setLogger(r),i||s._log("error","Invalid `debug` value at config. Logs will be disabled."),s},consent:function(e){var t=e.userConsent,n=e.log;return t=f(t),Vr.indexOf(t)>-1?t:(n.error(Sn,["userConsent",Vr,O]),O)}};var Yr=new Set(["splitsdkclientkey","splitsdkversion","splitsdkmachineip","splitsdkmachinename","splitsdkimpressionsmode","host","referrer","content-type","content-length","content-encoding","accept","keep-alive","x-fastly-debug"]);function $r(e,t){var n;if(null===(n=e.sync.requestOptions)||void 0===n?void 0:n.getHeaderOverrides)try{var r=e.sync.requestOptions.getHeaderOverrides({headers:Vn({},t)});Object.keys(r).filter((function(e){return!Yr.has(e.toLowerCase())})).forEach((function(e){return t[e]=r[e]}))}catch(t){e.log.error("Problem adding custom headers to request decorator: "+t)}return t}function Zr(e,t){return e<1?t:new Promise((function(n,r){var i=setTimeout((function(){r(new Error("Operation timed out because it exceeded the configured time limit of "+e+" ms."))}),e);t.then((function(e){clearTimeout(i),n(e)}),(function(e){clearTimeout(i),r(e)}))}))}var Xr=100,ei="Global fetch API is not available.";var ti={headers:{"Cache-Control":"no-cache"}};function ni(e){return"users="+encodeURIComponent(e)}function ri(e,t,n){var r=e.urls,i=e.sync.__splitFiltersValidation&&e.sync.__splitFiltersValidation.queryString,s=e.sync.impressionsMode,o=function(e,t){var n=t.getOptions,r=t.getFetch,i=e.log,s=e.core.authorizationKey,o=e.version,a=e.runtime,u=a.ip,c=a.hostname,l=n&&n(e),f=r&&r(e);f||i.error(en,[ei]);var h={Accept:"application/json","Content-Type":"application/json",Authorization:"Bearer "+s,SplitSDKVersion:o};return u&&(h.SplitSDKMachineIP=u),c&&(h.SplitSDKMachineName=c.replace(/[^\x00-\xFF]/g,"")),function(t,n,r,s){void 0===n&&(n={}),void 0===r&&(r=function(){}),void 0===s&&(s=!1);var o=Vn({headers:$r(e,Vn({},h,n.headers||{})),method:n.method||"GET",body:n.body},l);return f?f(t,o).then((function(e){return e.ok?(r(),e):Zr(Xr,e.text()).then((function(t){return Promise.reject({response:e,message:t})}),(function(){return Promise.reject({response:e})}))})).catch((function(e){var n=e&&e.response,o="";o=n?404===n.status?"Invalid SDK key or resource not found.":e.message:e.message||"Network Error",n&&403===n.status||i[s?"info":"error"](bn,[n?"status code "+n.status:"no status code",t,o]);var a=new Error(o);throw a.statusCode=n&&n.status,r(a),a})):Promise.reject(new Error(ei))}}(e,t);return{getSdkAPIHealthCheck:function(){var e=r.sdk+"/version";return o(e).then((function(){return!0})).catch((function(){return!1}))},getEventsAPIHealthCheck:function(){var e=r.events+"/version";return o(e).then((function(){return!0})).catch((function(){return!1}))},fetchAuth:function(t){var i=r.auth+"/v2/auth?s="+e.sync.flagSpecVersion;if(t){var s=t.map(ni).join("&");s&&(i+="&"+s)}return o(i,void 0,n.trackHttp(ee))},fetchSplitChanges:function(t,s,a,u){var c=r.sdk+"/splitChanges?s="+e.sync.flagSpecVersion+"&since="+t+(u?"&rbSince="+u:"")+(i||"")+(a?"&till="+a:"");return o(c,s?ti:void 0,n.trackHttp(J)).catch((function(t){throw 414===t.statusCode&&e.log.error(Rn),t}))},fetchSegmentChanges:function(e,t,i,s){var a=r.sdk+"/segmentChanges/"+t+"?since="+e+(s?"&till="+s:"");return o(a,i?ti:void 0,n.trackHttp(te))},fetchMemberships:function(e,t,i){var s=r.sdk+"/memberships/"+encodeURIComponent(e)+(i?"?till="+i:"");return o(s,t?ti:void 0,n.trackHttp(ne))},postEventsBulk:function(e,t){var i=r.events+"/events/bulk";return o(i,{method:"POST",body:e,headers:t},n.trackHttp(Z))},postTestImpressionsBulk:function(e,t){var i=r.events+"/testImpressions/bulk";return o(i,{method:"POST",body:e,headers:Vn({SplitSDKImpressionsMode:s},t)},n.trackHttp(Y))},postTestImpressionsCount:function(e,t){var i=r.events+"/testImpressions/count";return o(i,{method:"POST",body:e,headers:t},n.trackHttp($))},postUniqueKeysBulkCs:function(e,t){var i=r.telemetry+"/v1/keys/cs";return o(i,{method:"POST",body:e,headers:t},n.trackHttp(X))},postUniqueKeysBulkSs:function(e,t){var i=r.telemetry+"/v1/keys/ss";return o(i,{method:"POST",body:e,headers:t},n.trackHttp(X))},postMetricsConfig:function(e,t){var i=r.telemetry+"/v1/metrics/config";return o(i,{method:"POST",body:e,headers:t},n.trackHttp(X),!0)},postMetricsUsage:function(e,t){var i=r.telemetry+"/v1/metrics/usage";return o(i,{method:"POST",body:e,headers:t},n.trackHttp(X),!0)}}}function ii(e,t,n,r){void 0===r&&(r="task");var i,s,o,a=0,u=!1,c=0;function l(){for(var n=[],s=0;s0},start:function(){for(var t=[],i=0;i0&&(a=oi(a,i)),o.setOnFullQueueCb((function(){a.isRunning()&&(n.info(ht,[o.name]),a.execute())})),a}function ui(e,t){var n=function(e,t){var n={};if(Array.isArray(e)&&l(t))for(var r=0;r0?n=t:e.error(vn,[Nn,bi]):e.error(mn,[Nn,bi]),n}(e,t);return n&&(ki[n]?(e.warn(Wt,[ki[n]+" factory/ies with this SDK Key"]),ki[n]++):(ki[n]=1,Object.keys(ki).length>1&&e.warn(Wt,["an instance of the Split factory"]))),n}function wi(e){var t=e();return function(){return Math.round(e()-t)}}var Ni=((Ei={})[k]=0,Ei[w]=1,Ei[N]=2,Ei),_i=((Ti={})[E]=0,Ti[b]=1,Ti[T]=2,Ti),Ai=((Ri={})[L]=1,Ri[O]=2,Ri[F]=3,Ri);function Ii(e,t){return{name:"telemetry config",isEmpty:function(){return!1},clear:function(){},pop:function(){var n,r,i=t.urls,s=t.scheduler,o=xr(t),a=function(e){var t=0;e.validFilters.forEach((function(e){"bySet"===e.type&&(t+=e.values.length)}));var n=e.groupedFilters.bySet.length;return{flagSetsTotal:t,flagSetsIgnored:t-n}}(t.sync.__splitFiltersValidation),u=a.flagSetsTotal,c=a.flagSetsIgnored;return Vn((n=t.mode,r=t.storage.type,{oM:Ni[n],st:r.toLowerCase(),aF:Object.keys(ki).length,rF:Object.keys(ki).reduce((function(e,t){return e+ki[t]-1}),0)}),{sE:t.streamingEnabled,rR:{sp:s.featuresRefreshRate/1e3,se:o?s.segmentsRefreshRate/1e3:void 0,ms:o?void 0:s.segmentsRefreshRate/1e3,im:s.impressionsRefreshRate/1e3,ev:s.eventsPushRate/1e3,te:s.telemetryRefreshRate/1e3},uO:{s:i.sdk!==lr.urls.sdk,e:i.events!==lr.urls.events,a:i.auth!==lr.urls.auth,st:i.streaming!==lr.urls.streaming,t:i.telemetry!==lr.urls.telemetry},iQ:s.impressionsQueueSize,eQ:s.eventsQueueSize,iM:_i[t.sync.impressionsMode],iL:!!t.impressionListener,hP:!1,tR:e.getTimeUntilReady(),tC:e.getTimeUntilReadyFromCache(),nR:e.getNonReadyUsage(),t:e.popTags(),i:t.integrations&&t.integrations.map((function(e){return e.type})),uC:t.userConsent?Ai[t.userConsent]:0,fsT:u,fsI:c})}}}var Oi=9e5;function Fi(e){var t=e.settings,n=t.log,r=t.core.key,i=e.splitApi,s=i.postUniqueKeysBulkCs,o=i.postUniqueKeysBulkSs,a=e.storage.uniqueKeys,u=si(n,void 0!==r?s:o,a,Oi);return a.setOnFullQueueCb((function(){u.isRunning()&&(n.info(ht,[a.name]),u.execute())})),u}function Li(e){var t=[ci(e),ai(e),hi(e),Fi(e)],n=function(e){var t=e.storage.telemetry,n=e.platform.now;if(t&&n){var r=e.settings,i=e.settings,s=i.log,o=i.scheduler.telemetryRefreshRate,a=e.splitApi,u=e.readiness,c=e.sdkReadinessManager,l=wi(n),f=oi(si(s,a.postMetricsUsage,t,o,void 0,0,!0),o);return u.gate.once(yi,(function(){t.recordTimeUntilReadyFromCache(l())})),c.incInternalReadyCbCount(),u.gate.once(vi,(function(){t.recordTimeUntilReady(l()),f.isRunning()&&si(s,a.postMetricsConfig,Ii(t,r),0,void 0,0,!0).execute()})),f}}(e);return{start:function(e){e||t.forEach((function(e){return e.start()})),n&&n.start()},stop:function(e){t.forEach((function(e){return e.stop()})),!e&&n&&n.stop()},isRunning:function(){return t.some((function(e){return e.isRunning()}))},execute:function(e){var r=e?[]:t.map((function(e){return e.execute()}));return n&&r.push(n.execute()),Promise.all(r)},isExecuting:function(){return t.some((function(e){return e.isExecuting()}))}}}var Di,Mi=600,Ki="PUSH_NONRETRYABLE_ERROR",xi="PUSH_RETRYABLE_ERROR",Pi="PUSH_SUBSYSTEM_UP",Ui="PUSH_SUBSYSTEM_DOWN",Bi="MEMBERSHIPS_MS_UPDATE",ji="MEMBERSHIPS_LS_UPDATE",qi="SEGMENT_UPDATE",zi="SPLIT_KILL",Wi="SPLIT_UPDATE",Gi="RB_SEGMENT_UPDATE",Hi="CONTROL",Qi="OCCUPANCY";function Vi(e){var t=e.userConsent;return!t||t===O}function Ji(e,t){return function(n){var r=n.settings,i=n.settings,s=i.log,o=i.streamingEnabled,a=i.sync.enabled,u=n.telemetryTracker,c=n.storage,l=n.readiness,f=e&&e(n),h=a&&o&&f&&t?t(n,f):void 0,p=Li(n);h&&(h.on(Pi,(function(){s.info(mt),f.isRunning()&&(f.stop(),u.streamingEvent(ve,Se)),f.syncAll()})),h.on(Ui,(function(){f.isRunning()?s.info(dt):(s.info(gt),f.start(),u.streamingEvent(ve,be))})));var g=!1,d=!0;return{pollingManager:f,pushManager:h,submitterManager:p,start:function(){return g=!0,p.start(!Vi(r)),Promise.resolve(!!c.validateCache&&c.validateCache()).then((function(e){g&&(d&&e&&l.splits.emit(gi),f&&(a?h?(d&&f.syncAll(),h.start()):f.start():d&&f.syncAll()),d=!1)}))},stop:function(){g=!1,h&&h.stop(),f&&f.isRunning()&&f.stop(),p.stop()},isRunning:function(){return g},flush:function(){return p.execute(!Vi(r))},shared:function(e,t,n){if(f){var r=f.add(e,t,n);return a&&h&&h.add(e,r),g&&(a?h?f.isRunning()?wr(n)&&r.start():r.execute():wr(n)&&r.start():t.isReady()||r.execute()),{stop:function(){var t=f.get(e);t&&(h&&h.remove(e),t.isRunning()&&t.stop(),f.remove(e))},flush:function(){return Promise.resolve()}}}}}}}!function(e){e.STREAMING_DISABLED="STREAMING_DISABLED",e.STREAMING_PAUSED="STREAMING_PAUSED",e.STREAMING_RESUMED="STREAMING_RESUMED",e.STREAMING_RESET="STREAMING_RESET"}(Di||(Di={}));var Yi=function(){function e(t,n,r){this.baseMillis=e.__TEST__BASE_MILLIS||n||e.DEFAULT_BASE_MILLIS,this.maxMillis=e.__TEST__MAX_MILLIS||r||e.DEFAULT_MAX_MILLIS,this.attempts=0,this.cb=t}return e.prototype.scheduleCall=function(){var e=this,t=Math.min(this.baseMillis*Math.pow(2,this.attempts),this.maxMillis);return this.timeoutID&&clearTimeout(this.timeoutID),this.timeoutID=setTimeout((function(){e.timeoutID=void 0,e.cb()}),t),this.attempts++,t},e.prototype.reset=function(){this.attempts=0,this.timeoutID&&(clearTimeout(this.timeoutID),this.timeoutID=void 0)},e.DEFAULT_BASE_MILLIS=1e3,e.DEFAULT_MAX_MILLIS=18e5,e}();var $i=[/control_pri$/,/control_sec$/],Zi=[10,20];function Xi(e,t,n){var r=function(e,t){var n=$i.map((function(e){return{regex:e,hasPublishers:!0,oTime:-1,cTime:-1}})),r=!0,i=!0;return{handleOpen:function(){t.streamingEvent(he),e.emit(Pi)},isStreamingUp:function(){return i&&r},handleOccupancyEvent:function(s,o,a){for(var u=0;uc.oTime){c.oTime=a,c.hasPublishers=0!==s;var l=n.some((function(e){return e.hasPublishers}));i&&(!l&&r?e.emit(Ui):l&&!r&&e.emit(Pi)),r=l}return}}},handleControlEvent:function(s,o,a){if(s!==Di.STREAMING_RESET)for(var u=0;uc.cTime&&(c.cTime=a,s===Di.STREAMING_DISABLED?(t.streamingEvent(pe,Te),e.emit(Ki)):r&&(s===Di.STREAMING_PAUSED&&i?(t.streamingEvent(pe,ke),e.emit(Ui)):s!==Di.STREAMING_RESUMED||i||(t.streamingEvent(pe,Re),e.emit(Pi))),i=s===Di.STREAMING_RESUMED))}else e.emit(s)}}}(t,n);return{handleOpen:function(){r.handleOpen()},handleError:function(r){var i=r;try{i=function(e){return l(e.data)&&(e.parsedData=JSON.parse(e.data)),e}(r)}catch(t){e.warn(_t,[t])}var s=i.parsedData&&i.parsedData.message||i.message;e.error(nn,[s]),!function(e){if(e.parsedData&&e.parsedData.code){var t=e.parsedData.code;if(n.streamingEvent(me,t),40140<=t&&t<=40149)return!0;if(4e4<=t&&t<=49999)return!1}else n.streamingEvent(ge,Ee);return!0}(i)?t.emit(Ki):t.emit(xi)},handleMessage:function(n){var i;try{if(i=function(e){if(e.data){var t=JSON.parse(e.data);return t.parsedData=JSON.parse(t.data),t.name&&"[meta]occupancy"===t.name&&(t.parsedData.type=Qi),t}}(n),!i)return}catch(t){return void e.warn(At,[t])}var s=i.parsedData,o=i.data,a=i.channel,u=i.timestamp;if(e.debug(We,[o]),r.isStreamingUp()||-1!==[Qi,Hi].indexOf(s.type))switch(s.type){case Wi:case qi:case Bi:case ji:case zi:case Gi:t.emit(s.type,s);break;case Qi:r.handleOccupancyEvent(s.metrics.publishers,a,u);break;case Hi:r.handleControlEvent(s.controlType,a,u)}}}}var es=1e4,ts=6e4,ns=10;function rs(e,t,n,r){var i,s,o;function a(t){var i,a,u,c=0,l=-1,f=!1,h=new Yi(p);function p(){if(i=!0,c>Math.max(l,t.getChangeNumber())){f=!1;var g=c;(s?new Promise((function(e){o=setTimeout((function(){s=void 0,n.execute(u,!0,a?c:void 0).then(e)}),s)})):n.execute(u,!0,a?c:void 0)).then((function(n){if(i){if(!1!==n){var s=t.getChangeNumber();l=s>-1?s:Math.max(l,g)}if(f)p();else{u&&r.trackUpdatesFromSSE(ne);var o=h.attempts+1;if(c<=l)return e.debug("Refresh completed"+(a?" bypassing the CDN":"")+" in "+o+" attempts."),void(i=!1);if(o(n.getChangeNumber(r)||-1)?(a=!1,t.execute(!1,r,!0,s?o:void 0).then((function(){if(i)if(a)c();else{var t=u.attempts+1;if(o<=(n.getChangeNumber(r)||-1))return e.debug("Refresh completed"+(s?" bypassing the CDN":"")+" in "+t+" attempts."),void(i=!1);if(t>>1|(21845&h)<<1;p=(61680&(p=(52428&p)>>>2|(13107&p)<<2))>>>4|(3855&p)<<4,f[h]=((65280&p)>>>8|(255&p)<<8)>>>1}var g=function(e,n,r){for(var i=e.length,s=0,o=new t(n);s>>c]=l}else for(a=new t(i),s=0;s>>15-e[s]);return a},d=new e(288);for(h=0;h<144;++h)d[h]=8;for(h=144;h<256;++h)d[h]=9;for(h=256;h<280;++h)d[h]=7;for(h=280;h<288;++h)d[h]=8;var m=new e(32);for(h=0;h<32;++h)m[h]=5;var v=g(d,9,1),y=g(m,5,1),S=function(e){for(var t=e[0],n=1;nt&&(t=e[n]);return t},b=function(e,t,n){var r=t/8|0;return(e[r]|e[r+1]<<8)>>(7&t)&n},E=function(e,t){var n=t/8|0;return(e[n]|e[n+1]<<8|e[n+2]<<16)>>(7&t)},T=["unexpected EOF","invalid block type","invalid length/literal","invalid distance","stream finished","no stream handler",,"no callback","invalid UTF-8 data","extra field too long","date not in range 1980-2099","filename too long","stream finishing","invalid zip data"],R=function(e,t,n){var r=new Error(t||T[e]);if(r.code=e,Error.captureStackTrace&&Error.captureStackTrace(r,R),!n)throw r;return r},k=function(o,a,c){var f=o.length;if(!f||c&&c.f&&!c.l)return a||new e(0);var h=!a||c,p=!c||c.i;c||(c={}),a||(a=new e(3*f));var d=function(t){var n=a.length;if(t>n){var r=new e(Math.max(2*n,t));r.set(a),a=r}},m=c.f||0,T=c.p||0,k=c.b||0,C=c.l,w=c.d,N=c.m,_=c.n,A=8*f;do{if(!C){m=b(o,T,1);var I=b(o,T+1,3);if(T+=3,!I){var O=o[(q=4+((T+7)/8|0))-4]|o[q-3]<<8,F=q+O;if(F>f){p&&R(0);break}h&&d(k+O),a.set(o.subarray(q,F),k),c.b=k+=O,c.p=T=8*F,c.f=m;continue}if(1==I)C=v,w=y,N=9,_=5;else if(2==I){var L=b(o,T,31)+257,D=b(o,T+10,15)+4,M=L+b(o,T+5,31)+1;T+=14;for(var K=new e(M),x=new e(19),P=0;P>>4)<16)K[P++]=q;else{var W=0,G=0;for(16==q?(G=3+b(o,T,3),T+=2,W=K[P-1]):17==q?(G=3+b(o,T,7),T+=3):18==q&&(G=11+b(o,T,127),T+=7);G--;)K[P++]=W}}var H=K.subarray(0,L),Q=K.subarray(L);N=S(H),_=S(Q),C=g(H,N,1),w=g(Q,_,1)}else R(1);if(T>A){p&&R(0);break}}h&&d(k+131072);for(var V=(1<>>4;if((T+=15&W)>A){p&&R(0);break}if(W||R(2),$<256)a[k++]=$;else{if(256==$){Y=T,C=null;break}var Z=$-254;if($>264){var X=r[P=$-257];Z=b(o,T,(1<>>4;ee||R(3),T+=15ⅇQ=l[te];if(te>3){X=i[te];Q+=E(o,T)&(1<A){p&&R(0);break}h&&d(k+131072);for(var ne=k+Z;kr.length)&&(s=r.length);var o=new(r instanceof t?t:r instanceof n?n:e)(s-i);return o.set(r.subarray(i,s)),o}(a,0,k)};return{gunzipSync:function(t,n){return k(t.subarray(function(e){31==e[0]&&139==e[1]&&8==e[2]||R(6,"invalid gzip data");var t=e[3],n=10;4&t&&(n+=e[10]|2+(e[11]<<8));for(var r=(t>>3&1)+(t>>4&1);r>0;r-=!e[n++]);return n+(2&t)}(t),-8),n||new e((i=(r=t).length,(r[i-4]|r[i-3]<<8|r[i-2]<<16|r[i-1]<<24)>>>0)));var r,i},unzlibSync:function(e,t){return k(((8!=(15&(n=e)[0])||n[0]>>>4>7||(n[0]<<8|n[1])%31)&&R(6,"invalid zlib data"),32&n[1]&&R(6,"invalid zlib data: preset dictionaries not supported"),e.subarray(2,-4)),t);var n}}}(),os="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";function as(e){var t=String(e).replace(/[=]+$/,"");if(t.length%4==1)throw new Error("'atob' failed: The string to be decoded is not correctly encoded.");for(var n="",r=0,i=void 0,s=void 0,o=0;s=t.charAt(o++);~s&&(i=r%4?64*i+s:s,r++%4)?n+=String.fromCharCode(255&i>>(-2*r&6)):0)s=os.indexOf(s);return n}var us=String.fromCharCode;function cs(e){var t,n,r,i,s;return n=function(e){if("string"!=typeof e)throw TypeError("Illegal argument: "+typeof e);var t=0;return function(){return t>=e.length?null:e.charCodeAt(t++)}}(e),i=[],s=[],r=t=function(){if(0===arguments.length)return s.join("")+us.apply(String,i);i.length+arguments.length>1024&&(s.push(us.apply(String,i)),i.length=0),Array.prototype.push.apply(i,arguments)},function(e,t){for(var n,r=null;null!==(n=null!==r?r:e());)n>=55296&&n<=57343&&null!==(r=e())&&r>=56320&&r<=57343?(t(1024*(n-55296)+r-56320+65536),r=null):t(n);null!==r&&t(r)}(n,(function(e){!function(e,t){var n=null;for("number"==typeof e&&(n=e,e=function(){return null});null!==n||null!==(n=e());)n<128?t(127&n):n<2048?(t(n>>6&31|192),t(63&n|128)):n<65536?(t(n>>12&15|224),t(n>>6&63|128),t(63&n|128)):(t(n>>18&7|240),t(n>>12&63|128),t(n>>6&63|128),t(63&n|128)),n=null}(e,r)})),t()} +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).splitio=t()}(this,(function(){"use strict";var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},e(t,n)};function t(t,n){if("function"!=typeof n&&null!==n)throw new TypeError("Class extends value "+String(n)+" is not a constructor or null");function r(){this.constructor=t}e(t,n),t.prototype=null===n?Object.create(n):(r.prototype=n.prototype,new r)}function n(e,t){var n;if(c(e))for(var r=Object.keys(e),i=0;i0)return t;e.error(vn,[n,r])}else e.error(mn,[n,r]);return!1}function Qn(e,t,n,r,i){if(void 0===r&&(r="feature flag names"),void 0===i&&(i="feature flag name"),Array.isArray(t)&&t.length>0){var s=[];if(t.forEach((function(t){var r=Hn(e,t,n,i);r&&s.push(r)})),s.length)return m(s)}return e.error(yn,[n,r]),!1}var Vn=Object.assign||function(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");e=Object(e);for(var t=1;t0}))}function er(e,t,n){var r,i,s={validFilters:[],queryString:null,groupedFilters:{bySet:[],byName:[],byPrefix:[]}};return t?Ie(n)?(e.warn(jt),s):Array.isArray(t)&&0!==t.length?(s.validFilters=t.filter((function(t,n){return t&&(r=t.type,Jn.some((function(e){return e.type===r})))&&Array.isArray(t.values)?(s.groupedFilters[t.type]=s.groupedFilters[t.type].concat(t.values),!0):(e.warn(qt,[n]),!1);var r})),Jn.forEach((function(t){var n=t.type,r=t.maxLength;s.groupedFilters[n].length>0&&(s.groupedFilters[n]=function(e,t,n,r){var i=Qn(e,n,wn,t+" filter",t+" filter value");if(i){if("bySet"===t&&(i=Zn(e,i,wn)),i.length>r)throw new Error(r+" unique values can be specified at most for '"+t+"' filter. You passed "+i.length+". Please consider reducing the amount or using other filter.");i.sort()}return i||[]}(e,n,s.groupedFilters[n],r))})),Xn(s.validFilters,"bySet")&&((Xn(s.validFilters,"byName")||Xn(s.validFilters,"byPrefix"))&&e.error(kn),Vn(s.groupedFilters,{byName:[],byPrefix:[]})),s.queryString=(r=s.groupedFilters,i=[],Jn.forEach((function(e){var t=e.type,n=e.queryParam,s=r[t];s.length>0&&i.push(n+s.map((function(e){return encodeURIComponent(e)})).join(","))})),i.length>0?"&"+i.join("&"):null),e.debug(Ve,[s.queryString]),s):(e.warn(zt),s):s}var tr,nr=250;function rr(e,t,n,r){if(null==t)return e.error(pn,[n,r]),!1;if(a(t))return e.warn(Mt,[n,r,t]),d(t);if(l(t)){if((t=t.trim()).length>0&&t.length<=nr)return t;0===t.length?e.error(vn,[n,r]):t.length>nr&&e.error(gn,[n,r])}else e.error(mn,[n,r]);return!1}function ir(e,t,n){if(c(t)){var r=rr(e,t.matchingKey,n,"matchingKey"),i=rr(e,t.bucketingKey,n,"bucketingKey");return r&&i?{matchingKey:r,bucketingKey:i}:(e.error(dn,[n]),!1)}return rr(e,t,n,"key")}function sr(e,t,n,r){var i=n.splits,s=n.rbSegments,o=n.segments,a=n.largeSegments,u=t.splitChanges,c=u.ff,l=u.rbs;e.debug("storage: set feature flags and segments"+(r?" for key "+r:"")),i&&c&&(i.clear(),i.update(c.d,[],c.t)),s&&l&&(s.clear(),s.update(l.d,[],l.t));var f=t.segmentChanges;if(r){var h=t.memberships&&t.memberships[r];!h&&f&&(h={ms:{k:f.filter((function(e){return e.added.indexOf(r)>-1})).map((function(e){return{n:e.name}}))}}),h&&(h.ms&&o.resetSegments(h.ms),h.ls&&a&&a.resetSegments(h.ls))}else f&&(o.clear(),f.forEach((function(e){o.update(e.name,e.added,e.removed,e.till)})))}!function(e){e.FlagName="Invalid flag name (max 100 chars, no spaces)",e.Treatment="Invalid treatment (max 100 chars and must match pattern)"}(tr||(tr={}));var or=/^[0-9]+[.a-zA-Z0-9_-]*$|^[a-zA-Z]+[a-zA-Z0-9_-]*$/;function ar(e){var t=c(e)?e.treatment:e;return!(!l(t)||t.length>100)&&or.test(t)}function ur(e,t){if(void 0!==t){if(ar(t))return t;e.error("Fallback treatments - Discarded fallback: "+tr.Treatment)}}function cr(e,t){var n={};return c(t)?(Object.keys(t).forEach((function(r){var i,s=t[r];(i=r).length<=100&&!i.includes(" ")?ar(s)?n[r]=s:e.error("Fallback treatments - Discarded treatment for flag '"+r+"': "+tr.Treatment):e.error("Fallback treatments - Discarded flag '"+r+"': "+tr.FlagName)})),n):n}var lr={mode:k,core:{authorizationKey:void 0,key:void 0,labelsEnabled:!0,IPAddressesEnabled:void 0},scheduler:{featuresRefreshRate:60,segmentsRefreshRate:60,telemetryRefreshRate:3600,impressionsRefreshRate:300,offlineRefreshRate:15,eventsPushRate:60,eventsQueueSize:500,impressionsQueueSize:3e4,pushRetryBackoffBase:1},urls:{sdk:"https://sdk.split.io/api",events:"https://events.split.io/api",auth:"https://auth.split.io/api",streaming:"https://streaming.split.io",telemetry:"https://telemetry.split.io/api"},storage:void 0,debug:void 0,impressionListener:void 0,version:void 0,integrations:void 0,streamingEnabled:!0,sync:{splitFilters:void 0,impressionsMode:E,enabled:!0,flagSpecVersion:Ce},log:void 0};function fr(e){return Math.round(1e3*e)}function hr(e,t){var n=t.defaults,r=t.runtime,i=t.storage,o=t.integrations,a=t.logger,u=t.consent,l=t.flagSpec,p=h({},lr,n,e);p.features=s(e,"features");var g=a(p);p.log=g;var d=p.sync;d.impressionsMode=function(e,t){return t=f(t),[b,E,T].indexOf(t)>-1?t:(e.error(Sn,["impressionsMode",[b,E,T],E]),E)}(g,d.impressionsMode);var m,v,y,S=p.scheduler,_=p.startup;S.featuresRefreshRate=fr(S.featuresRefreshRate),S.segmentsRefreshRate=fr(S.segmentsRefreshRate),S.offlineRefreshRate=fr(S.offlineRefreshRate),S.eventsPushRate=fr(S.eventsPushRate),S.telemetryRefreshRate=fr((m="telemetryRefreshRate",(v=S.telemetryRefreshRate)>=(y=60)?v:(g.error(Tn,[m,y]),y))),void 0===s(e,"scheduler.impressionsRefreshRate")&&d.impressionsMode===b&&(S.impressionsRefreshRate=60),S.impressionsRefreshRate=fr(S.impressionsRefreshRate),S.metricsRefreshRate&&g.warn("`metricsRefreshRate` will be deprecated soon. For configuring telemetry rates, update `telemetryRefreshRate` value in configs"),_.requestTimeoutBeforeReady=fr(_.requestTimeoutBeforeReady),_.readyTimeout=fr(_.readyTimeout),_.eventsFirstPushWindow=fr(_.eventsFirstPushWindow),p.mode=function(e,t){if("localhost"===e)return R;if(-1===[k,C,w,N].indexOf(t))throw Error("Invalid mode provided");return t}(p.core.authorizationKey,p.mode),i&&(p.storage=i(p)),p.initialRolloutPlan&&(p.initialRolloutPlan=function(e,t){var n=t.mode,r=t.initialRolloutPlan;if(!Ie(n))return c(r)&&c(r.splitChanges)?r:void e.error("storage: invalid rollout plan provided");e.warn("storage: initial rollout plan is ignored in consumer mode")}(g,p));var A=p.core.key;t.acceptKey?p.mode===R&&void 0===A?p.core.key="localhost_key":p.core.key=ir(g,A,_n):(void 0!==A&&g.warn("Provided `key` is ignored in server-side SDK."),p.core.key=void 0),p.runtime=r(p),o&&(p.integrations=o(p)),!1!==p.streamingEnabled&&(p.streamingEnabled=!0,S.pushRetryBackoffBase=fr(S.pushRetryBackoffBase)),!1!==d.enabled&&(d.enabled=!0);var I=er(g,d.splitFilters,p.mode);return d.splitFilters=I.validFilters,d.__splitFiltersValidation=I,d.flagSpecVersion=l?l(p):Ce,p.userConsent=u?u(p):void 0,p.fallbackTreatments=p.fallbackTreatments?function(e,t){if(c(t))return{global:ur(e,t.global),byFlag:cr(e,t.byFlag)};e.error("Fallback treatments - Discarded configuration: it must be an object with optional `global` and `byFlag` properties")}(g,p.fallbackTreatments):void 0,p}function pr(e){return null!==e&&"object"==typeof e&&"function"==typeof e.debug&&"function"==typeof e.info&&"function"==typeof e.warn&&"function"==typeof e.error}var gr={DEBUG:"DEBUG",INFO:"INFO",WARN:"WARN",ERROR:"ERROR",NONE:"NONE"},dr={DEBUG:1,INFO:2,WARN:3,ERROR:4,NONE:5},mr={debug:function(e){console.log("[DEBUG] "+e)},info:function(e){console.log("[INFO] "+e)},warn:function(e){console.log("[WARN] "+e)},error:function(e){console.log("[ERROR] "+e)}};function vr(e){return!!n(gr,(function(t){return e===t}))}function yr(e,t){void 0===e&&(e=""),void 0===t&&(t=[]);var n=0;return e.replace(/%s/g,(function(){var e=t[n++];if(c(e)||Array.isArray(e))try{e=JSON.stringify(e)}catch(e){}return e}))}var Sr,br={prefix:"splitio",logLevel:gr.NONE},Er=function(){function e(e,t){this.options=Vn({},br,e),this.codes=t||new Map,this.logLevel=dr[this.options.logLevel]}return e.prototype.setLogLevel=function(e){this.options.logLevel=e,this.logLevel=dr[e]},e.prototype.setLogger=function(e){if(e){if(pr(e))return void(this.logger=e);this.error("Invalid `logger` instance. It must be an object with `debug`, `info`, `warn` and `error` methods. Defaulting to `console.log`")}this.logger=void 0},e.prototype.debug=function(e,t){this._shouldLog(dr.DEBUG)&&this._log("debug",e,t)},e.prototype.info=function(e,t){this._shouldLog(dr.INFO)&&this._log("info",e,t)},e.prototype.warn=function(e,t){this._shouldLog(dr.WARN)&&this._log("warn",e,t)},e.prototype.error=function(e,t){this._shouldLog(dr.ERROR)&&this._log("error",e,t)},e.prototype._log=function(e,t,n){if("number"==typeof t){var r=this.codes.get(t);t=r?yr(r,n):"Message code "+t+(n?", with args: "+n.toString():"")}else n&&(t=yr(t,n));if(this.options.prefix&&(t=this.options.prefix+" => "+t),this.logger)try{return void this.logger[e](t)}catch(e){}mr[e](t)},e.prototype._shouldLog=function(e){return e>=this.logLevel},e}();try{var Tr=localStorage.getItem("splitio_debug")||"";Sr=/^(enabled?|on)/i.test(Tr)?gr.DEBUG:vr(Tr)?Tr:gr.NONE}catch(Ei){}var Rr={startup:{requestTimeoutBeforeReady:5,retriesOnFailureBeforeReady:1,readyTimeout:10,eventsFirstPushWindow:10},userConsent:O,version:"browserjs-1.6.0",debug:Sr};var kr=function(){function e(){}return e.prototype.update=function(e,t,n){var r=this;this.setChangeNumber(n);var i=e.map((function(e){return r.addSplit(e)})).some((function(e){return e}));return t.map((function(e){return r.removeSplit(e.name)})).some((function(e){return e}))||i},e.prototype.getSplits=function(e){var t=this,n={};return e.forEach((function(e){n[e]=t.getSplit(e)})),n},e.prototype.getAll=function(){var e=this;return this.getSplitNames().map((function(t){return e.getSplit(t)}))},e.prototype.killLocally=function(e,t,n){var r=this.getSplit(e);if(r&&(!r.changeNumber||r.changeNumber0)}function wr(e){return e.splits.usesSegments()||e.rbSegments.usesSegments()}var Nr=function(e){function n(t){var n=e.call(this)||this;return n.splitsCache={},n.ttCache={},n.changeNumber=-1,n.segmentsCount=0,n.flagSetsCache={},n.flagSetsFilter=t?t.groupedFilters.bySet:[],n}return t(n,e),n.prototype.clear=function(){this.splitsCache={},this.ttCache={},this.changeNumber=-1,this.segmentsCount=0,this.flagSetsCache={}},n.prototype.addSplit=function(e){var t=e.name,n=this.getSplit(t);if(n){var r=n.trafficTypeName;this.ttCache[r]--,this.ttCache[r]||delete this.ttCache[r],this.removeFromFlagSets(n.name,n.sets),Cr(n)&&this.segmentsCount--}this.splitsCache[t]=e;var i=e.trafficTypeName;return this.ttCache[i]=(this.ttCache[i]||0)+1,this.addToFlagSets(e),Cr(e)&&this.segmentsCount++,!0},n.prototype.removeSplit=function(e){var t=this.getSplit(e);if(!t)return!1;delete this.splitsCache[e];var n=t.trafficTypeName;return this.ttCache[n]--,this.ttCache[n]||delete this.ttCache[n],this.removeFromFlagSets(t.name,t.sets),Cr(t)&&this.segmentsCount--,!0},n.prototype.getSplit=function(e){return this.splitsCache[e]||null},n.prototype.setChangeNumber=function(e){return this.changeNumber=e,!0},n.prototype.getChangeNumber=function(){return this.changeNumber},n.prototype.getSplitNames=function(){return Object.keys(this.splitsCache)},n.prototype.trafficTypeExists=function(e){return a(this.ttCache[e])&&this.ttCache[e]>0},n.prototype.usesSegments=function(){return-1===this.getChangeNumber()||this.segmentsCount>0},n.prototype.getNamesByFlagSets=function(e){var t=this;return e.map((function(e){return t.flagSetsCache[e]||new Set}))},n.prototype.addToFlagSets=function(e){var t=this;e.sets&&e.sets.forEach((function(n){t.flagSetsFilter.length>0&&!t.flagSetsFilter.some((function(e){return e===n}))||(t.flagSetsCache[n]||(t.flagSetsCache[n]=new Set([])),t.flagSetsCache[n].add(e.name))}))},n.prototype.removeFromFlagSets=function(e,t){var n=this;t&&t.forEach((function(t){n.removeNames(t,e)}))},n.prototype.removeNames=function(e,t){this.flagSetsCache[e]&&(this.flagSetsCache[e].delete(t),0===this.flagSetsCache[e].size&&delete this.flagSetsCache[e])},n}(kr),_r=function(){function e(){}return e.prototype.clear=function(){this.resetSegments({})},e.prototype.registerSegments=function(){return!1},e.prototype.update=function(){return!1},e.prototype.resetSegments=function(e){var t=this;this.setChangeNumber(e.cn);var n=e,r=n.added,i=n.removed;if(r&&i){var s=!1;return r.forEach((function(e){s=t.addSegment(e)||s})),i.forEach((function(e){s=t.removeSegment(e)||s})),s}var o=(e.k||[]).map((function(e){return e.n})).sort(),a=this.getRegisteredSegments().sort();if(!o.length&&!a.length)return!1;for(var u=0;u0&&this.queue.length>=this.maxQueue&&this.onFullQueue&&this.onFullQueue()},e.prototype.clear=function(){this.queue=[]},e.prototype.pop=function(e){var t=this.queue;return this.clear(),e?e.concat(t):t},e.prototype.isEmpty=function(){return 0===this.queue.length},e}(),Or=function(){function e(e){void 0===e&&(e=0),this.name="events",this.maxQueue=e,this.queue=[],this.queueByteSize=0}return e.prototype.setOnFullQueueCb=function(e){this.onFullQueue=e},e.prototype.track=function(e,t){return void 0===t&&(t=0),this.queueByteSize+=t,this.queue.push(e),this._checkForFlush(),!0},e.prototype.clear=function(){this.queue=[],this.queueByteSize=0},e.prototype.pop=function(e){var t=this.queue;return this.clear(),e?e.concat(t):t},e.prototype.isEmpty=function(){return 0===this.queue.length},e.prototype._checkForFlush=function(){(this.queueByteSize>5242880||this.maxQueue>0&&this.queue.length>=this.maxQueue)&&this.onFullQueue&&this.onFullQueue()},e}(),Fr=36e5;function Lr(e){return e-e%Fr}var Dr=function(){function e(e){void 0===e&&(e=3e4),this.name="impression counts",this.cache={},this.cacheSize=0,this.maxStorage=e}return e.prototype._makeKey=function(e,t){return e+"::"+Lr(t)},e.prototype.track=function(e,t,n){var r=this._makeKey(e,t),i=this.cache[r];this.cache[r]=i?i+n:n,this.onFullQueue&&(this.cacheSize=this.cacheSize+n,this.cacheSize>=this.maxStorage&&this.onFullQueue())},e.prototype.pop=function(e){var t=this.cache;return this.clear(),e?(Object.keys(t).forEach((function(n){e[n]?e[n]+=t[n]:e[n]=t[n]})),e):t},e.prototype.clear=function(){this.cache={},this.cacheSize=0},e.prototype.isEmpty=function(){return 0===Object.keys(this.cache).length},e}();function Mr(e){return c(e)?e.matchingKey:e}function Kr(e){return c(e)?{matchingKey:e.matchingKey,bucketingKey:e.bucketingKey}:{matchingKey:e,bucketingKey:e}}function xr(e){return!e.core.key}function Pr(e){var t=Math.min(22,Math.max(0,Math.ceil(Math.log(e)/Math.log(1.5))));return u(t)?0:t}var Ur=.001;function Br(e){var t=e.settings;return t.mode!==R&&(xr(t)||Math.random()<=Ur)}var jr=function(){function e(e,t,n){this.splits=e,this.segments=t,this.largeSegments=n,this.name="telemetry stats",this.e=!0,this.notReadyUsage=0,this.impressionStats=[0,0,0],this.eventStats=[0,0],this.lastSync={},this.httpErrors={},this.httpLatencies={},this.authRejections=0,this.tokenRefreshes=0,this.streamingEvents=[],this.tags=[],this.exceptions={},this.latencies={},this.updatesFromSSE={}}return e.prototype.isEmpty=function(){return this.e},e.prototype.clear=function(){},e.prototype.pop=function(){return this.e=!0,{lS:this.getLastSynchronization(),mL:this.popLatencies(),mE:this.popExceptions(),hE:this.popHttpErrors(),hL:this.popHttpLatencies(),tR:this.popTokenRefreshes(),aR:this.popAuthRejections(),iQ:this.getImpressionStats(H),iDe:this.getImpressionStats(V),iDr:this.getImpressionStats(Q),spC:this.splits&&this.splits.getSplitNames().length,seC:this.segments&&this.segments.getRegisteredSegments().length,skC:this.segments&&this.segments.getKeysCount(),lsC:this.largeSegments&&this.largeSegments.getRegisteredSegments().length,lskC:this.largeSegments&&this.largeSegments.getKeysCount(),sL:this.getSessionLength(),eQ:this.getEventStats(H),eD:this.getEventStats(Q),sE:this.popStreamingEvents(),t:this.popTags(),ufs:this.popUpdatesFromSSE()}},e.prototype.getTimeUntilReady=function(){return this.timeUntilReady},e.prototype.recordTimeUntilReady=function(e){this.timeUntilReady=e},e.prototype.getTimeUntilReadyFromCache=function(){return this.timeUntilReadyFromCache},e.prototype.recordTimeUntilReadyFromCache=function(e){this.timeUntilReadyFromCache=e},e.prototype.getNonReadyUsage=function(){return this.notReadyUsage},e.prototype.recordNonReadyUsage=function(){this.notReadyUsage++},e.prototype.getImpressionStats=function(e){return this.impressionStats[e]},e.prototype.recordImpressionStats=function(e,t){this.impressionStats[e]+=t,this.e=!1},e.prototype.getEventStats=function(e){return this.eventStats[e]},e.prototype.recordEventStats=function(e,t){this.eventStats[e]+=t,this.e=!1},e.prototype.getLastSynchronization=function(){return this.lastSync},e.prototype.recordSuccessfulSync=function(e,t){this.lastSync[e]=t,this.e=!1},e.prototype.popHttpErrors=function(){var e=this.httpErrors;return this.httpErrors={},e},e.prototype.recordHttpError=function(e,t){var n=this.httpErrors[e]=this.httpErrors[e]||{};n[t]=(n[t]||0)+1,this.e=!1},e.prototype.popHttpLatencies=function(){var e=this.httpLatencies;return this.httpLatencies={},e},e.prototype.recordHttpLatency=function(e,t){(this.httpLatencies[e]=this.httpLatencies[e]||[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0])[Pr(t)]++,this.e=!1},e.prototype.popAuthRejections=function(){var e=this.authRejections;return this.authRejections=0,e},e.prototype.recordAuthRejections=function(){this.authRejections++,this.e=!1},e.prototype.popTokenRefreshes=function(){var e=this.tokenRefreshes;return this.tokenRefreshes=0,e},e.prototype.recordTokenRefreshes=function(){this.tokenRefreshes++,this.e=!1},e.prototype.popStreamingEvents=function(){return this.streamingEvents.splice(0)},e.prototype.recordStreamingEvents=function(e){this.streamingEvents.length<20&&this.streamingEvents.push(e),this.e=!1},e.prototype.popTags=function(){return this.tags.splice(0)},e.prototype.addTag=function(e){this.tags.length<10&&this.tags.push(e),this.e=!1},e.prototype.getSessionLength=function(){return this.sessionLength},e.prototype.recordSessionLength=function(e){this.sessionLength=e,this.e=!1},e.prototype.popExceptions=function(){var e=this.exceptions;return this.exceptions={},e},e.prototype.recordException=function(e){this.exceptions[e]=(this.exceptions[e]||0)+1,this.e=!1},e.prototype.popLatencies=function(){var e=this.latencies;return this.latencies={},e},e.prototype.recordLatency=function(e,t){(this.latencies[e]=this.latencies[e]||[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0])[Pr(t)]++,this.e=!1},e.prototype.popUpdatesFromSSE=function(){var e=this.updatesFromSSE;return this.updatesFromSSE={},e},e.prototype.recordUpdatesFromSSE=function(e){this.updatesFromSSE[e]=(this.updatesFromSSE[e]||0)+1,this.e=!1},e}();function qr(e){if(Array.from)return Array.from(e);var t=[];return e.forEach((function(e){t.push(e)})),t}var zr=function(){function e(e){void 0===e&&(e=3e4),this.name="unique keys",this.uniqueTrackerSize=0,this.uniqueKeysTracker={},this.maxStorage=e}return e.prototype.setOnFullQueueCb=function(e){this.onFullQueue=e},e.prototype.track=function(e,t){this.uniqueKeysTracker[e]||(this.uniqueKeysTracker[e]=new Set);var n=this.uniqueKeysTracker[e];n.has(t)||(n.add(t),this.uniqueTrackerSize++),this.uniqueTrackerSize>=this.maxStorage&&this.onFullQueue&&this.onFullQueue()},e.prototype.clear=function(){this.uniqueTrackerSize=0,this.uniqueKeysTracker={}},e.prototype.pop=function(){var e=this.uniqueKeysTracker;return this.clear(),this.fromUniqueKeysCollector(e)},e.prototype.isEmpty=function(){return 0===Object.keys(this.uniqueKeysTracker).length},e.prototype.fromUniqueKeysCollector=function(e){for(var t=[],n=Object.keys(e),r=0;r0},e}();function Gr(e){var t=e.settings,n=t.scheduler,r=n.impressionsQueueSize,i=n.eventsQueueSize,s=t.sync.__splitFiltersValidation,o=new Nr(s),a=new Wr,u=new Ar,c={splits:o,rbSegments:a,segments:u,largeSegments:new Ar,impressions:new Ir(r),impressionCounts:new Dr,events:new Or(i),telemetry:Br(e)?new jr(o,u):void 0,uniqueKeys:new zr,destroy:function(){},shared:function(){return{splits:this.splits,rbSegments:this.rbSegments,segments:new Ar,largeSegments:new Ar,impressions:this.impressions,impressionCounts:this.impressionCounts,events:this.events,telemetry:this.telemetry,uniqueKeys:this.uniqueKeys,destroy:function(){}}}};if(e.settings.mode===R){var l=function(){return!0};c.impressions.track=l,c.events.track=l,c.impressionCounts.track=l,c.uniqueKeys.track=l}return c}function Hr(e){var t=Gr(e);return t.validateCache=function(){return Promise.resolve(!0)},t}Gr.type=_,Hr.type=_;var Qr=gr.NONE;var Vr=[F,O,L];var Jr={defaults:Rr,acceptKey:!0,runtime:function(){return{ip:!1,hostname:!1}},storage:function(e){var t=e.storage,n=void 0===t?Gr:t,r=e.log,i=e.mode;if("function"==typeof n&&-1!==[_,A,I].indexOf(n.type)||(n=Gr,r.error(324)),i===R&&n.type===A)return Hr;if(-1===[R,k].indexOf(i)){if(n.type!==I)throw new Error("A PluggableStorage instance is required on consumer mode")}else n.type===I&&(n=Gr,r.error(324,[" It requires consumer mode."]));return n},integrations:function(e){return function(e,t,n){var r=e.integrations,i=e.log;if(!Array.isArray(r)||0===r.length)return[];var s=r.filter(t),o=r.length-s.length;return o&&i.warn(Bt,[o,n||""]),s}(e,(function(e){return"function"==typeof e}),"Integration items must be functions that initialize the integrations")},logger:function(e){var t,n=e.debug,r=e.logger,i=Qr;if(void 0!==n){if(function(e){return pr(e)&&"function"==typeof e.setLogLevel}(n))return n.setLogger(r),n;i="boolean"==typeof(t=e.debug)?t?gr.DEBUG:gr.NONE:"string"==typeof t&&vr(t)?t:void 0}var s=new Er({logLevel:i||Qr});return s.setLogger(r),i||s._log("error","Invalid `debug` value at config. Logs will be disabled."),s},consent:function(e){var t=e.userConsent,n=e.log;return t=f(t),Vr.indexOf(t)>-1?t:(n.error(Sn,["userConsent",Vr,O]),O)}};var Yr=new Set(["splitsdkclientkey","splitsdkversion","splitsdkmachineip","splitsdkmachinename","splitsdkimpressionsmode","host","referrer","content-type","content-length","content-encoding","accept","keep-alive","x-fastly-debug"]);function $r(e,t){var n;if(null===(n=e.sync.requestOptions)||void 0===n?void 0:n.getHeaderOverrides)try{var r=e.sync.requestOptions.getHeaderOverrides({headers:Vn({},t)});Object.keys(r).filter((function(e){return!Yr.has(e.toLowerCase())})).forEach((function(e){return t[e]=r[e]}))}catch(t){e.log.error("Problem adding custom headers to request decorator: "+t)}return t}function Zr(e,t){return e<1?t:new Promise((function(n,r){var i=setTimeout((function(){r(new Error("Operation timed out because it exceeded the configured time limit of "+e+" ms."))}),e);t.then((function(e){clearTimeout(i),n(e)}),(function(e){clearTimeout(i),r(e)}))}))}var Xr=100,ei="Global fetch API is not available.";var ti={headers:{"Cache-Control":"no-cache"}};function ni(e){return"users="+encodeURIComponent(e)}function ri(e,t,n){var r=e.urls,i=e.sync.__splitFiltersValidation&&e.sync.__splitFiltersValidation.queryString,s=e.sync.impressionsMode,o=function(e,t){var n=t.getOptions,r=t.getFetch,i=e.log,s=e.core.authorizationKey,o=e.version,a=e.runtime,u=a.ip,c=a.hostname,l=n&&n(e),f=r&&r(e);f||i.error(en,[ei]);var h={Accept:"application/json","Content-Type":"application/json",Authorization:"Bearer "+s,SplitSDKVersion:o};return u&&(h.SplitSDKMachineIP=u),c&&(h.SplitSDKMachineName=c.replace(/[^\x00-\xFF]/g,"")),function(t,n,r,s){void 0===n&&(n={}),void 0===r&&(r=function(){}),void 0===s&&(s=!1);var o=Vn({headers:$r(e,Vn({},h,n.headers||{})),method:n.method||"GET",body:n.body},l);return f?f(t,o).then((function(e){return e.ok?(r(),e):Zr(Xr,e.text()).then((function(t){return Promise.reject({response:e,message:t})}),(function(){return Promise.reject({response:e})}))})).catch((function(e){var n=e&&e.response,o="";o=n?404===n.status?"Invalid SDK key or resource not found.":e.message:e.message||"Network Error",n&&403===n.status||i[s?"info":"error"](bn,[n?"status code "+n.status:"no status code",t,o]);var a=new Error(o);throw a.statusCode=n&&n.status,r(a),a})):Promise.reject(new Error(ei))}}(e,t);return{getSdkAPIHealthCheck:function(){var e=r.sdk+"/version";return o(e).then((function(){return!0})).catch((function(){return!1}))},getEventsAPIHealthCheck:function(){var e=r.events+"/version";return o(e).then((function(){return!0})).catch((function(){return!1}))},fetchAuth:function(t){var i=r.auth+"/v2/auth?s="+e.sync.flagSpecVersion;if(t){var s=t.map(ni).join("&");s&&(i+="&"+s)}return o(i,void 0,n.trackHttp(ee))},fetchSplitChanges:function(t,s,a,u){var c=r.sdk+"/splitChanges?s="+e.sync.flagSpecVersion+"&since="+t+(u?"&rbSince="+u:"")+(i||"")+(a?"&till="+a:"");return o(c,s?ti:void 0,n.trackHttp(J)).catch((function(t){throw 414===t.statusCode&&e.log.error(Rn),t}))},fetchSegmentChanges:function(e,t,i,s){var a=r.sdk+"/segmentChanges/"+t+"?since="+e+(s?"&till="+s:"");return o(a,i?ti:void 0,n.trackHttp(te))},fetchMemberships:function(e,t,i){var s=r.sdk+"/memberships/"+encodeURIComponent(e)+(i?"?till="+i:"");return o(s,t?ti:void 0,n.trackHttp(ne))},postEventsBulk:function(e,t){var i=r.events+"/events/bulk";return o(i,{method:"POST",body:e,headers:t},n.trackHttp(Z))},postTestImpressionsBulk:function(e,t){var i=r.events+"/testImpressions/bulk";return o(i,{method:"POST",body:e,headers:Vn({SplitSDKImpressionsMode:s},t)},n.trackHttp(Y))},postTestImpressionsCount:function(e,t){var i=r.events+"/testImpressions/count";return o(i,{method:"POST",body:e,headers:t},n.trackHttp($))},postUniqueKeysBulkCs:function(e,t){var i=r.telemetry+"/v1/keys/cs";return o(i,{method:"POST",body:e,headers:t},n.trackHttp(X))},postUniqueKeysBulkSs:function(e,t){var i=r.telemetry+"/v1/keys/ss";return o(i,{method:"POST",body:e,headers:t},n.trackHttp(X))},postMetricsConfig:function(e,t){var i=r.telemetry+"/v1/metrics/config";return o(i,{method:"POST",body:e,headers:t},n.trackHttp(X),!0)},postMetricsUsage:function(e,t){var i=r.telemetry+"/v1/metrics/usage";return o(i,{method:"POST",body:e,headers:t},n.trackHttp(X),!0)}}}function ii(e,t,n,r){void 0===r&&(r="task");var i,s,o,a=0,u=!1,c=0;function l(){for(var n=[],s=0;s0},start:function(){for(var t=[],i=0;i0&&(a=oi(a,i)),o.setOnFullQueueCb((function(){a.isRunning()&&(n.info(ht,[o.name]),a.execute())})),a}function ui(e,t){var n=function(e,t){var n={};if(Array.isArray(e)&&l(t))for(var r=0;r0?n=t:e.error(vn,[Nn,bi]):e.error(mn,[Nn,bi]),n}(e,t);return n&&(ki[n]?(e.warn(Wt,[ki[n]+" factory/ies with this SDK Key"]),ki[n]++):(ki[n]=1,Object.keys(ki).length>1&&e.warn(Wt,["an instance of the Split factory"]))),n}function wi(e){var t=e();return function(){return Math.round(e()-t)}}var Ni=((Ei={})[k]=0,Ei[w]=1,Ei[N]=2,Ei),_i=((Ti={})[E]=0,Ti[b]=1,Ti[T]=2,Ti),Ai=((Ri={})[L]=1,Ri[O]=2,Ri[F]=3,Ri);function Ii(e,t){return{name:"telemetry config",isEmpty:function(){return!1},clear:function(){},pop:function(){var n,r,i=t.urls,s=t.scheduler,o=xr(t),a=function(e){var t=0;e.validFilters.forEach((function(e){"bySet"===e.type&&(t+=e.values.length)}));var n=e.groupedFilters.bySet.length;return{flagSetsTotal:t,flagSetsIgnored:t-n}}(t.sync.__splitFiltersValidation),u=a.flagSetsTotal,c=a.flagSetsIgnored;return Vn((n=t.mode,r=t.storage.type,{oM:Ni[n],st:r.toLowerCase(),aF:Object.keys(ki).length,rF:Object.keys(ki).reduce((function(e,t){return e+ki[t]-1}),0)}),{sE:t.streamingEnabled,rR:{sp:s.featuresRefreshRate/1e3,se:o?s.segmentsRefreshRate/1e3:void 0,ms:o?void 0:s.segmentsRefreshRate/1e3,im:s.impressionsRefreshRate/1e3,ev:s.eventsPushRate/1e3,te:s.telemetryRefreshRate/1e3},uO:{s:i.sdk!==lr.urls.sdk,e:i.events!==lr.urls.events,a:i.auth!==lr.urls.auth,st:i.streaming!==lr.urls.streaming,t:i.telemetry!==lr.urls.telemetry},iQ:s.impressionsQueueSize,eQ:s.eventsQueueSize,iM:_i[t.sync.impressionsMode],iL:!!t.impressionListener,hP:!1,tR:e.getTimeUntilReady(),tC:e.getTimeUntilReadyFromCache(),nR:e.getNonReadyUsage(),t:e.popTags(),i:t.integrations&&t.integrations.map((function(e){return e.type})),uC:t.userConsent?Ai[t.userConsent]:0,fsT:u,fsI:c})}}}var Oi=9e5;function Fi(e){var t=e.settings,n=t.log,r=t.core.key,i=e.splitApi,s=i.postUniqueKeysBulkCs,o=i.postUniqueKeysBulkSs,a=e.storage.uniqueKeys,u=si(n,void 0!==r?s:o,a,Oi);return a.setOnFullQueueCb((function(){u.isRunning()&&(n.info(ht,[a.name]),u.execute())})),u}function Li(e){var t=[ci(e),ai(e),hi(e),Fi(e)],n=function(e){var t=e.storage.telemetry,n=e.platform.now;if(t&&n){var r=e.settings,i=e.settings,s=i.log,o=i.scheduler.telemetryRefreshRate,a=e.splitApi,u=e.readiness,c=e.sdkReadinessManager,l=wi(n),f=oi(si(s,a.postMetricsUsage,t,o,void 0,0,!0),o);return u.gate.once(yi,(function(){t.recordTimeUntilReadyFromCache(l())})),c.incInternalReadyCbCount(),u.gate.once(vi,(function(){t.recordTimeUntilReady(l()),f.isRunning()&&si(s,a.postMetricsConfig,Ii(t,r),0,void 0,0,!0).execute()})),f}}(e);return{start:function(e){e||t.forEach((function(e){return e.start()})),n&&n.start()},stop:function(e){t.forEach((function(e){return e.stop()})),!e&&n&&n.stop()},isRunning:function(){return t.some((function(e){return e.isRunning()}))},execute:function(e){var r=e?[]:t.map((function(e){return e.execute()}));return n&&r.push(n.execute()),Promise.all(r).then((()=>{}))},isExecuting:function(){return t.some((function(e){return e.isExecuting()}))}}}var Di,Mi=600,Ki="PUSH_NONRETRYABLE_ERROR",xi="PUSH_RETRYABLE_ERROR",Pi="PUSH_SUBSYSTEM_UP",Ui="PUSH_SUBSYSTEM_DOWN",Bi="MEMBERSHIPS_MS_UPDATE",ji="MEMBERSHIPS_LS_UPDATE",qi="SEGMENT_UPDATE",zi="SPLIT_KILL",Wi="SPLIT_UPDATE",Gi="RB_SEGMENT_UPDATE",Hi="CONTROL",Qi="OCCUPANCY";function Vi(e){var t=e.userConsent;return!t||t===O}function Ji(e,t){return function(n){var r=n.settings,i=n.settings,s=i.log,o=i.streamingEnabled,a=i.sync.enabled,u=n.telemetryTracker,c=n.storage,l=n.readiness,f=e&&e(n),h=a&&o&&f&&t?t(n,f):void 0,p=Li(n);h&&(h.on(Pi,(function(){s.info(mt),f.isRunning()&&(f.stop(),u.streamingEvent(ve,Se)),f.syncAll()})),h.on(Ui,(function(){f.isRunning()?s.info(dt):(s.info(gt),f.start(),u.streamingEvent(ve,be))})));var g=!1,d=!0;return{pollingManager:f,pushManager:h,submitterManager:p,start:function(){return g=!0,p.start(!Vi(r)),Promise.resolve(!!c.validateCache&&c.validateCache()).then((function(e){g&&(d&&e&&l.splits.emit(gi),f&&(a?h?(d&&f.syncAll(),h.start()):f.start():d&&f.syncAll()),d=!1)}))},stop:function(){g=!1,h&&h.stop(),f&&f.isRunning()&&f.stop(),p.stop()},isRunning:function(){return g},flush:function(){return p.execute(!Vi(r))},shared:function(e,t,n){if(f){var r=f.add(e,t,n);return a&&h&&h.add(e,r),g&&(a?h?f.isRunning()?wr(n)&&r.start():r.execute():wr(n)&&r.start():t.isReady()||r.execute()),{stop:function(){var t=f.get(e);t&&(h&&h.remove(e),t.isRunning()&&t.stop(),f.remove(e))},flush:function(){return Promise.resolve()}}}}}}}!function(e){e.STREAMING_DISABLED="STREAMING_DISABLED",e.STREAMING_PAUSED="STREAMING_PAUSED",e.STREAMING_RESUMED="STREAMING_RESUMED",e.STREAMING_RESET="STREAMING_RESET"}(Di||(Di={}));var Yi=function(){function e(t,n,r){this.baseMillis=e.__TEST__BASE_MILLIS||n||e.DEFAULT_BASE_MILLIS,this.maxMillis=e.__TEST__MAX_MILLIS||r||e.DEFAULT_MAX_MILLIS,this.attempts=0,this.cb=t}return e.prototype.scheduleCall=function(){var e=this,t=Math.min(this.baseMillis*Math.pow(2,this.attempts),this.maxMillis);return this.timeoutID&&clearTimeout(this.timeoutID),this.timeoutID=setTimeout((function(){e.timeoutID=void 0,e.cb()}),t),this.attempts++,t},e.prototype.reset=function(){this.attempts=0,this.timeoutID&&(clearTimeout(this.timeoutID),this.timeoutID=void 0)},e.DEFAULT_BASE_MILLIS=1e3,e.DEFAULT_MAX_MILLIS=18e5,e}();var $i=[/control_pri$/,/control_sec$/],Zi=[10,20];function Xi(e,t,n){var r=function(e,t){var n=$i.map((function(e){return{regex:e,hasPublishers:!0,oTime:-1,cTime:-1}})),r=!0,i=!0;return{handleOpen:function(){t.streamingEvent(he),e.emit(Pi)},isStreamingUp:function(){return i&&r},handleOccupancyEvent:function(s,o,a){for(var u=0;uc.oTime){c.oTime=a,c.hasPublishers=0!==s;var l=n.some((function(e){return e.hasPublishers}));i&&(!l&&r?e.emit(Ui):l&&!r&&e.emit(Pi)),r=l}return}}},handleControlEvent:function(s,o,a){if(s!==Di.STREAMING_RESET)for(var u=0;uc.cTime&&(c.cTime=a,s===Di.STREAMING_DISABLED?(t.streamingEvent(pe,Te),e.emit(Ki)):r&&(s===Di.STREAMING_PAUSED&&i?(t.streamingEvent(pe,ke),e.emit(Ui)):s!==Di.STREAMING_RESUMED||i||(t.streamingEvent(pe,Re),e.emit(Pi))),i=s===Di.STREAMING_RESUMED))}else e.emit(s)}}}(t,n);return{handleOpen:function(){r.handleOpen()},handleError:function(r){var i=r;try{i=function(e){return l(e.data)&&(e.parsedData=JSON.parse(e.data)),e}(r)}catch(t){e.warn(_t,[t])}var s=i.parsedData&&i.parsedData.message||i.message;e.error(nn,[s]),!function(e){if(e.parsedData&&e.parsedData.code){var t=e.parsedData.code;if(n.streamingEvent(me,t),40140<=t&&t<=40149)return!0;if(4e4<=t&&t<=49999)return!1}else n.streamingEvent(ge,Ee);return!0}(i)?t.emit(Ki):t.emit(xi)},handleMessage:function(n){var i;try{if(i=function(e){if(e.data){var t=JSON.parse(e.data);return t.parsedData=JSON.parse(t.data),t.name&&"[meta]occupancy"===t.name&&(t.parsedData.type=Qi),t}}(n),!i)return}catch(t){return void e.warn(At,[t])}var s=i.parsedData,o=i.data,a=i.channel,u=i.timestamp;if(e.debug(We,[o]),r.isStreamingUp()||-1!==[Qi,Hi].indexOf(s.type))switch(s.type){case Wi:case qi:case Bi:case ji:case zi:case Gi:t.emit(s.type,s);break;case Qi:r.handleOccupancyEvent(s.metrics.publishers,a,u);break;case Hi:r.handleControlEvent(s.controlType,a,u)}}}}var es=1e4,ts=6e4,ns=10;function rs(e,t,n,r){var i,s,o;function a(t){var i,a,u,c=0,l=-1,f=!1,h=new Yi(p);function p(){if(i=!0,c>Math.max(l,t.getChangeNumber())){f=!1;var g=c;(s?new Promise((function(e){o=setTimeout((function(){s=void 0,n.execute(u,!0,a?c:void 0).then(e)}),s)})):n.execute(u,!0,a?c:void 0)).then((function(n){if(i){if(!1!==n){var s=t.getChangeNumber();l=s>-1?s:Math.max(l,g)}if(f)p();else{u&&r.trackUpdatesFromSSE(ne);var o=h.attempts+1;if(c<=l)return e.debug("Refresh completed"+(a?" bypassing the CDN":"")+" in "+o+" attempts."),void(i=!1);if(o(n.getChangeNumber(r)||-1)?(a=!1,t.execute(!1,r,!0,s?o:void 0).then((function(){if(i)if(a)c();else{var t=u.attempts+1;if(o<=(n.getChangeNumber(r)||-1))return e.debug("Refresh completed"+(s?" bypassing the CDN":"")+" in "+t+" attempts."),void(i=!1);if(t>>1|(21845&h)<<1;p=(61680&(p=(52428&p)>>>2|(13107&p)<<2))>>>4|(3855&p)<<4,f[h]=((65280&p)>>>8|(255&p)<<8)>>>1}var g=function(e,n,r){for(var i=e.length,s=0,o=new t(n);s>>c]=l}else for(a=new t(i),s=0;s>>15-e[s]);return a},d=new e(288);for(h=0;h<144;++h)d[h]=8;for(h=144;h<256;++h)d[h]=9;for(h=256;h<280;++h)d[h]=7;for(h=280;h<288;++h)d[h]=8;var m=new e(32);for(h=0;h<32;++h)m[h]=5;var v=g(d,9,1),y=g(m,5,1),S=function(e){for(var t=e[0],n=1;nt&&(t=e[n]);return t},b=function(e,t,n){var r=t/8|0;return(e[r]|e[r+1]<<8)>>(7&t)&n},E=function(e,t){var n=t/8|0;return(e[n]|e[n+1]<<8|e[n+2]<<16)>>(7&t)},T=["unexpected EOF","invalid block type","invalid length/literal","invalid distance","stream finished","no stream handler",,"no callback","invalid UTF-8 data","extra field too long","date not in range 1980-2099","filename too long","stream finishing","invalid zip data"],R=function(e,t,n){var r=new Error(t||T[e]);if(r.code=e,Error.captureStackTrace&&Error.captureStackTrace(r,R),!n)throw r;return r},k=function(o,a,c){var f=o.length;if(!f||c&&c.f&&!c.l)return a||new e(0);var h=!a||c,p=!c||c.i;c||(c={}),a||(a=new e(3*f));var d=function(t){var n=a.length;if(t>n){var r=new e(Math.max(2*n,t));r.set(a),a=r}},m=c.f||0,T=c.p||0,k=c.b||0,C=c.l,w=c.d,N=c.m,_=c.n,A=8*f;do{if(!C){m=b(o,T,1);var I=b(o,T+1,3);if(T+=3,!I){var O=o[(q=4+((T+7)/8|0))-4]|o[q-3]<<8,F=q+O;if(F>f){p&&R(0);break}h&&d(k+O),a.set(o.subarray(q,F),k),c.b=k+=O,c.p=T=8*F,c.f=m;continue}if(1==I)C=v,w=y,N=9,_=5;else if(2==I){var L=b(o,T,31)+257,D=b(o,T+10,15)+4,M=L+b(o,T+5,31)+1;T+=14;for(var K=new e(M),x=new e(19),P=0;P>>4)<16)K[P++]=q;else{var W=0,G=0;for(16==q?(G=3+b(o,T,3),T+=2,W=K[P-1]):17==q?(G=3+b(o,T,7),T+=3):18==q&&(G=11+b(o,T,127),T+=7);G--;)K[P++]=W}}var H=K.subarray(0,L),Q=K.subarray(L);N=S(H),_=S(Q),C=g(H,N,1),w=g(Q,_,1)}else R(1);if(T>A){p&&R(0);break}}h&&d(k+131072);for(var V=(1<>>4;if((T+=15&W)>A){p&&R(0);break}if(W||R(2),$<256)a[k++]=$;else{if(256==$){Y=T,C=null;break}var Z=$-254;if($>264){var X=r[P=$-257];Z=b(o,T,(1<>>4;ee||R(3),T+=15ⅇQ=l[te];if(te>3){X=i[te];Q+=E(o,T)&(1<A){p&&R(0);break}h&&d(k+131072);for(var ne=k+Z;kr.length)&&(s=r.length);var o=new(r instanceof t?t:r instanceof n?n:e)(s-i);return o.set(r.subarray(i,s)),o}(a,0,k)};return{gunzipSync:function(t,n){return k(t.subarray(function(e){31==e[0]&&139==e[1]&&8==e[2]||R(6,"invalid gzip data");var t=e[3],n=10;4&t&&(n+=e[10]|2+(e[11]<<8));for(var r=(t>>3&1)+(t>>4&1);r>0;r-=!e[n++]);return n+(2&t)}(t),-8),n||new e((i=(r=t).length,(r[i-4]|r[i-3]<<8|r[i-2]<<16|r[i-1]<<24)>>>0)));var r,i},unzlibSync:function(e,t){return k(((8!=(15&(n=e)[0])||n[0]>>>4>7||(n[0]<<8|n[1])%31)&&R(6,"invalid zlib data"),32&n[1]&&R(6,"invalid zlib data: preset dictionaries not supported"),e.subarray(2,-4)),t);var n}}}(),os="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";function as(e){var t=String(e).replace(/[=]+$/,"");if(t.length%4==1)throw new Error("'atob' failed: The string to be decoded is not correctly encoded.");for(var n="",r=0,i=void 0,s=void 0,o=0;s=t.charAt(o++);~s&&(i=r%4?64*i+s:s,r++%4)?n+=String.fromCharCode(255&i>>(-2*r&6)):0)s=os.indexOf(s);return n}var us=String.fromCharCode;function cs(e){var t,n,r,i,s;return n=function(e){if("string"!=typeof e)throw TypeError("Illegal argument: "+typeof e);var t=0;return function(){return t>=e.length?null:e.charCodeAt(t++)}}(e),i=[],s=[],r=t=function(){if(0===arguments.length)return s.join("")+us.apply(String,i);i.length+arguments.length>1024&&(s.push(us.apply(String,i)),i.length=0),Array.prototype.push.apply(i,arguments)},function(e,t){for(var n,r=null;null!==(n=null!==r?r:e());)n>=55296&&n<=57343&&null!==(r=e())&&r>=56320&&r<=57343?(t(1024*(n-55296)+r-56320+65536),r=null):t(n);null!==r&&t(r)}(n,(function(e){!function(e,t){var n=null;for("number"==typeof e&&(n=e,e=function(){return null});null!==n||null!==(n=e());)n<128?t(127&n):n<2048?(t(n>>6&31|192),t(63&n|128)):n<65536?(t(n>>12&15|224),t(n>>6&63|128),t(63&n|128)):(t(n>>18&7|240),t(n>>12&63|128),t(n>>6&63|128),t(63&n|128)),n=null}(e,r)})),t()} /*! * +----------------------------------------------------------------------------------+ * | murmurHash3.js v3.0.0 (http://github.com/karanlyons/murmurHash3.js) | From 00e23f71e13b8c69e6acdaa723b3689be1ddc57e Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 8 Jan 2026 17:16:30 -0300 Subject: [PATCH 41/55] Add SplitView type --- splitio_web/lib/splitio_web.dart | 16 ++--- splitio_web/lib/src/js_interop.dart | 105 +++++++++++++++++----------- 2 files changed, 71 insertions(+), 50 deletions(-) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index 370876b..7c0ed5f 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -715,7 +715,7 @@ class SplitioWeb extends SplitioPlatform { final result = manager.split(splitName.toJS); - return result != null ? jsObjectToSplitView(result) : null; + return result != null ? jsSplitViewToSplitView(result) : null; } @override @@ -724,7 +724,7 @@ class SplitioWeb extends SplitioPlatform { final result = manager.splits(); - return result.toDart.map(jsObjectToSplitView).toList(); + return result.toDart.map(jsSplitViewToSplitView).toList(); } @override @@ -767,9 +767,7 @@ class SplitioWeb extends SplitioPlatform { bucketingKey: bucketingKey, ); - if ((client.getStatus.callAsFunction(null) as JS_ReadinessStatus) - .isReady - .toDart) { + if (client.getStatus().isReady.toDart) { return; } else { final completer = Completer(); @@ -793,9 +791,7 @@ class SplitioWeb extends SplitioPlatform { bucketingKey: bucketingKey, ); - if ((client.getStatus.callAsFunction(null) as JS_ReadinessStatus) - .isReadyFromCache - .toDart) { + if (client.getStatus().isReadyFromCache.toDart) { return; } else { final completer = Completer(); @@ -819,9 +815,7 @@ class SplitioWeb extends SplitioPlatform { bucketingKey: bucketingKey, ); - if ((client.getStatus.callAsFunction(null) as JS_ReadinessStatus) - .hasTimedout - .toDart) { + if (client.getStatus().hasTimedout.toDart) { return; } else { final completer = Completer(); diff --git a/splitio_web/lib/src/js_interop.dart b/splitio_web/lib/src/js_interop.dart index 0917b1c..6de2354 100644 --- a/splitio_web/lib/src/js_interop.dart +++ b/splitio_web/lib/src/js_interop.dart @@ -65,19 +65,56 @@ extension type JS_ReadinessStatus._(JSObject _) implements JSObject { external JSBoolean hasTimedout; } +@JS() +extension type JS_TreatmentWithConfig._(JSObject _) implements JSObject { + external JSString treatment; + external JSString? config; +} + +@JS() +extension type JS_Prerequisite._(JSObject _) implements JSObject { + external JSString flagName; + external JSArray treatments; +} + +@JS() +extension type JS_SplitView._(JSObject _) implements JSObject { + external JSString name; + external JSString trafficType; + external JSBoolean killed; + external JSArray treatments; + external JSNumber changeNumber; + external JSObject configs; + external JSArray sets; + external JSString defaultTreatment; + external JSBoolean impressionsDisabled; + external JSArray prerequisites; +} + @JS() extension type JS_IBrowserClient._(JSObject _) implements JSObject { external JSString getTreatment( JSString flagName, JSObject attributes, JSObject evaluationOptions); - external JSObject getTreatments(JSArray flagNames, JSObject attributes, JSObject evaluationOptions); - external JSObject getTreatmentWithConfig(JSString flagName, JSObject attributes, JSObject evaluationOptions); - external JSObject getTreatmentsWithConfig(JSArray flagNames, JSObject attributes, JSObject evaluationOptions); - external JSObject getTreatmentsByFlagSet(JSString flagSetName, JSObject attributes, JSObject evaluationOptions); - external JSObject getTreatmentsByFlagSets(JSArray flagSetNames, JSObject attributes, JSObject evaluationOptions); - external JSObject getTreatmentsWithConfigByFlagSet(JSString flagSetName, JSObject attributes, JSObject evaluationOptions); - external JSObject getTreatmentsWithConfigByFlagSets(JSArray flagSetNames, JSObject attributes, JSObject evaluationOptions); - external JSBoolean track(JSString? trafficType, JSString eventType, JSNumber? value, JSObject? attributes); - external JSBoolean setAttribute(JSString attributeName, JSAny? attributeValue); + external JSObject getTreatments(JSArray flagNames, + JSObject attributes, JSObject evaluationOptions); + external JS_TreatmentWithConfig getTreatmentWithConfig( + JSString flagName, JSObject attributes, JSObject evaluationOptions); + external JSObject getTreatmentsWithConfig(JSArray flagNames, + JSObject attributes, JSObject evaluationOptions); + external JSObject getTreatmentsByFlagSet( + JSString flagSetName, JSObject attributes, JSObject evaluationOptions); + external JSObject getTreatmentsByFlagSets(JSArray flagSetNames, + JSObject attributes, JSObject evaluationOptions); + external JSObject getTreatmentsWithConfigByFlagSet( + JSString flagSetName, JSObject attributes, JSObject evaluationOptions); + external JSObject getTreatmentsWithConfigByFlagSets( + JSArray flagSetNames, + JSObject attributes, + JSObject evaluationOptions); + external JSBoolean track(JSString? trafficType, JSString eventType, + JSNumber? value, JSObject? attributes); + external JSBoolean setAttribute( + JSString attributeName, JSAny? attributeValue); external JSAny getAttribute(JSString attributeName); external JSBoolean removeAttribute(JSString attributeName); external JSBoolean setAttributes(JSObject attributes); @@ -89,14 +126,14 @@ extension type JS_IBrowserClient._(JSObject _) implements JSObject { external JSFunction off; external JSFunction emit; external JS_EventConsts Event; - external JSFunction getStatus; + external JS_ReadinessStatus getStatus(); } @JS() extension type JS_IBrowserManager._(JSObject _) implements JSObject { external JSArray names(); - external JSObject? split(JSString name); - external JSArray splits(); + external JS_SplitView? split(JSString name); + external JSArray splits(); } @JS() @@ -167,40 +204,30 @@ Map jsTreatmentsWithConfigToMap(JSObject obj) { k, SplitResult(v['treatment'] as String, v['config'] as String?))); } -SplitResult jsTreatmentWithConfigToSplitResult(JSObject obj) { - final config = _reflectGet(obj, 'config'.toJS); - return SplitResult((_reflectGet(obj, 'treatment'.toJS) as JSString).toDart, - (config is JSString) ? config.toDart : null); +SplitResult jsTreatmentWithConfigToSplitResult(JS_TreatmentWithConfig obj) { + return SplitResult(obj.treatment.toDart, + (obj.config is JSString) ? obj.config!.toDart : null); } -Prerequisite jsObjectToPrerequisite(JSObject obj) { +Prerequisite jsPrerequisiteToPrerequisite(JS_Prerequisite obj) { return Prerequisite( - (_reflectGet(obj, 'flagName'.toJS) as JSString).toDart, - jsArrayToList(_reflectGet(obj, 'treatments'.toJS) as JSArray) - .toSet() - .cast(), + obj.flagName.toDart, + jsArrayToList(obj.treatments).toSet().cast(), ); } -// @TODO: JS_SplitView -SplitView jsObjectToSplitView(JSObject obj) { +SplitView jsSplitViewToSplitView(JS_SplitView obj) { return SplitView( - (_reflectGet(obj, 'name'.toJS) as JSString).toDart, - (_reflectGet(obj, 'trafficType'.toJS) as JSString).toDart, - (_reflectGet(obj, 'killed'.toJS) as JSBoolean).toDart, - jsArrayToList(_reflectGet(obj, 'treatments'.toJS) as JSArray) - .cast(), - (_reflectGet(obj, 'changeNumber'.toJS) as JSNumber).toDartInt, - jsObjectToMap(_reflectGet(obj, 'configs'.toJS) as JSObject) - .cast(), - (_reflectGet(obj, 'defaultTreatment'.toJS) as JSString).toDart, - jsArrayToList(_reflectGet(obj, 'sets'.toJS) as JSArray) - .cast(), - (_reflectGet(obj, 'impressionsDisabled'.toJS) as JSBoolean).toDart, - (_reflectGet(obj, 'prerequisites'.toJS) as JSArray) - .toDart - .map(jsObjectToPrerequisite) - .toSet()); + obj.name.toDart, + obj.trafficType.toDart, + obj.killed.toDart, + jsArrayToList(obj.treatments).cast(), + obj.changeNumber.toDartInt, + jsObjectToMap(obj.configs).cast(), + obj.defaultTreatment.toDart, + jsArrayToList(obj.sets).cast(), + obj.impressionsDisabled.toDart, + obj.prerequisites.toDart.map(jsPrerequisiteToPrerequisite).toSet()); } Impression jsImpressionDataToImpression(JS_ImpressionData obj) { From 2fc187e6a629aa1c3c09ac511f303a0e3fa5a72c Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 9 Jan 2026 13:47:30 -0300 Subject: [PATCH 42/55] Add EvaluationOptions type --- splitio_web/lib/splitio_web.dart | 15 ++++++++------- splitio_web/lib/src/js_interop.dart | 29 +++++++++++++++++++---------- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index 7c0ed5f..40b3cc1 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -117,9 +117,9 @@ class SplitioWeb extends SplitioPlatform { } // Map SplitConfiguration to JS equivalent object - JSObject _buildConfig(String apiKey, String matchingKey, String? bucketingKey, - SplitConfiguration? configuration) { - final config = JSObject(); + JS_Configuration _buildConfig(String apiKey, String matchingKey, + String? bucketingKey, SplitConfiguration? configuration) { + final config = JSObject() as JS_Configuration; final core = JSObject(); core.setProperty('authorizationKey'.toJS, apiKey.toJS); @@ -393,12 +393,13 @@ class SplitioWeb extends SplitioPlatform { return jsMap; } - JSObject _convertEvaluationOptions(EvaluationOptions evaluationOptions) { - final jsEvalOptions = JSObject(); + JS_EvaluationOptions _convertEvaluationOptions( + EvaluationOptions evaluationOptions) { + final jsEvalOptions = JSObject() as JS_EvaluationOptions; if (evaluationOptions.properties.isNotEmpty) { - jsEvalOptions.setProperty( - 'properties'.toJS, _convertMap(evaluationOptions.properties, false)); + jsEvalOptions.properties = + _convertMap(evaluationOptions.properties, false); } return jsEvalOptions; diff --git a/splitio_web/lib/src/js_interop.dart b/splitio_web/lib/src/js_interop.dart index 6de2354..cfda9c6 100644 --- a/splitio_web/lib/src/js_interop.dart +++ b/splitio_web/lib/src/js_interop.dart @@ -39,7 +39,11 @@ extension type JS_IImpressionListener._(JSObject _) implements JSObject { } @JS() -extension type JS_ISettings._(JSObject _) implements JSObject { +extension type JS_Configuration._(JSObject _) implements JSObject { +} + +@JS() +extension type JS_ISettings._(JSObject _) implements JS_Configuration { external JS_Logger log; external JS_IImpressionListener? impressionListener; } @@ -91,26 +95,31 @@ extension type JS_SplitView._(JSObject _) implements JSObject { external JSArray prerequisites; } +@JS() +extension type JS_EvaluationOptions._(JSObject _) implements JSObject { + external JSObject properties; +} + @JS() extension type JS_IBrowserClient._(JSObject _) implements JSObject { external JSString getTreatment( - JSString flagName, JSObject attributes, JSObject evaluationOptions); + JSString flagName, JSObject attributes, JS_EvaluationOptions evaluationOptions); external JSObject getTreatments(JSArray flagNames, - JSObject attributes, JSObject evaluationOptions); + JSObject attributes, JS_EvaluationOptions evaluationOptions); external JS_TreatmentWithConfig getTreatmentWithConfig( - JSString flagName, JSObject attributes, JSObject evaluationOptions); + JSString flagName, JSObject attributes, JS_EvaluationOptions evaluationOptions); external JSObject getTreatmentsWithConfig(JSArray flagNames, - JSObject attributes, JSObject evaluationOptions); + JSObject attributes, JS_EvaluationOptions evaluationOptions); external JSObject getTreatmentsByFlagSet( - JSString flagSetName, JSObject attributes, JSObject evaluationOptions); + JSString flagSetName, JSObject attributes, JS_EvaluationOptions evaluationOptions); external JSObject getTreatmentsByFlagSets(JSArray flagSetNames, - JSObject attributes, JSObject evaluationOptions); + JSObject attributes, JS_EvaluationOptions evaluationOptions); external JSObject getTreatmentsWithConfigByFlagSet( - JSString flagSetName, JSObject attributes, JSObject evaluationOptions); + JSString flagSetName, JSObject attributes, JS_EvaluationOptions evaluationOptions); external JSObject getTreatmentsWithConfigByFlagSets( JSArray flagSetNames, JSObject attributes, - JSObject evaluationOptions); + JS_EvaluationOptions evaluationOptions); external JSBoolean track(JSString? trafficType, JSString eventType, JSNumber? value, JSObject? attributes); external JSBoolean setAttribute( @@ -146,7 +155,7 @@ extension type JS_IBrowserSDK._(JSObject _) implements JSObject { @JS() extension type JS_BrowserSDKPackage._(JSObject _) implements JSObject { - external JS_IBrowserSDK SplitFactory(JSObject config); + external JS_IBrowserSDK SplitFactory(JS_Configuration config); external JSFunction? InLocalStorage; external JSFunction? DebugLogger; external JSFunction? InfoLogger; From e3a8f532e64f14066e465a7c5edac314e22d8cb4 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 9 Jan 2026 15:48:47 -0300 Subject: [PATCH 43/55] Remove usage of js_interop_unsafe --- splitio_web/lib/splitio_web.dart | 157 +++--- splitio_web/lib/src/js_interop.dart | 98 +++- splitio_web/test/splitio_web_test.dart | 3 +- .../test/utils/js_interop_test_utils.dart | 485 ++++++++++-------- 4 files changed, 431 insertions(+), 312 deletions(-) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index 40b3cc1..b8f9242 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -1,6 +1,5 @@ import 'dart:async'; import 'dart:js_interop'; -import 'dart:js_interop_unsafe'; import 'package:flutter_web_plugins/flutter_web_plugins.dart' show Registrar; import 'package:splitio_platform_interface/splitio_platform_interface.dart'; import 'package:splitio_web/src/js_interop.dart'; @@ -121,90 +120,77 @@ class SplitioWeb extends SplitioPlatform { String? bucketingKey, SplitConfiguration? configuration) { final config = JSObject() as JS_Configuration; - final core = JSObject(); - core.setProperty('authorizationKey'.toJS, apiKey.toJS); - core.setProperty('key'.toJS, buildJsKey(matchingKey, bucketingKey)); - config.setProperty('core'.toJS, core); + final core = JSObject() as JS_ConfigurationCore; + core.authorizationKey = apiKey.toJS; + core.key = buildJsKey(matchingKey, bucketingKey); + config.core = core; if (configuration != null) { - final scheduler = JSObject(); + final scheduler = JSObject() as JS_ConfigurationScheduler; if (configuration.configurationMap.containsKey('featuresRefreshRate')) - scheduler.setProperty( - 'featuresRefreshRate'.toJS, - (configuration.configurationMap['featuresRefreshRate'] as int) - .toJS); + scheduler.featuresRefreshRate = + (configuration.configurationMap['featuresRefreshRate'] as int).toJS; if (configuration.configurationMap.containsKey('segmentsRefreshRate')) - scheduler.setProperty( - 'segmentsRefreshRate'.toJS, - (configuration.configurationMap['segmentsRefreshRate'] as int) - .toJS); + scheduler.segmentsRefreshRate = + (configuration.configurationMap['segmentsRefreshRate'] as int).toJS; if (configuration.configurationMap.containsKey('impressionsRefreshRate')) - scheduler.setProperty( - 'impressionsRefreshRate'.toJS, + scheduler.impressionsRefreshRate = (configuration.configurationMap['impressionsRefreshRate'] as int) - .toJS); + .toJS; if (configuration.configurationMap.containsKey('telemetryRefreshRate')) - scheduler.setProperty( - 'telemetryRefreshRate'.toJS, + scheduler.telemetryRefreshRate = (configuration.configurationMap['telemetryRefreshRate'] as int) - .toJS); + .toJS; if (configuration.configurationMap.containsKey('eventsQueueSize')) - scheduler.setProperty('eventsQueueSize'.toJS, - (configuration.configurationMap['eventsQueueSize'] as int).toJS); + scheduler.eventsQueueSize = + (configuration.configurationMap['eventsQueueSize'] as int).toJS; if (configuration.configurationMap.containsKey('impressionsQueueSize')) - scheduler.setProperty( - 'impressionsQueueSize'.toJS, + scheduler.impressionsQueueSize = (configuration.configurationMap['impressionsQueueSize'] as int) - .toJS); + .toJS; if (configuration.configurationMap.containsKey('eventFlushInterval')) - scheduler.setProperty('eventsPushRate'.toJS, - (configuration.configurationMap['eventFlushInterval'] as int).toJS); - config.setProperty('scheduler'.toJS, scheduler); + scheduler.eventsPushRate = + (configuration.configurationMap['eventFlushInterval'] as int).toJS; + config.scheduler = scheduler; if (configuration.configurationMap.containsKey('streamingEnabled')) - config.setProperty('streamingEnabled'.toJS, - (configuration.configurationMap['streamingEnabled'] as bool).toJS); + config.streamingEnabled = + (configuration.configurationMap['streamingEnabled'] as bool).toJS; - final urls = JSObject(); + final urls = JSObject() as JS_ConfigurationUrls; if (configuration.configurationMap.containsKey('sdkEndpoint')) - urls.setProperty('sdk'.toJS, - (configuration.configurationMap['sdkEndpoint'] as String).toJS); + urls.sdk = + (configuration.configurationMap['sdkEndpoint'] as String).toJS; if (configuration.configurationMap.containsKey('eventsEndpoint')) - urls.setProperty('events'.toJS, - (configuration.configurationMap['eventsEndpoint'] as String).toJS); + urls.events = + (configuration.configurationMap['eventsEndpoint'] as String).toJS; if (configuration.configurationMap.containsKey('authServiceEndpoint')) - urls.setProperty( - 'auth'.toJS, + urls.auth = (configuration.configurationMap['authServiceEndpoint'] as String) - .toJS); + .toJS; if (configuration.configurationMap .containsKey('streamingServiceEndpoint')) - urls.setProperty( - 'streaming'.toJS, - (configuration.configurationMap['streamingServiceEndpoint'] - as String) - .toJS); + urls.streaming = (configuration + .configurationMap['streamingServiceEndpoint'] as String) + .toJS; if (configuration.configurationMap .containsKey('telemetryServiceEndpoint')) - urls.setProperty( - 'telemetry'.toJS, - (configuration.configurationMap['telemetryServiceEndpoint'] - as String) - .toJS); - config.setProperty('urls'.toJS, urls); + urls.telemetry = (configuration + .configurationMap['telemetryServiceEndpoint'] as String) + .toJS; + config.urls = urls; - final sync = JSObject(); + final sync = JSObject() as JS_ConfigurationSync; if (configuration.configurationMap['impressionsMode'] != null) { - sync.setProperty( - 'impressionsMode'.toJS, + sync.impressionsMode = (configuration.configurationMap['impressionsMode'] as String) .toUpperCase() - .toJS); + .toJS; } if (configuration.configurationMap['syncEnabled'] != null) { - sync.setProperty('enabled'.toJS, - (configuration.configurationMap['syncEnabled'] as bool).toJS); + sync.enabled = + (configuration.configurationMap['syncEnabled'] as bool).toJS; } if (configuration.configurationMap['syncConfig'] != null) { @@ -229,16 +215,15 @@ class SplitioWeb extends SplitioPlatform { splitFilters.add( {'type': 'bySet', 'values': syncConfig['syncConfigFlagSets']}); } - sync.setProperty('splitFilters'.toJS, splitFilters.jsify()); + sync.splitFilters = splitFilters.jsify() as JSArray; } - config.setProperty('sync'.toJS, sync); + config.sync = sync; if (configuration.configurationMap['userConsent'] != null) { - config.setProperty( - 'userConsent'.toJS, + config.userConsent = (configuration.configurationMap['userConsent'] as String) .toUpperCase() - .toJS); + .toJS; } final logLevel = configuration.configurationMap['logLevel']; @@ -262,49 +247,43 @@ class SplitioWeb extends SplitioPlatform { break; } if (logger != null) { - config.setProperty('debug'.toJS, logger); // Browser SDK + config.debug = logger; // Browser SDK } else { - config.setProperty( - 'debug'.toJS, logLevel.toUpperCase().toJS); // JS SDK + config.debug = logLevel.toUpperCase().toJS; // JS SDK } } else if (configuration.configurationMap['enableDebug'] == true) { - config.setProperty( - 'debug'.toJS, - window.splitio!.DebugLogger != null - ? window.splitio!.DebugLogger!.callAsFunction() // Browser SDK - : 'DEBUG'.toJS // JS SDK - ); + config.debug = window.splitio!.DebugLogger != null + ? window.splitio!.DebugLogger!.callAsFunction() // Browser SDK + : 'DEBUG'.toJS; // JS SDK } if (configuration.configurationMap['readyTimeout'] != null) { - final startup = JSObject(); - startup.setProperty('readyTimeout'.toJS, - (configuration.configurationMap['readyTimeout'] as int).toJS); - config.setProperty('startup'.toJS, startup); + final startup = JSObject() as JS_ConfigurationStartup; + startup.readyTimeout = + (configuration.configurationMap['readyTimeout'] as int).toJS; + config.startup = startup; } - final storageOptions = JSObject(); - storageOptions.setProperty('type'.toJS, 'LOCALSTORAGE'.toJS); + final storageOptions = JSObject() as JS_ConfigurationStorage; + storageOptions.type = 'LOCALSTORAGE'.toJS; if (configuration.configurationMap['rolloutCacheConfiguration'] != null) { final rolloutCacheConfiguration = configuration.configurationMap['rolloutCacheConfiguration'] as Map; if (rolloutCacheConfiguration['expirationDays'] != null) { - storageOptions.setProperty('expirationDays'.toJS, - (rolloutCacheConfiguration['expirationDays'] as int).toJS); + storageOptions.expirationDays = + (rolloutCacheConfiguration['expirationDays'] as int).toJS; } if (rolloutCacheConfiguration['clearOnInit'] != null) { - storageOptions.setProperty('clearOnInit'.toJS, - (rolloutCacheConfiguration['clearOnInit'] as bool).toJS); + storageOptions.clearOnInit = + (rolloutCacheConfiguration['clearOnInit'] as bool).toJS; } } if (window.splitio!.InLocalStorage != null) { - config.setProperty( - 'storage'.toJS, - window.splitio!.InLocalStorage - ?.callAsFunction(null, storageOptions)); // Browser SDK + config.storage = window.splitio!.InLocalStorage + ?.callAsFunction(null, storageOptions); // Browser SDK } else { - config.setProperty('storage'.toJS, storageOptions); // JS SDK + config.storage = storageOptions; // JS SDK } if (configuration.configurationMap['impressionListener'] is bool) { @@ -312,10 +291,10 @@ class SplitioWeb extends SplitioPlatform { _impressionsStreamController.add(jsImpressionDataToImpression(data)); }).toJS; - final JSObject impressionListener = JSObject(); - impressionListener.setProperty('logImpression'.toJS, logImpression); + final impressionListener = JSObject() as JS_IImpressionListener; + reflectSet(impressionListener, 'logImpression'.toJS, logImpression); - config.setProperty('impressionListener'.toJS, impressionListener); + config.impressionListener = impressionListener; } } @@ -355,7 +334,7 @@ class SplitioWeb extends SplitioPlatform { return _clients[key]!; } - Future _getManager() async { + Future _getManager() async { await this._initFuture; return _factory.manager(); @@ -382,7 +361,7 @@ class SplitioWeb extends SplitioPlatform { final jsValue = _convertValue(value, isAttribute); if (jsValue != null) { - jsMap.setProperty(key.toJS, jsValue); + reflectSet(jsMap, key.toJS, jsValue); } else { this._factory.settings.log.warn( 'Invalid ${isAttribute ? 'attribute' : 'property'} value: $value, for key: $key, will be ignored' diff --git a/splitio_web/lib/src/js_interop.dart b/splitio_web/lib/src/js_interop.dart index cfda9c6..9d7847c 100644 --- a/splitio_web/lib/src/js_interop.dart +++ b/splitio_web/lib/src/js_interop.dart @@ -38,8 +38,70 @@ extension type JS_IImpressionListener._(JSObject _) implements JSObject { external JSVoid logImpression(JS_ImpressionData impression); } +@JS() +extension type JS_ConfigurationCore._(JSObject _) implements JSObject { + external JSString authorizationKey; + external JSAny key; // string | SplitKey +} + +@JS() +extension type JS_ConfigurationStartup._(JSObject _) implements JSObject { + external JSNumber? readyTimeout; +} + +@JS() +extension type JS_ConfigurationScheduler._(JSObject _) implements JSObject { + external JSNumber? featuresRefreshRate; + external JSNumber? segmentsRefreshRate; + external JSNumber? impressionsRefreshRate; + external JSNumber? eventsRefreshRate; + external JSNumber? telemetryRefreshRate; + external JSNumber? eventsQueueSize; + external JSNumber? impressionsQueueSize; + external JSNumber? eventsPushRate; +} + +@JS() +extension type JS_ConfigurationUrls._(JSObject _) implements JSObject { + external JSString? sdk; + external JSString? events; + external JSString? auth; + external JSString? streaming; + external JSString? telemetry; +} + +@JS() +extension type JS_SplitFilter._(JSObject _) implements JSObject { + external JSString type; + external JSArray values; +} + +@JS() +extension type JS_ConfigurationSync._(JSObject _) implements JSObject { + external JSString? impressionsMode; + external JSBoolean? enabled; + external JSArray? splitFilters; +} + +@JS() +extension type JS_ConfigurationStorage._(JSObject _) implements JSObject { + external JSString? type; + external JSNumber? expirationDays; + external JSBoolean? clearOnInit; +} + @JS() extension type JS_Configuration._(JSObject _) implements JSObject { + external JS_ConfigurationCore core; + external JS_ConfigurationStartup? startup; + external JS_ConfigurationScheduler? scheduler; + external JS_ConfigurationUrls? urls; + external JS_ConfigurationSync? sync; + external JSBoolean? streamingEnabled; + external JSString? userConsent; + external JS_IImpressionListener? impressionListener; + external JSAny? debug; + external JSAny? storage; } @JS() @@ -102,20 +164,20 @@ extension type JS_EvaluationOptions._(JSObject _) implements JSObject { @JS() extension type JS_IBrowserClient._(JSObject _) implements JSObject { - external JSString getTreatment( - JSString flagName, JSObject attributes, JS_EvaluationOptions evaluationOptions); + external JSString getTreatment(JSString flagName, JSObject attributes, + JS_EvaluationOptions evaluationOptions); external JSObject getTreatments(JSArray flagNames, JSObject attributes, JS_EvaluationOptions evaluationOptions); - external JS_TreatmentWithConfig getTreatmentWithConfig( - JSString flagName, JSObject attributes, JS_EvaluationOptions evaluationOptions); + external JS_TreatmentWithConfig getTreatmentWithConfig(JSString flagName, + JSObject attributes, JS_EvaluationOptions evaluationOptions); external JSObject getTreatmentsWithConfig(JSArray flagNames, JSObject attributes, JS_EvaluationOptions evaluationOptions); - external JSObject getTreatmentsByFlagSet( - JSString flagSetName, JSObject attributes, JS_EvaluationOptions evaluationOptions); + external JSObject getTreatmentsByFlagSet(JSString flagSetName, + JSObject attributes, JS_EvaluationOptions evaluationOptions); external JSObject getTreatmentsByFlagSets(JSArray flagSetNames, JSObject attributes, JS_EvaluationOptions evaluationOptions); - external JSObject getTreatmentsWithConfigByFlagSet( - JSString flagSetName, JSObject attributes, JS_EvaluationOptions evaluationOptions); + external JSObject getTreatmentsWithConfigByFlagSet(JSString flagSetName, + JSObject attributes, JS_EvaluationOptions evaluationOptions); external JSObject getTreatmentsWithConfigByFlagSets( JSArray flagSetNames, JSObject attributes, @@ -139,7 +201,7 @@ extension type JS_IBrowserClient._(JSObject _) implements JSObject { } @JS() -extension type JS_IBrowserManager._(JSObject _) implements JSObject { +extension type JS_IManager._(JSObject _) implements JSObject { external JSArray names(); external JS_SplitView? split(JSString name); external JSArray splits(); @@ -148,7 +210,7 @@ extension type JS_IBrowserManager._(JSObject _) implements JSObject { @JS() extension type JS_IBrowserSDK._(JSObject _) implements JSObject { external JS_IBrowserClient client(JSAny? key); - external JS_IBrowserManager manager(); + external JS_IManager manager(); external JS_ISettings settings; external JS_IUserConsentAPI UserConsent; } @@ -166,13 +228,16 @@ extension type JS_BrowserSDKPackage._(JSObject _) implements JSObject { // Conversion utils: JS to Dart types @JS('Object.keys') -external JSArray _objectKeys(JSObject obj); +external JSArray objectKeys(JSObject obj); @JS('Reflect.get') -external JSAny? _reflectGet(JSObject target, JSAny propertyKey); +external JSAny? reflectGet(JSObject target, JSAny propertyKey); + +@JS('Reflect.set') +external JSAny? reflectSet(JSObject target, JSAny propertyKey, JSAny value); @JS('JSON.parse') -external JSObject _jsonParse(JSString obj); +external JSObject jsonParse(JSString obj); List jsArrayToList(JSArray obj) { return obj.toDart.map(jsAnyToDart).toList(); @@ -180,9 +245,8 @@ List jsArrayToList(JSArray obj) { Map jsObjectToMap(JSObject obj) { return { - for (final jsKey in _objectKeys(obj).toDart) - // @TODO _reflectGet (js_interop) vs obj.getProperty (js_interop_unsafe) - jsKey.toDart: jsAnyToDart(_reflectGet(obj, jsKey)), + for (final jsKey in objectKeys(obj).toDart) + jsKey.toDart: jsAnyToDart(reflectGet(obj, jsKey)), }; } @@ -252,7 +316,7 @@ Impression jsImpressionDataToImpression(JS_ImpressionData obj) { obj.impression.changeNumber.toDartInt, obj.attributes != null ? jsObjectToMap(obj.attributes!) : {}, obj.impression.properties != null - ? jsObjectToMap(_jsonParse(obj.impression.properties!)) + ? jsObjectToMap(jsonParse(obj.impression.properties!)) : null, ); } diff --git a/splitio_web/test/splitio_web_test.dart b/splitio_web/test/splitio_web_test.dart index 9c50d2e..c48e68e 100644 --- a/splitio_web/test/splitio_web_test.dart +++ b/splitio_web/test/splitio_web_test.dart @@ -1,5 +1,4 @@ import 'dart:js_interop'; -import 'dart:js_interop_unsafe'; import 'package:splitio_platform_interface/splitio_platform_interface.dart'; import 'package:web/web.dart' as web; import 'package:flutter_test/flutter_test.dart'; @@ -23,7 +22,7 @@ void main() { final mock = SplitioMock(); setUp(() { - (web.window as JSObject)['splitio'] = mock.splitio; + web.window.splitio = mock.splitio; _platform.init( apiKey: 'apiKey', diff --git a/splitio_web/test/utils/js_interop_test_utils.dart b/splitio_web/test/utils/js_interop_test_utils.dart index ce0a7d4..5fe842c 100644 --- a/splitio_web/test/utils/js_interop_test_utils.dart +++ b/splitio_web/test/utils/js_interop_test_utils.dart @@ -1,5 +1,4 @@ import 'dart:js_interop'; -import 'dart:js_interop_unsafe'; import 'package:splitio_web/src/js_interop.dart'; @JS('Promise.resolve') @@ -10,7 +9,7 @@ external JSObject _objectAssign(JSObject target, JSObject source); class SplitioMock { // JS Browser SDK API mock - final JSObject splitio = JSObject(); + final JS_BrowserSDKPackage splitio = JSObject() as JS_BrowserSDKPackage; // Test utils final List<({String methodName, List methodArguments})> calls = []; @@ -45,26 +44,35 @@ class SplitioMock { } SplitioMock() { - final mockManager = JSObject(); - mockManager['split'] = (JSString splitName) { - calls.add((methodName: 'split', methodArguments: [splitName])); + final mockManager = JSObject() as JS_IManager; + reflectSet( + mockManager, + 'split'.toJS, + (JSString splitName) { + calls.add((methodName: 'split', methodArguments: [splitName])); - if (splitName.toDart == 'inexistent_split') { - return null; - } - return _createSplitViewJSObject(splitName); - }.toJS; - mockManager['splits'] = () { - calls.add((methodName: 'splits', methodArguments: [])); - return [ - _createSplitViewJSObject('split1'.toJS), - _createSplitViewJSObject('split2'.toJS), - ].jsify(); - }.toJS; - mockManager['names'] = () { - calls.add((methodName: 'names', methodArguments: [])); - return ['split1'.toJS, 'split2'.toJS].jsify(); - }.toJS; + if (splitName.toDart == 'inexistent_split') { + return null; + } + return _createSplitViewJSObject(splitName); + }.toJS); + reflectSet( + mockManager, + 'splits'.toJS, + () { + calls.add((methodName: 'splits', methodArguments: [])); + return [ + _createSplitViewJSObject('split1'.toJS), + _createSplitViewJSObject('split2'.toJS), + ].jsify(); + }.toJS); + reflectSet( + mockManager, + 'names'.toJS, + () { + calls.add((methodName: 'names', methodArguments: [])); + return ['split1'.toJS, 'split2'.toJS].jsify(); + }.toJS); final mockEvents = { 'SDK_READY': 'init::ready', @@ -73,19 +81,19 @@ class SplitioMock { 'SDK_UPDATE': 'state::update' }.jsify() as JS_EventConsts; - final mockClient = JSObject(); - mockClient['Event'] = mockEvents; - mockClient['on'] = (JSString event, JSFunction listener) { + final mockClient = JSObject() as JS_IBrowserClient; + mockClient.Event = mockEvents; + mockClient.on = (JSString event, JSFunction listener) { calls.add((methodName: 'on', methodArguments: [event, listener])); _eventListeners[event] ??= Set(); _eventListeners[event]!.add(listener); }.toJS; - mockClient['off'] = (JSString event, JSFunction listener) { + mockClient.off = (JSString event, JSFunction listener) { calls.add((methodName: 'off', methodArguments: [event, listener])); _eventListeners[event] ??= Set(); _eventListeners[event]!.remove(listener); }.toJS; - mockClient['emit'] = (JSString event) { + mockClient.emit = (JSString event) { calls.add((methodName: 'emit', methodArguments: [event])); _eventListeners[event]?.forEach((listener) { listener.callAsFunction(null, event); @@ -98,203 +106,272 @@ class SplitioMock { _readinessStatus.hasTimedout = true.toJS; } }.toJS; - mockClient['getStatus'] = () { - calls.add((methodName: 'getStatus', methodArguments: [])); - return _readinessStatus; - }.toJS; - mockClient['getTreatment'] = + reflectSet( + mockClient, + 'getStatus'.toJS, + () { + calls.add((methodName: 'getStatus', methodArguments: [])); + return _readinessStatus; + }.toJS); + reflectSet( + mockClient, + 'getTreatment'.toJS, (JSAny? flagName, JSAny? attributes, JSAny? evaluationOptions) { - calls.add(( - methodName: 'getTreatment', - methodArguments: [flagName, attributes, evaluationOptions] - )); - return 'on'.toJS; - }.toJS; - mockClient['getTreatments'] = + calls.add(( + methodName: 'getTreatment', + methodArguments: [flagName, attributes, evaluationOptions] + )); + return 'on'.toJS; + }.toJS); + reflectSet( + mockClient, + 'getTreatments'.toJS, (JSAny? flagNames, JSAny? attributes, JSAny? evaluationOptions) { - calls.add(( - methodName: 'getTreatments', - methodArguments: [flagNames, attributes, evaluationOptions] - )); - if (flagNames is JSArray) { - return flagNames.toDart.fold(JSObject(), (previousValue, element) { - if (element is JSString) { - previousValue.setProperty(element, 'on'.toJS); + calls.add(( + methodName: 'getTreatments', + methodArguments: [flagNames, attributes, evaluationOptions] + )); + if (flagNames is JSArray) { + return flagNames.toDart.fold(JSObject(), (previousValue, flagName) { + if (flagName is JSString) { + reflectSet(previousValue, flagName, 'on'.toJS); + } + return previousValue; + }); } - return previousValue; - }); - } - return JSObject(); - }.toJS; - mockClient['getTreatmentWithConfig'] = + return JSObject(); + }.toJS); + reflectSet( + mockClient, + 'getTreatmentWithConfig'.toJS, (JSAny? flagName, JSAny? attributes, JSAny? evaluationOptions) { - calls.add(( - methodName: 'getTreatmentWithConfig', - methodArguments: [flagName, attributes, evaluationOptions] - )); - final result = JSObject(); - result.setProperty('treatment'.toJS, 'on'.toJS); - result.setProperty('config'.toJS, 'some-config'.toJS); - return result; - }.toJS; - mockClient['getTreatmentsWithConfig'] = + calls.add(( + methodName: 'getTreatmentWithConfig', + methodArguments: [flagName, attributes, evaluationOptions] + )); + final result = JSObject() as JS_TreatmentWithConfig; + result.treatment = 'on'.toJS; + result.config = 'some-config'.toJS; + return result; + }.toJS); + reflectSet( + mockClient, + 'getTreatmentsWithConfig'.toJS, (JSAny? flagNames, JSAny? attributes, JSAny? evaluationOptions) { - calls.add(( - methodName: 'getTreatmentsWithConfig', - methodArguments: [flagNames, attributes, evaluationOptions] - )); - if (flagNames is JSArray) { - return flagNames.toDart.fold(JSObject(), (previousValue, element) { - if (element is JSString) { - final result = JSObject(); - result.setProperty('treatment'.toJS, 'on'.toJS); - result.setProperty('config'.toJS, 'some-config'.toJS); - previousValue.setProperty(element, result); + calls.add(( + methodName: 'getTreatmentsWithConfig', + methodArguments: [flagNames, attributes, evaluationOptions] + )); + if (flagNames is JSArray) { + return flagNames.toDart.fold(JSObject(), (previousValue, flagName) { + if (flagName is JSString) { + final result = JSObject() as JS_TreatmentWithConfig; + result.treatment = 'on'.toJS; + result.config = 'some-config'.toJS; + reflectSet(previousValue, flagName, result); + } + return previousValue; + }); } - return previousValue; - }); - } - return JSObject(); - }.toJS; - mockClient['getTreatmentsByFlagSet'] = + return JSObject(); + }.toJS); + reflectSet( + mockClient, + 'getTreatmentsByFlagSet'.toJS, (JSAny? flagSetName, JSAny? attributes, JSAny? evaluationOptions) { - calls.add(( - methodName: 'getTreatmentsByFlagSet', - methodArguments: [flagSetName, attributes, evaluationOptions] - )); - final result = JSObject(); - result.setProperty('split1'.toJS, 'on'.toJS); - result.setProperty('split2'.toJS, 'on'.toJS); - return result; - }.toJS; - mockClient['getTreatmentsByFlagSets'] = + calls.add(( + methodName: 'getTreatmentsByFlagSet', + methodArguments: [flagSetName, attributes, evaluationOptions] + )); + final result = JSObject(); + reflectSet(result, 'split1'.toJS, 'on'.toJS); + reflectSet(result, 'split2'.toJS, 'on'.toJS); + return result; + }.toJS); + reflectSet( + mockClient, + 'getTreatmentsByFlagSets'.toJS, (JSAny? flagSetNames, JSAny? attributes, JSAny? evaluationOptions) { - calls.add(( - methodName: 'getTreatmentsByFlagSets', - methodArguments: [flagSetNames, attributes, evaluationOptions] - )); - final result = JSObject(); - result.setProperty('split1'.toJS, 'on'.toJS); - result.setProperty('split2'.toJS, 'on'.toJS); - return result; - }.toJS; - mockClient['getTreatmentsWithConfigByFlagSet'] = + calls.add(( + methodName: 'getTreatmentsByFlagSets', + methodArguments: [flagSetNames, attributes, evaluationOptions] + )); + final result = JSObject(); + reflectSet(result, 'split1'.toJS, 'on'.toJS); + reflectSet(result, 'split2'.toJS, 'on'.toJS); + return result; + }.toJS); + reflectSet( + mockClient, + 'getTreatmentsWithConfigByFlagSet'.toJS, (JSAny? flagSetName, JSAny? attributes, JSAny? evaluationOptions) { - calls.add(( - methodName: 'getTreatmentsWithConfigByFlagSet', - methodArguments: [flagSetName, attributes, evaluationOptions] - )); + calls.add(( + methodName: 'getTreatmentsWithConfigByFlagSet', + methodArguments: [flagSetName, attributes, evaluationOptions] + )); - final treatmentWithConfig = JSObject(); - treatmentWithConfig.setProperty('treatment'.toJS, 'on'.toJS); - treatmentWithConfig.setProperty('config'.toJS, 'some-config'.toJS); + final treatmentWithConfig = JSObject() as JS_TreatmentWithConfig; + treatmentWithConfig.treatment = 'on'.toJS; + treatmentWithConfig.config = 'some-config'.toJS; - final result = JSObject(); - result.setProperty('split1'.toJS, treatmentWithConfig); - result.setProperty('split2'.toJS, treatmentWithConfig); - return result; - }.toJS; - mockClient['getTreatmentsWithConfigByFlagSets'] = + final result = JSObject(); + reflectSet(result, 'split1'.toJS, treatmentWithConfig); + reflectSet(result, 'split2'.toJS, treatmentWithConfig); + return result; + }.toJS); + reflectSet( + mockClient, + 'getTreatmentsWithConfigByFlagSets'.toJS, (JSAny? flagSetNames, JSAny? attributes, JSAny? evaluationOptions) { - calls.add(( - methodName: 'getTreatmentsWithConfigByFlagSets', - methodArguments: [flagSetNames, attributes, evaluationOptions] - )); + calls.add(( + methodName: 'getTreatmentsWithConfigByFlagSets', + methodArguments: [flagSetNames, attributes, evaluationOptions] + )); - final treatmentWithConfig = JSObject(); - treatmentWithConfig.setProperty('treatment'.toJS, 'on'.toJS); - treatmentWithConfig.setProperty('config'.toJS, 'some-config'.toJS); + final treatmentWithConfig = JSObject() as JS_TreatmentWithConfig; + treatmentWithConfig.treatment = 'on'.toJS; + treatmentWithConfig.config = 'some-config'.toJS; - final result = JSObject(); - result.setProperty('split1'.toJS, treatmentWithConfig); - result.setProperty('split2'.toJS, treatmentWithConfig); - return result; - }.toJS; - mockClient['track'] = (JSAny? trafficType, JSAny? eventType, JSAny? value, - JSAny? properties) { - calls.add(( - methodName: 'track', - methodArguments: [trafficType, eventType, value, properties] - )); - return trafficType != null ? true.toJS : false.toJS; - }.toJS; - mockClient['setAttribute'] = (JSAny? attributeName, JSAny? attributeValue) { - calls.add(( - methodName: 'setAttribute', - methodArguments: [attributeName, attributeValue] - )); - return true.toJS; - }.toJS; - mockClient['getAttribute'] = (JSAny? attributeName) { - calls.add((methodName: 'getAttribute', methodArguments: [attributeName])); - return 'attr-value'.toJS; - }.toJS; - mockClient['removeAttribute'] = (JSAny? attributeName) { - calls.add( - (methodName: 'removeAttribute', methodArguments: [attributeName])); - return true.toJS; - }.toJS; - mockClient['setAttributes'] = (JSAny? attributes) { - calls.add((methodName: 'setAttributes', methodArguments: [attributes])); - return true.toJS; - }.toJS; - mockClient['getAttributes'] = () { - calls.add((methodName: 'getAttributes', methodArguments: [])); - return { - 'attrBool': true, - 'attrString': 'value', - 'attrInt': 1, - 'attrDouble': 1.1, - 'attrList': ['value1', 100, false], - }.jsify(); - }.toJS; - mockClient['clearAttributes'] = () { - calls.add((methodName: 'clearAttributes', methodArguments: [])); - return true.toJS; - }.toJS; - mockClient['flush'] = () { - calls.add((methodName: 'flush', methodArguments: [])); - return _promiseResolve(); - }.toJS; - mockClient['destroy'] = () { - calls.add((methodName: 'destroy', methodArguments: [])); - return _promiseResolve(); - }.toJS; + final result = JSObject(); + reflectSet(result, 'split1'.toJS, treatmentWithConfig); + reflectSet(result, 'split2'.toJS, treatmentWithConfig); + return result; + }.toJS); + reflectSet( + mockClient, + 'track'.toJS, + (JSAny? trafficType, JSAny? eventType, JSAny? value, + JSAny? properties) { + calls.add(( + methodName: 'track', + methodArguments: [trafficType, eventType, value, properties] + )); + return trafficType != null ? true.toJS : false.toJS; + }.toJS); + reflectSet( + mockClient, + 'setAttribute'.toJS, + (JSAny? attributeName, JSAny? attributeValue) { + calls.add(( + methodName: 'setAttribute', + methodArguments: [attributeName, attributeValue] + )); + return true.toJS; + }.toJS); + reflectSet( + mockClient, + 'getAttribute'.toJS, + (JSAny? attributeName) { + calls.add( + (methodName: 'getAttribute', methodArguments: [attributeName])); + return 'attr-value'.toJS; + }.toJS); + reflectSet( + mockClient, + 'removeAttribute'.toJS, + (JSAny? attributeName) { + calls.add(( + methodName: 'removeAttribute', + methodArguments: [attributeName] + )); + return true.toJS; + }.toJS); + reflectSet( + mockClient, + 'setAttributes'.toJS, + (JSAny? attributes) { + calls.add( + (methodName: 'setAttributes', methodArguments: [attributes])); + return true.toJS; + }.toJS); + reflectSet( + mockClient, + 'getAttributes'.toJS, + () { + calls.add((methodName: 'getAttributes', methodArguments: [])); + return { + 'attrBool': true, + 'attrString': 'value', + 'attrInt': 1, + 'attrDouble': 1.1, + 'attrList': ['value1', 100, false], + }.jsify(); + }.toJS); + reflectSet( + mockClient, + 'clearAttributes'.toJS, + () { + calls.add((methodName: 'clearAttributes', methodArguments: [])); + return true.toJS; + }.toJS); + reflectSet( + mockClient, + 'flush'.toJS, + () { + calls.add((methodName: 'flush', methodArguments: [])); + return _promiseResolve(); + }.toJS); + reflectSet( + mockClient, + 'destroy'.toJS, + () { + calls.add((methodName: 'destroy', methodArguments: [])); + return _promiseResolve(); + }.toJS); - final mockLog = JSObject(); - mockLog['warn'] = (JSAny? arg1) { - calls.add((methodName: 'warn', methodArguments: [arg1])); - }.toJS; + final mockLog = JSObject() as JS_Logger; + reflectSet( + mockLog, + 'warn'.toJS, + (JSAny? arg1) { + calls.add((methodName: 'warn', methodArguments: [arg1])); + }.toJS); - final mockUserConsent = JSObject(); - mockUserConsent['setStatus'] = (JSBoolean arg1) { - _userConsent = arg1.toDart ? 'GRANTED'.toJS : 'DECLINED'.toJS; - calls.add((methodName: 'setStatus', methodArguments: [arg1])); - return true.toJS; - }.toJS; - mockUserConsent['getStatus'] = () { - calls.add((methodName: 'getStatus', methodArguments: [])); - return _userConsent; - }.toJS; + final mockUserConsent = JSObject() as JS_IUserConsentAPI; + reflectSet( + mockUserConsent, + 'setStatus'.toJS, + (JSBoolean arg1) { + _userConsent = arg1.toDart ? 'GRANTED'.toJS : 'DECLINED'.toJS; + calls.add((methodName: 'setStatus', methodArguments: [arg1])); + return true.toJS; + }.toJS); + reflectSet( + mockUserConsent, + 'getStatus'.toJS, + () { + calls.add((methodName: 'getStatus', methodArguments: [])); + return _userConsent; + }.toJS); - mockFactory['client'] = (JSAny? splitKey) { - calls.add((methodName: 'client', methodArguments: [splitKey])); - return mockClient; - }.toJS; - mockFactory['manager'] = () { - calls.add((methodName: 'manager', methodArguments: [])); - return mockManager; - }.toJS; - mockFactory['UserConsent'] = mockUserConsent; + reflectSet( + mockFactory, + 'client'.toJS, + (JSAny? splitKey) { + calls.add((methodName: 'client', methodArguments: [splitKey])); + return mockClient; + }.toJS); + reflectSet( + mockFactory, + 'manager'.toJS, + () { + calls.add((methodName: 'manager', methodArguments: [])); + return mockManager; + }.toJS); + mockFactory.UserConsent = mockUserConsent; - splitio['SplitFactory'] = (JSObject config) { - calls.add((methodName: 'SplitFactory', methodArguments: [config])); + reflectSet( + splitio, + 'SplitFactory'.toJS, + (JSObject config) { + calls.add((methodName: 'SplitFactory', methodArguments: [config])); - final mockSettings = _objectAssign(JSObject(), config); - mockSettings['log'] = mockLog; - mockFactory['settings'] = mockSettings; + final mockSettings = + _objectAssign(JSObject(), config) as JS_ISettings; + mockSettings.log = mockLog; + mockFactory.settings = mockSettings; - return mockFactory; - }.toJS; + return mockFactory; + }.toJS); } } From a7a55d62fe0902d8d5a03cc4f9dfdb5c72110571 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 9 Jan 2026 17:33:01 -0300 Subject: [PATCH 44/55] Normalize service endpoint URLs for JS SDK compatibility --- splitio_web/lib/splitio_web.dart | 37 +++++++++++++++++--------- splitio_web/test/splitio_web_test.dart | 20 +++++++------- 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index b8f9242..c04a521 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -164,20 +164,33 @@ class SplitioWeb extends SplitioPlatform { if (configuration.configurationMap.containsKey('eventsEndpoint')) urls.events = (configuration.configurationMap['eventsEndpoint'] as String).toJS; - if (configuration.configurationMap.containsKey('authServiceEndpoint')) - urls.auth = - (configuration.configurationMap['authServiceEndpoint'] as String) - .toJS; + + // Convert urls for consistency between JS SDK and Android/iOS SDK + if (configuration.configurationMap.containsKey('authServiceEndpoint')) { + final auth = + configuration.configurationMap['authServiceEndpoint'] as String; + final jsAuth = + auth.endsWith('/v2') ? auth.substring(0, auth.length - 3) : auth; + urls.auth = jsAuth.toJS; + } if (configuration.configurationMap - .containsKey('streamingServiceEndpoint')) - urls.streaming = (configuration - .configurationMap['streamingServiceEndpoint'] as String) - .toJS; + .containsKey('streamingServiceEndpoint')) { + final streaming = configuration + .configurationMap['streamingServiceEndpoint'] as String; + final jsStreaming = streaming.endsWith('/sse') + ? streaming.substring(0, streaming.length - 4) + : streaming; + urls.streaming = jsStreaming.toJS; + } if (configuration.configurationMap - .containsKey('telemetryServiceEndpoint')) - urls.telemetry = (configuration - .configurationMap['telemetryServiceEndpoint'] as String) - .toJS; + .containsKey('telemetryServiceEndpoint')) { + final telemetry = configuration + .configurationMap['telemetryServiceEndpoint'] as String; + final jsTelemetry = telemetry.endsWith('/v1') + ? telemetry.substring(0, telemetry.length - 3) + : telemetry; + urls.telemetry = jsTelemetry.toJS; + } config.urls = urls; final sync = JSObject() as JS_ConfigurationSync; diff --git a/splitio_web/test/splitio_web_test.dart b/splitio_web/test/splitio_web_test.dart index c48e68e..4f6b07f 100644 --- a/splitio_web/test/splitio_web_test.dart +++ b/splitio_web/test/splitio_web_test.dart @@ -647,11 +647,11 @@ void main() { streamingEnabled: false, persistentAttributesEnabled: true, // unsupported in Web impressionListener: true, - sdkEndpoint: 'sdk-endpoint', - eventsEndpoint: 'events-endpoint', - authServiceEndpoint: 'auth-service-endpoint', - streamingServiceEndpoint: 'streaming-service-endpoint', - telemetryServiceEndpoint: 'telemetry-service-endpoint', + sdkEndpoint: 'https://sdk.split-stage.io/api', + eventsEndpoint: 'https://events.split-stage.io/api', + authServiceEndpoint: 'https://auth.split-stage.io/api/v2', + streamingServiceEndpoint: 'https://streaming.split.io/sse', + telemetryServiceEndpoint: 'https://telemetry.split-stage.io/api/v1', syncConfig: SyncConfig( names: ['flag_1', 'flag_2'], prefixes: ['prefix_1']), impressionsMode: ImpressionsMode.none, @@ -693,11 +693,11 @@ void main() { 'eventsPushRate': 7, }, 'urls': { - 'sdk': 'sdk-endpoint', - 'events': 'events-endpoint', - 'auth': 'auth-service-endpoint', - 'streaming': 'streaming-service-endpoint', - 'telemetry': 'telemetry-service-endpoint', + 'sdk': 'https://sdk.split-stage.io/api', + 'events': 'https://events.split-stage.io/api', + 'auth': 'https://auth.split-stage.io/api', + 'streaming': 'https://streaming.split.io', + 'telemetry': 'https://telemetry.split-stage.io/api', }, 'sync': { 'impressionsMode': 'NONE', From d4fce3c610066cf5fe7aaaa0bbc98385a9f628dd Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 9 Jan 2026 17:33:37 -0300 Subject: [PATCH 45/55] Export RolloutCacheConfiguration --- splitio/lib/splitio.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/splitio/lib/splitio.dart b/splitio/lib/splitio.dart index 948c0b7..82e4bfa 100644 --- a/splitio/lib/splitio.dart +++ b/splitio/lib/splitio.dart @@ -11,6 +11,7 @@ export 'package:splitio_platform_interface/split_sync_config.dart'; export 'package:splitio_platform_interface/split_view.dart'; export 'package:splitio_platform_interface/split_certificate_pinning_configuration.dart'; export 'package:splitio_platform_interface/split_evaluation_options.dart'; +export 'package:splitio_platform_interface/split_rollout_cache_configuration.dart'; typedef ClientReadinessCallback = void Function(SplitClient splitClient); From f7d162e0db82692af9ac8042f515f438d20fb30e Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Sat, 10 Jan 2026 12:38:52 -0300 Subject: [PATCH 46/55] Improve mock to support multiple clients --- splitio_web/lib/splitio_web.dart | 10 +- splitio_web/lib/src/js_interop.dart | 16 ++ splitio_web/test/splitio_web_test.dart | 3 +- .../test/utils/js_interop_test_utils.dart | 140 ++++++++++-------- 4 files changed, 97 insertions(+), 72 deletions(-) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index c04a521..6711bac 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -314,10 +314,6 @@ class SplitioWeb extends SplitioPlatform { return config; } - static String _buildKeyString(String matchingKey, String? bucketingKey) { - return bucketingKey == null ? matchingKey : '${matchingKey}_$bucketingKey'; - } - @override Future getClient({ required String matchingKey, @@ -325,7 +321,7 @@ class SplitioWeb extends SplitioPlatform { }) async { await this._initFuture; - final key = _buildKeyString(matchingKey, bucketingKey); + final key = buildKeyString(matchingKey, bucketingKey); if (_clients.containsKey(key)) { return; @@ -342,7 +338,7 @@ class SplitioWeb extends SplitioPlatform { }) async { await getClient(matchingKey: matchingKey, bucketingKey: bucketingKey); - final key = _buildKeyString(matchingKey, bucketingKey); + final key = buildKeyString(matchingKey, bucketingKey); return _clients[key]!; } @@ -827,7 +823,7 @@ class SplitioWeb extends SplitioPlatform { @override Stream? onUpdated( {required String matchingKey, required String? bucketingKey}) { - final client = _clients[_buildKeyString(matchingKey, bucketingKey)]; + final client = _clients[buildKeyString(matchingKey, bucketingKey)]; if (client == null) { return null; diff --git a/splitio_web/lib/src/js_interop.dart b/splitio_web/lib/src/js_interop.dart index 9d7847c..ac73ebc 100644 --- a/splitio_web/lib/src/js_interop.dart +++ b/splitio_web/lib/src/js_interop.dart @@ -330,3 +330,19 @@ JSAny buildJsKey(String matchingKey, String? bucketingKey) { } return matchingKey.toJS; } + +({String matchingKey, String? bucketingKey}) buildDartKey(JSAny splitKey) { + return splitKey is JSString + ? (matchingKey: splitKey.toDart, bucketingKey: null) + : ( + matchingKey: + (reflectGet(splitKey as JSObject, 'matchingKey'.toJS) as JSString) + .toDart, + bucketingKey: + (reflectGet(splitKey, 'bucketingKey'.toJS) as JSString).toDart, + ); +} + +String buildKeyString(String matchingKey, String? bucketingKey) { + return bucketingKey == null ? matchingKey : '${matchingKey}_$bucketingKey'; +} diff --git a/splitio_web/test/splitio_web_test.dart b/splitio_web/test/splitio_web_test.dart index 4f6b07f..bc98998 100644 --- a/splitio_web/test/splitio_web_test.dart +++ b/splitio_web/test/splitio_web_test.dart @@ -651,7 +651,8 @@ void main() { eventsEndpoint: 'https://events.split-stage.io/api', authServiceEndpoint: 'https://auth.split-stage.io/api/v2', streamingServiceEndpoint: 'https://streaming.split.io/sse', - telemetryServiceEndpoint: 'https://telemetry.split-stage.io/api/v1', + telemetryServiceEndpoint: + 'https://telemetry.split-stage.io/api/v1', syncConfig: SyncConfig( names: ['flag_1', 'flag_2'], prefixes: ['prefix_1']), impressionsMode: ImpressionsMode.none, diff --git a/splitio_web/test/utils/js_interop_test_utils.dart b/splitio_web/test/utils/js_interop_test_utils.dart index 5fe842c..4db3f8e 100644 --- a/splitio_web/test/utils/js_interop_test_utils.dart +++ b/splitio_web/test/utils/js_interop_test_utils.dart @@ -15,6 +15,15 @@ class SplitioMock { final List<({String methodName, List methodArguments})> calls = []; final JS_IBrowserSDK mockFactory = JSObject() as JS_IBrowserSDK; + final _mockEvents = { + 'SDK_READY': 'init::ready', + 'SDK_READY_FROM_CACHE': 'init::cache-ready', + 'SDK_READY_TIMED_OUT': 'init::timeout', + 'SDK_UPDATE': 'state::update' + }.jsify() as JS_EventConsts; + final _mockClients = {}; + + JS_Configuration? _config; JSString _userConsent = 'UNKNOWN'.toJS; JS_ReadinessStatus _readinessStatus = { 'isReady': false, @@ -74,15 +83,71 @@ class SplitioMock { return ['split1'.toJS, 'split2'.toJS].jsify(); }.toJS); - final mockEvents = { - 'SDK_READY': 'init::ready', - 'SDK_READY_FROM_CACHE': 'init::cache-ready', - 'SDK_READY_TIMED_OUT': 'init::timeout', - 'SDK_UPDATE': 'state::update' - }.jsify() as JS_EventConsts; + final mockLog = JSObject() as JS_Logger; + reflectSet( + mockLog, + 'warn'.toJS, + (JSAny? arg1) { + calls.add((methodName: 'warn', methodArguments: [arg1])); + }.toJS); + + final mockUserConsent = JSObject() as JS_IUserConsentAPI; + reflectSet( + mockUserConsent, + 'setStatus'.toJS, + (JSBoolean arg1) { + _userConsent = arg1.toDart ? 'GRANTED'.toJS : 'DECLINED'.toJS; + calls.add((methodName: 'setStatus', methodArguments: [arg1])); + return true.toJS; + }.toJS); + reflectSet( + mockUserConsent, + 'getStatus'.toJS, + () { + calls.add((methodName: 'getStatus', methodArguments: [])); + return _userConsent; + }.toJS); + + reflectSet( + mockFactory, + 'client'.toJS, + (JSAny? splitKey) { + calls.add((methodName: 'client', methodArguments: [splitKey])); + + final dartKey = buildDartKey(splitKey ?? _config!.core.key); + final stringKey = buildKeyString(dartKey.matchingKey, dartKey.bucketingKey); + _mockClients[stringKey] ??= _buildMockClient(); + return _mockClients[stringKey]; + }.toJS); + reflectSet( + mockFactory, + 'manager'.toJS, + () { + calls.add((methodName: 'manager', methodArguments: [])); + return mockManager; + }.toJS); + mockFactory.UserConsent = mockUserConsent; + + reflectSet( + splitio, + 'SplitFactory'.toJS, + (JS_Configuration config) { + calls.add((methodName: 'SplitFactory', methodArguments: [config])); + + final mockSettings = + _objectAssign(JSObject(), config) as JS_ISettings; + mockSettings.log = mockLog; + mockFactory.settings = mockSettings; + + _config = config; + + return mockFactory; + }.toJS); + } + JS_IBrowserClient _buildMockClient() { final mockClient = JSObject() as JS_IBrowserClient; - mockClient.Event = mockEvents; + mockClient.Event = _mockEvents; mockClient.on = (JSString event, JSFunction listener) { calls.add((methodName: 'on', methodArguments: [event, listener])); _eventListeners[event] ??= Set(); @@ -98,11 +163,11 @@ class SplitioMock { _eventListeners[event]?.forEach((listener) { listener.callAsFunction(null, event); }); - if (event == mockEvents.SDK_READY) { + if (event == _mockEvents.SDK_READY) { _readinessStatus.isReady = true.toJS; - } else if (event == mockEvents.SDK_READY_FROM_CACHE) { + } else if (event == _mockEvents.SDK_READY_FROM_CACHE) { _readinessStatus.isReadyFromCache = true.toJS; - } else if (event == mockEvents.SDK_READY_TIMED_OUT) { + } else if (event == _mockEvents.SDK_READY_TIMED_OUT) { _readinessStatus.hasTimedout = true.toJS; } }.toJS; @@ -319,59 +384,6 @@ class SplitioMock { return _promiseResolve(); }.toJS); - final mockLog = JSObject() as JS_Logger; - reflectSet( - mockLog, - 'warn'.toJS, - (JSAny? arg1) { - calls.add((methodName: 'warn', methodArguments: [arg1])); - }.toJS); - - final mockUserConsent = JSObject() as JS_IUserConsentAPI; - reflectSet( - mockUserConsent, - 'setStatus'.toJS, - (JSBoolean arg1) { - _userConsent = arg1.toDart ? 'GRANTED'.toJS : 'DECLINED'.toJS; - calls.add((methodName: 'setStatus', methodArguments: [arg1])); - return true.toJS; - }.toJS); - reflectSet( - mockUserConsent, - 'getStatus'.toJS, - () { - calls.add((methodName: 'getStatus', methodArguments: [])); - return _userConsent; - }.toJS); - - reflectSet( - mockFactory, - 'client'.toJS, - (JSAny? splitKey) { - calls.add((methodName: 'client', methodArguments: [splitKey])); - return mockClient; - }.toJS); - reflectSet( - mockFactory, - 'manager'.toJS, - () { - calls.add((methodName: 'manager', methodArguments: [])); - return mockManager; - }.toJS); - mockFactory.UserConsent = mockUserConsent; - - reflectSet( - splitio, - 'SplitFactory'.toJS, - (JSObject config) { - calls.add((methodName: 'SplitFactory', methodArguments: [config])); - - final mockSettings = - _objectAssign(JSObject(), config) as JS_ISettings; - mockSettings.log = mockLog; - mockFactory.settings = mockSettings; - - return mockFactory; - }.toJS); + return mockClient; } } From 35bcd3fca985cb62f0a7ab2b2ee5fb8d982b5ec3 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Sat, 10 Jan 2026 14:49:51 -0300 Subject: [PATCH 47/55] Make onUpdated stream callbacks async to wait for client initialization --- splitio_web/lib/splitio_web.dart | 39 ++++++++++++++++---------- splitio_web/test/splitio_web_test.dart | 15 ++++------ 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index 6711bac..468f333 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -748,6 +748,9 @@ class SplitioWeb extends SplitioPlatform { _factory.UserConsent.setStatus(enabled.toJS); } + // To ensure the public `onXXX` callbacks and `whenXXX` methods work correctly, + // the `onXXX` method implementations always return a Future or Stream that waits for the client to be initialized. + @override Future? onReady( {required String matchingKey, required String? bucketingKey}) async { @@ -823,11 +826,9 @@ class SplitioWeb extends SplitioPlatform { @override Stream? onUpdated( {required String matchingKey, required String? bucketingKey}) { - final client = _clients[buildKeyString(matchingKey, bucketingKey)]; - - if (client == null) { - return null; - } + // To ensure the public `onUpdated` callback and `whenUpdated` method work correctly, + // this method always return a stream, and the StreamController callbacks + // are async to wait for the client to be initialized. late final StreamController controller; final JSFunction jsCallback = (() { @@ -835,19 +836,27 @@ class SplitioWeb extends SplitioPlatform { controller.add(null); } }).toJS; + final registerJsCallback = () async { + final client = await _getClient( + matchingKey: matchingKey, + bucketingKey: bucketingKey, + ); + client.on.callAsFunction(client, client.Event.SDK_UPDATE, jsCallback); + }; + final deregisterJsCallback = () async { + final client = await _getClient( + matchingKey: matchingKey, + bucketingKey: bucketingKey, + ); + client.off.callAsFunction(client, client.Event.SDK_UPDATE, jsCallback); + }; controller = StreamController( - onListen: () { - client.on.callAsFunction(client, client.Event.SDK_UPDATE, jsCallback); - }, - onPause: () { - client.off.callAsFunction(client, client.Event.SDK_UPDATE, jsCallback); - }, - onResume: () { - client.on.callAsFunction(client, client.Event.SDK_UPDATE, jsCallback); - }, + onListen: registerJsCallback, + onPause: deregisterJsCallback, + onResume: registerJsCallback, onCancel: () async { - client.off.callAsFunction(client, client.Event.SDK_UPDATE, jsCallback); + await deregisterJsCallback(); if (!controller.isClosed) { await controller.close(); } diff --git a/splitio_web/test/splitio_web_test.dart b/splitio_web/test/splitio_web_test.dart index bc98998..287c8f7 100644 --- a/splitio_web/test/splitio_web_test.dart +++ b/splitio_web/test/splitio_web_test.dart @@ -933,34 +933,31 @@ void main() { }); test('onUpdated', () async { - // Precondition: client is initialized before onUpdated is called - await _platform.getClient( - matchingKey: 'matching-key', bucketingKey: 'bucketing-key'); final mockClient = mock.mockFactory.client(buildJsKey('matching-key', 'bucketing-key')); final stream = _platform.onUpdated( matchingKey: 'matching-key', bucketingKey: 'bucketing-key')!; final subscription = stream.listen(expectAsync1((_) {}, count: 3)); + await Future.delayed(Duration.zero); // onListen is async // Emit SDK_UPDATE events. Should be received mockClient.emit.callAsFunction(null, mockClient.Event.SDK_UPDATE); - mockClient.emit.callAsFunction(null, mockClient.Event.SDK_UPDATE); - await Future.delayed( - const Duration(milliseconds: 100)); // let events deliver - // Pause subscription and emit SDK_UPDATE event. Should not be received subscription.pause(); + await Future.delayed(Duration.zero); // onPause is async + mockClient.emit.callAsFunction(null, mockClient.Event.SDK_UPDATE); - await Future.delayed(const Duration(milliseconds: 100)); // Resume subscription and emit SDK_UPDATE event. Should be received subscription.resume(); + await Future.delayed(Duration.zero); // onResume is async + mockClient.emit.callAsFunction(null, mockClient.Event.SDK_UPDATE); - await Future.delayed(const Duration(milliseconds: 100)); + await Future.delayed(Duration.zero); // let last event deliver await subscription.cancel(); }); }); From 45727c529b8d46d350844c27d36b01053954d4b0 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Sat, 10 Jan 2026 15:21:23 -0300 Subject: [PATCH 48/55] Tests with multiple clients --- splitio_web/lib/splitio_web.dart | 17 ++---- splitio_web/test/splitio_web_test.dart | 54 ++++++++++++++----- .../test/utils/js_interop_test_utils.dart | 16 +++--- 3 files changed, 55 insertions(+), 32 deletions(-) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index 6711bac..a7e3119 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -319,28 +319,19 @@ class SplitioWeb extends SplitioPlatform { required String matchingKey, required String? bucketingKey, }) async { - await this._initFuture; - - final key = buildKeyString(matchingKey, bucketingKey); - - if (_clients.containsKey(key)) { - return; - } - - final client = this._factory.client(buildJsKey(matchingKey, bucketingKey)); - - _clients[key] = client; + await _getClient(matchingKey: matchingKey, bucketingKey: bucketingKey); } Future _getClient({ required String matchingKey, required String? bucketingKey, }) async { - await getClient(matchingKey: matchingKey, bucketingKey: bucketingKey); + await this._initFuture; final key = buildKeyString(matchingKey, bucketingKey); - return _clients[key]!; + return (_clients[key] ??= + _factory.client(buildJsKey(matchingKey, bucketingKey))); } Future _getManager() async { diff --git a/splitio_web/test/splitio_web_test.dart b/splitio_web/test/splitio_web_test.dart index bc98998..c59240a 100644 --- a/splitio_web/test/splitio_web_test.dart +++ b/splitio_web/test/splitio_web_test.dart @@ -43,6 +43,30 @@ void main() { mock.calls.last.methodArguments.map(jsAnyToDart), ['split', {}, {}]); }); + test('getTreatment in multiple clients', () async { + final result = await _platform.getTreatment( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + splitName: 'split'); + + final result2 = await _platform.getTreatment( + matchingKey: 'matching-key-getTreatment', + bucketingKey: null, + splitName: 'split-getTreatment'); + + expect(result, 'on'); + expect(result2, 'on'); + expect(mock.calls[mock.calls.length - 3].methodName, 'getTreatment'); + expect(mock.calls[mock.calls.length - 3].methodArguments.map(jsAnyToDart), + ['split', {}, {}]); + expect(mock.calls[mock.calls.length - 2].methodName, 'client'); + expect(mock.calls[mock.calls.length - 2].methodArguments.map(jsAnyToDart), + ['matching-key-getTreatment']); + expect(mock.calls.last.methodName, 'getTreatment'); + expect(mock.calls.last.methodArguments.map(jsAnyToDart), + ['split-getTreatment', {}, {}]); + }); + test('getTreatment with attributes', () async { final result = await _platform.getTreatment( matchingKey: 'matching-key', @@ -87,7 +111,7 @@ void main() { 'Invalid attribute value: null, for key: attrNull, will be ignored')); }); - test('getTreatment with evaluation properties', () async { + test('getTreatment with evaluation options', () async { final result = await _platform.getTreatment( matchingKey: 'matching-key', bucketingKey: 'bucketing-key', @@ -141,7 +165,7 @@ void main() { 'Invalid property value: [value1, 100, false], for key: propList, will be ignored')); }); - test('getTreatments without attributes', () async { + test('getTreatments', () async { final result = await _platform.getTreatments( matchingKey: 'matching-key', bucketingKey: 'bucketing-key', @@ -156,7 +180,7 @@ void main() { ]); }); - test('getTreatments with attributes and evaluation properties', () async { + test('getTreatments with attributes and evaluation options', () async { final result = await _platform.getTreatments( matchingKey: 'matching-key', bucketingKey: 'bucketing-key', @@ -188,7 +212,7 @@ void main() { ]); }); - test('getTreatmentWithConfig without attributes', () async { + test('getTreatmentWithConfig', () async { final result = await _platform.getTreatmentWithConfig( matchingKey: 'matching-key', bucketingKey: 'bucketing-key', @@ -200,7 +224,7 @@ void main() { mock.calls.last.methodArguments.map(jsAnyToDart), ['split1', {}, {}]); }); - test('getTreatmentsWithConfig without attributes', () async { + test('getTreatmentsWithConfig', () async { final result = await _platform.getTreatmentsWithConfig( matchingKey: 'matching-key', bucketingKey: 'bucketing-key', @@ -243,7 +267,7 @@ void main() { ]); }); - test('getTreatmentsByFlagSet without attributes', () async { + test('getTreatmentsByFlagSet', () async { final result = await _platform.getTreatmentsByFlagSet( matchingKey: 'matching-key', bucketingKey: 'bucketing-key', @@ -271,7 +295,7 @@ void main() { ]); }); - test('getTreatmentsByFlagSets without attributes', () async { + test('getTreatmentsByFlagSets', () async { final result = await _platform.getTreatmentsByFlagSets( matchingKey: 'matching-key', bucketingKey: 'bucketing-key', @@ -302,7 +326,7 @@ void main() { ]); }); - test('getTreatmentsWithConfigByFlagSet without attributes', () async { + test('getTreatmentsWithConfigByFlagSet', () async { final result = await _platform.getTreatmentsWithConfigByFlagSet( matchingKey: 'matching-key', bucketingKey: 'bucketing-key', @@ -342,7 +366,7 @@ void main() { ]); }); - test('getTreatmentsWithConfigByFlagSets without attributes', () async { + test('getTreatmentsWithConfigByFlagSets', () async { final result = await _platform.getTreatmentsWithConfigByFlagSets( matchingKey: 'matching-key', bucketingKey: 'bucketing-key', @@ -918,18 +942,24 @@ void main() { expect(onReadyFromCache, completion(equals(true))); }); - test('onTimeout', () { + test('onTimeout (in multiple clients)', () async { Future? onTimeout = _platform .onTimeout(matchingKey: 'matching-key', bucketingKey: 'bucketing-key') ?.then((value) => true); - // Emit SDK_READY_TIMED_OUT event + Future? onTimeoutClient2 = _platform + .onTimeout(matchingKey: 'matching-key-2', bucketingKey: null) + ?.then((value) => false); + + // Emit SDK_READY_TIMED_OUT event on the first client final mockClient = mock.mockFactory.client(buildJsKey('matching-key', 'bucketing-key')); mockClient.emit .callAsFunction(null, mockClient.Event.SDK_READY_TIMED_OUT); - expect(onTimeout, completion(equals(true))); + // Assert that onTimeout is completed for the first client only + await expectLater(onTimeout, completion(isTrue)); + await expectLater(onTimeoutClient2, doesNotComplete); }); test('onUpdated', () async { diff --git a/splitio_web/test/utils/js_interop_test_utils.dart b/splitio_web/test/utils/js_interop_test_utils.dart index 4db3f8e..9854815 100644 --- a/splitio_web/test/utils/js_interop_test_utils.dart +++ b/splitio_web/test/utils/js_interop_test_utils.dart @@ -25,12 +25,6 @@ class SplitioMock { JS_Configuration? _config; JSString _userConsent = 'UNKNOWN'.toJS; - JS_ReadinessStatus _readinessStatus = { - 'isReady': false, - 'isReadyFromCache': false, - 'hasTimedout': false, - }.jsify() as JS_ReadinessStatus; - Map> _eventListeners = {}; JSObject _createSplitViewJSObject(JSString splitName) { return { @@ -115,7 +109,8 @@ class SplitioMock { calls.add((methodName: 'client', methodArguments: [splitKey])); final dartKey = buildDartKey(splitKey ?? _config!.core.key); - final stringKey = buildKeyString(dartKey.matchingKey, dartKey.bucketingKey); + final stringKey = + buildKeyString(dartKey.matchingKey, dartKey.bucketingKey); _mockClients[stringKey] ??= _buildMockClient(); return _mockClients[stringKey]; }.toJS); @@ -146,7 +141,14 @@ class SplitioMock { } JS_IBrowserClient _buildMockClient() { + final JS_ReadinessStatus _readinessStatus = { + 'isReady': false, + 'isReadyFromCache': false, + 'hasTimedout': false, + }.jsify() as JS_ReadinessStatus; + final Map> _eventListeners = {}; final mockClient = JSObject() as JS_IBrowserClient; + mockClient.Event = _mockEvents; mockClient.on = (JSString event, JSFunction listener) { calls.add((methodName: 'on', methodArguments: [event, listener])); From 4f72823d89b0c073a9050d17054c4ff628af7f3f Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Sat, 10 Jan 2026 15:28:29 -0300 Subject: [PATCH 49/55] Add evaluation options to treatment method tests --- splitio_web/test/splitio_web_test.dart | 87 +++++++++++++++++--------- 1 file changed, 58 insertions(+), 29 deletions(-) diff --git a/splitio_web/test/splitio_web_test.dart b/splitio_web/test/splitio_web_test.dart index c59240a..a0a94f8 100644 --- a/splitio_web/test/splitio_web_test.dart +++ b/splitio_web/test/splitio_web_test.dart @@ -185,43 +185,50 @@ void main() { matchingKey: 'matching-key', bucketingKey: 'bucketing-key', splitNames: ['split1', 'split2'], - attributes: {'attr1': true}); + attributes: {'attr1': true}, + evaluationOptions: EvaluationOptions({'prop1': true})); expect(result, {'split1': 'on', 'split2': 'on'}); expect(mock.calls.last.methodName, 'getTreatments'); expect(mock.calls.last.methodArguments.map(jsAnyToDart), [ ['split1', 'split2'], {'attr1': true}, - {} + { + 'properties': {'prop1': true} + } ]); }); - test('getTreatmentWithConfig with attributes', () async { + test('getTreatmentWithConfig', () async { final result = await _platform.getTreatmentWithConfig( matchingKey: 'matching-key', bucketingKey: 'bucketing-key', - splitName: 'split1', - attributes: {'attr1': true}); + splitName: 'split1'); expect(result.toString(), SplitResult('on', 'some-config').toString()); expect(mock.calls.last.methodName, 'getTreatmentWithConfig'); - expect(mock.calls.last.methodArguments.map(jsAnyToDart), [ - 'split1', - {'attr1': true}, - {} - ]); + expect( + mock.calls.last.methodArguments.map(jsAnyToDart), ['split1', {}, {}]); }); - test('getTreatmentWithConfig', () async { + test('getTreatmentWithConfig with attributes and evaluation options', + () async { final result = await _platform.getTreatmentWithConfig( matchingKey: 'matching-key', bucketingKey: 'bucketing-key', - splitName: 'split1'); + splitName: 'split1', + attributes: {'attr1': true}, + evaluationOptions: EvaluationOptions({'prop1': true})); expect(result.toString(), SplitResult('on', 'some-config').toString()); expect(mock.calls.last.methodName, 'getTreatmentWithConfig'); - expect( - mock.calls.last.methodArguments.map(jsAnyToDart), ['split1', {}, {}]); + expect(mock.calls.last.methodArguments.map(jsAnyToDart), [ + 'split1', + {'attr1': true}, + { + 'properties': {'prop1': true} + } + ]); }); test('getTreatmentsWithConfig', () async { @@ -245,12 +252,14 @@ void main() { ]); }); - test('getTreatmentsWithConfig with attributes', () async { + test('getTreatmentsWithConfig with attributes and evaluation options', + () async { final result = await _platform.getTreatmentsWithConfig( matchingKey: 'matching-key', bucketingKey: 'bucketing-key', splitNames: ['split1', 'split2'], - attributes: {'attr1': true}); + attributes: {'attr1': true}, + evaluationOptions: EvaluationOptions({'prop1': true})); expect(result, predicate>((result) { return result.length == 2 && @@ -263,7 +272,9 @@ void main() { expect(mock.calls.last.methodArguments.map(jsAnyToDart), [ ['split1', 'split2'], {'attr1': true}, - {} + { + 'properties': {'prop1': true} + } ]); }); @@ -279,19 +290,23 @@ void main() { mock.calls.last.methodArguments.map(jsAnyToDart), ['set_1', {}, {}]); }); - test('getTreatmentsByFlagSet with attributes', () async { + test('getTreatmentsByFlagSet with attributes and evaluation options', + () async { final result = await _platform.getTreatmentsByFlagSet( matchingKey: 'matching-key', bucketingKey: 'bucketing-key', flagSet: 'set_1', - attributes: {'attr1': true}); + attributes: {'attr1': true}, + evaluationOptions: EvaluationOptions({'prop1': true})); expect(result, {'split1': 'on', 'split2': 'on'}); expect(mock.calls.last.methodName, 'getTreatmentsByFlagSet'); expect(mock.calls.last.methodArguments.map(jsAnyToDart), [ 'set_1', {'attr1': true}, - {} + { + 'properties': {'prop1': true} + } ]); }); @@ -310,19 +325,23 @@ void main() { ]); }); - test('getTreatmentsByFlagSets with attributes', () async { + test('getTreatmentsByFlagSets with attributes and evaluation options', + () async { final result = await _platform.getTreatmentsByFlagSets( matchingKey: 'matching-key', bucketingKey: 'bucketing-key', flagSets: ['set_1', 'set_2'], - attributes: {'attr1': true}); + attributes: {'attr1': true}, + evaluationOptions: EvaluationOptions({'prop1': true})); expect(result, {'split1': 'on', 'split2': 'on'}); expect(mock.calls.last.methodName, 'getTreatmentsByFlagSets'); expect(mock.calls.last.methodArguments.map(jsAnyToDart), [ ['set_1', 'set_2'], {'attr1': true}, - {} + { + 'properties': {'prop1': true} + } ]); }); @@ -344,12 +363,15 @@ void main() { mock.calls.last.methodArguments.map(jsAnyToDart), ['set_1', {}, {}]); }); - test('getTreatmentsWithConfigByFlagSet with attributes', () async { + test( + 'getTreatmentsWithConfigByFlagSet with attributes and evaluation options', + () async { final result = await _platform.getTreatmentsWithConfigByFlagSet( matchingKey: 'matching-key', bucketingKey: 'bucketing-key', flagSet: 'set_1', - attributes: {'attr1': true}); + attributes: {'attr1': true}, + evaluationOptions: EvaluationOptions({'prop1': true})); expect(result, predicate>((result) { return result.length == 2 && @@ -362,7 +384,9 @@ void main() { expect(mock.calls.last.methodArguments.map(jsAnyToDart), [ 'set_1', {'attr1': true}, - {} + { + 'properties': {'prop1': true} + } ]); }); @@ -387,12 +411,15 @@ void main() { ]); }); - test('getTreatmentsWithConfigByFlagSets with attributes', () async { + test( + 'getTreatmentsWithConfigByFlagSets with attributes and evaluation options', + () async { final result = await _platform.getTreatmentsWithConfigByFlagSets( matchingKey: 'matching-key', bucketingKey: 'bucketing-key', flagSets: ['set_1', 'set_2'], - attributes: {'attr1': true}); + attributes: {'attr1': true}, + evaluationOptions: EvaluationOptions({'prop1': true})); expect(result, predicate>((result) { return result.length == 2 && @@ -405,7 +432,9 @@ void main() { expect(mock.calls.last.methodArguments.map(jsAnyToDart), [ ['set_1', 'set_2'], {'attr1': true}, - {} + { + 'properties': {'prop1': true} + } ]); }); }); From 110ec5b2676219c3ac707979a292838a0c70e99e Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Sat, 10 Jan 2026 15:42:05 -0300 Subject: [PATCH 50/55] Remove callAsFunction calls from event methods --- splitio_web/lib/splitio_web.dart | 17 +++--- splitio_web/lib/src/js_interop.dart | 6 +- splitio_web/test/splitio_web_test.dart | 16 +++--- .../test/utils/js_interop_test_utils.dart | 55 +++++++++++-------- 4 files changed, 49 insertions(+), 45 deletions(-) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index a7e3119..82d3d5c 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -752,8 +752,7 @@ class SplitioWeb extends SplitioPlatform { } else { final completer = Completer(); - client.on.callAsFunction( - client, + client.on( client.Event.SDK_READY, () { completer.complete(); @@ -776,8 +775,7 @@ class SplitioWeb extends SplitioPlatform { } else { final completer = Completer(); - client.on.callAsFunction( - client, + client.on( client.Event.SDK_READY_FROM_CACHE, () { completer.complete(); @@ -800,8 +798,7 @@ class SplitioWeb extends SplitioPlatform { } else { final completer = Completer(); - client.on.callAsFunction( - client, + client.on( client.Event.SDK_READY_TIMED_OUT, () { completer.complete(); @@ -829,16 +826,16 @@ class SplitioWeb extends SplitioPlatform { controller = StreamController( onListen: () { - client.on.callAsFunction(client, client.Event.SDK_UPDATE, jsCallback); + client.on(client.Event.SDK_UPDATE, jsCallback); }, onPause: () { - client.off.callAsFunction(client, client.Event.SDK_UPDATE, jsCallback); + client.off(client.Event.SDK_UPDATE, jsCallback); }, onResume: () { - client.on.callAsFunction(client, client.Event.SDK_UPDATE, jsCallback); + client.on(client.Event.SDK_UPDATE, jsCallback); }, onCancel: () async { - client.off.callAsFunction(client, client.Event.SDK_UPDATE, jsCallback); + client.off(client.Event.SDK_UPDATE, jsCallback); if (!controller.isClosed) { await controller.close(); } diff --git a/splitio_web/lib/src/js_interop.dart b/splitio_web/lib/src/js_interop.dart index ac73ebc..31f66cd 100644 --- a/splitio_web/lib/src/js_interop.dart +++ b/splitio_web/lib/src/js_interop.dart @@ -193,9 +193,9 @@ extension type JS_IBrowserClient._(JSObject _) implements JSObject { external JSBoolean clearAttributes(); external JSPromise flush(); external JSPromise destroy(); - external JSFunction on; - external JSFunction off; - external JSFunction emit; + external JSVoid on(JSString event, JSFunction listener); + external JSVoid off(JSString event, JSFunction listener); + external JSVoid emit(JSString event); external JS_EventConsts Event; external JS_ReadinessStatus getStatus(); } diff --git a/splitio_web/test/splitio_web_test.dart b/splitio_web/test/splitio_web_test.dart index a0a94f8..110f46b 100644 --- a/splitio_web/test/splitio_web_test.dart +++ b/splitio_web/test/splitio_web_test.dart @@ -949,7 +949,7 @@ void main() { // Emit SDK_READY event final mockClient = mock.mockFactory.client(buildJsKey('matching-key', 'bucketing-key')); - mockClient.emit.callAsFunction(null, mockClient.Event.SDK_READY); + mockClient.emit(mockClient.Event.SDK_READY); expect(onReady, completion(equals(true))); }); @@ -960,8 +960,7 @@ void main() { // Emit SDK_READY_FROM_CACHE event final mockClient = mock.mockFactory.client(buildJsKey('matching-key', 'bucketing-key')); - mockClient.emit - .callAsFunction(null, mockClient.Event.SDK_READY_FROM_CACHE); + mockClient.emit(mockClient.Event.SDK_READY_FROM_CACHE); Future? onReadyFromCache = _platform .onReadyFromCache( @@ -983,8 +982,7 @@ void main() { // Emit SDK_READY_TIMED_OUT event on the first client final mockClient = mock.mockFactory.client(buildJsKey('matching-key', 'bucketing-key')); - mockClient.emit - .callAsFunction(null, mockClient.Event.SDK_READY_TIMED_OUT); + mockClient.emit(mockClient.Event.SDK_READY_TIMED_OUT); // Assert that onTimeout is completed for the first client only await expectLater(onTimeout, completion(isTrue)); @@ -1003,21 +1001,21 @@ void main() { final subscription = stream.listen(expectAsync1((_) {}, count: 3)); // Emit SDK_UPDATE events. Should be received - mockClient.emit.callAsFunction(null, mockClient.Event.SDK_UPDATE); + mockClient.emit(mockClient.Event.SDK_UPDATE); - mockClient.emit.callAsFunction(null, mockClient.Event.SDK_UPDATE); + mockClient.emit(mockClient.Event.SDK_UPDATE); await Future.delayed( const Duration(milliseconds: 100)); // let events deliver // Pause subscription and emit SDK_UPDATE event. Should not be received subscription.pause(); - mockClient.emit.callAsFunction(null, mockClient.Event.SDK_UPDATE); + mockClient.emit(mockClient.Event.SDK_UPDATE); await Future.delayed(const Duration(milliseconds: 100)); // Resume subscription and emit SDK_UPDATE event. Should be received subscription.resume(); - mockClient.emit.callAsFunction(null, mockClient.Event.SDK_UPDATE); + mockClient.emit(mockClient.Event.SDK_UPDATE); await Future.delayed(const Duration(milliseconds: 100)); await subscription.cancel(); diff --git a/splitio_web/test/utils/js_interop_test_utils.dart b/splitio_web/test/utils/js_interop_test_utils.dart index 9854815..a9eeec1 100644 --- a/splitio_web/test/utils/js_interop_test_utils.dart +++ b/splitio_web/test/utils/js_interop_test_utils.dart @@ -150,29 +150,38 @@ class SplitioMock { final mockClient = JSObject() as JS_IBrowserClient; mockClient.Event = _mockEvents; - mockClient.on = (JSString event, JSFunction listener) { - calls.add((methodName: 'on', methodArguments: [event, listener])); - _eventListeners[event] ??= Set(); - _eventListeners[event]!.add(listener); - }.toJS; - mockClient.off = (JSString event, JSFunction listener) { - calls.add((methodName: 'off', methodArguments: [event, listener])); - _eventListeners[event] ??= Set(); - _eventListeners[event]!.remove(listener); - }.toJS; - mockClient.emit = (JSString event) { - calls.add((methodName: 'emit', methodArguments: [event])); - _eventListeners[event]?.forEach((listener) { - listener.callAsFunction(null, event); - }); - if (event == _mockEvents.SDK_READY) { - _readinessStatus.isReady = true.toJS; - } else if (event == _mockEvents.SDK_READY_FROM_CACHE) { - _readinessStatus.isReadyFromCache = true.toJS; - } else if (event == _mockEvents.SDK_READY_TIMED_OUT) { - _readinessStatus.hasTimedout = true.toJS; - } - }.toJS; + reflectSet( + mockClient, + 'on'.toJS, + (JSString event, JSFunction listener) { + calls.add((methodName: 'on', methodArguments: [event, listener])); + _eventListeners[event] ??= Set(); + _eventListeners[event]!.add(listener); + }.toJS); + reflectSet( + mockClient, + 'off'.toJS, + (JSString event, JSFunction listener) { + calls.add((methodName: 'off', methodArguments: [event, listener])); + _eventListeners[event] ??= Set(); + _eventListeners[event]!.remove(listener); + }.toJS); + reflectSet( + mockClient, + 'emit'.toJS, + (JSString event) { + calls.add((methodName: 'emit', methodArguments: [event])); + _eventListeners[event]?.forEach((listener) { + listener.callAsFunction(null, event); + }); + if (event == _mockEvents.SDK_READY) { + _readinessStatus.isReady = true.toJS; + } else if (event == _mockEvents.SDK_READY_FROM_CACHE) { + _readinessStatus.isReadyFromCache = true.toJS; + } else if (event == _mockEvents.SDK_READY_TIMED_OUT) { + _readinessStatus.hasTimedout = true.toJS; + } + }.toJS); reflectSet( mockClient, 'getStatus'.toJS, From 3f29ce3baf2148f0d2ac63c53713aa3b6f9b1939 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Mon, 12 Jan 2026 11:02:20 -0300 Subject: [PATCH 51/55] Remove callAsFunction from logger factory methods. Add test for Logger modules. --- splitio_web/lib/splitio_web.dart | 26 +++++------ splitio_web/lib/src/js_interop.dart | 19 +++++--- splitio_web/test/splitio_web_test.dart | 43 +++++++++++++++++++ .../test/utils/js_interop_test_utils.dart | 40 ++++++++++++++++- 4 files changed, 108 insertions(+), 20 deletions(-) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index 82d3d5c..640bbae 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -241,33 +241,35 @@ class SplitioWeb extends SplitioPlatform { final logLevel = configuration.configurationMap['logLevel']; if (logLevel is String) { - JSAny? logger; switch (SplitLogLevel.values.firstWhere((e) => e.name == logLevel)) { case SplitLogLevel.verbose: case SplitLogLevel.debug: - logger = window.splitio!.DebugLogger?.callAsFunction(); + config.debug = window.splitio!.DebugLogger != null + ? window.splitio!.DebugLogger!() + : 'DEBUG'.toJS; break; case SplitLogLevel.info: - logger = window.splitio!.InfoLogger?.callAsFunction(); + config.debug = window.splitio!.InfoLogger != null + ? window.splitio!.InfoLogger!() + : 'INFO'.toJS; break; case SplitLogLevel.warning: - logger = window.splitio!.WarnLogger?.callAsFunction(); + config.debug = window.splitio!.WarnLogger != null + ? window.splitio!.WarnLogger!() + : 'WARNING'.toJS; break; case SplitLogLevel.error: - logger = window.splitio!.ErrorLogger?.callAsFunction(); + config.debug = window.splitio!.ErrorLogger != null + ? window.splitio!.ErrorLogger!() + : 'ERROR'.toJS; break; default: break; } - if (logger != null) { - config.debug = logger; // Browser SDK - } else { - config.debug = logLevel.toUpperCase().toJS; // JS SDK - } } else if (configuration.configurationMap['enableDebug'] == true) { config.debug = window.splitio!.DebugLogger != null - ? window.splitio!.DebugLogger!.callAsFunction() // Browser SDK - : 'DEBUG'.toJS; // JS SDK + ? window.splitio!.DebugLogger!() + : 'DEBUG'.toJS; } if (configuration.configurationMap['readyTimeout'] != null) { diff --git a/splitio_web/lib/src/js_interop.dart b/splitio_web/lib/src/js_interop.dart index 31f66cd..43356a8 100644 --- a/splitio_web/lib/src/js_interop.dart +++ b/splitio_web/lib/src/js_interop.dart @@ -26,7 +26,7 @@ extension type JS_ImpressionData._(JSObject _) implements JSObject { } @JS() -extension type JS_Logger._(JSObject _) implements JSObject { +extension type JS_ILogger._(JSObject _) implements JSObject { external JSAny? debug(JSString message); external JSAny? info(JSString message); external JSAny? warn(JSString message); @@ -106,7 +106,7 @@ extension type JS_Configuration._(JSObject _) implements JSObject { @JS() extension type JS_ISettings._(JSObject _) implements JS_Configuration { - external JS_Logger log; + external JS_ILogger log; external JS_IImpressionListener? impressionListener; } @@ -215,14 +215,19 @@ extension type JS_IBrowserSDK._(JSObject _) implements JSObject { external JS_IUserConsentAPI UserConsent; } +@JS() +extension type JS_LoggerFactory._(JSFunction _) implements JSFunction { + external JSObject call(); +} + @JS() extension type JS_BrowserSDKPackage._(JSObject _) implements JSObject { external JS_IBrowserSDK SplitFactory(JS_Configuration config); external JSFunction? InLocalStorage; - external JSFunction? DebugLogger; - external JSFunction? InfoLogger; - external JSFunction? WarnLogger; - external JSFunction? ErrorLogger; + external JS_LoggerFactory? DebugLogger; + external JS_LoggerFactory? InfoLogger; + external JS_LoggerFactory? WarnLogger; + external JS_LoggerFactory? ErrorLogger; } // Conversion utils: JS to Dart types @@ -234,7 +239,7 @@ external JSArray objectKeys(JSObject obj); external JSAny? reflectGet(JSObject target, JSAny propertyKey); @JS('Reflect.set') -external JSAny? reflectSet(JSObject target, JSAny propertyKey, JSAny value); +external JSAny? reflectSet(JSObject target, JSAny propertyKey, JSAny? value); @JS('JSON.parse') external JSObject jsonParse(JSString obj); diff --git a/splitio_web/test/splitio_web_test.dart b/splitio_web/test/splitio_web_test.dart index 110f46b..56d0452 100644 --- a/splitio_web/test/splitio_web_test.dart +++ b/splitio_web/test/splitio_web_test.dart @@ -838,6 +838,49 @@ void main() { 'storage': {'type': 'LOCALSTORAGE'} })); }); + + test( + 'init with InLocalStorage and Logger factory modules (Browser SDK full CDN)', + () async { + mock.addFactoryModules(); + SplitioWeb _platform = SplitioWeb(); + + await _platform.init( + apiKey: 'api-key', + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + sdkConfiguration: SplitConfiguration( + logLevel: SplitLogLevel.info, + rolloutCacheConfiguration: RolloutCacheConfiguration( + expirationDays: 100, + clearOnInit: true, + ))); + + expect(mock.calls[mock.calls.length - 2].methodName, 'InfoLogger'); + + expect(mock.calls.last.methodName, 'SplitFactory'); + expect( + jsAnyToDart(mock.calls.last.methodArguments[0]), + equals({ + 'core': { + 'authorizationKey': 'api-key', + 'key': { + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + }, + }, + 'startup': { + 'readyTimeout': 10, + }, + 'scheduler': {}, + 'urls': {}, + 'sync': {}, + 'debug': {}, + 'storage': {'type': 'LOCALSTORAGE', 'expirationDays': 100, 'clearOnInit': true } + })); + + mock.removeFactoryModules(); + }); }); group('client', () { diff --git a/splitio_web/test/utils/js_interop_test_utils.dart b/splitio_web/test/utils/js_interop_test_utils.dart index a9eeec1..d227ba6 100644 --- a/splitio_web/test/utils/js_interop_test_utils.dart +++ b/splitio_web/test/utils/js_interop_test_utils.dart @@ -77,7 +77,7 @@ class SplitioMock { return ['split1'.toJS, 'split2'.toJS].jsify(); }.toJS); - final mockLog = JSObject() as JS_Logger; + final mockLog = JSObject() as JS_ILogger; reflectSet( mockLog, 'warn'.toJS, @@ -397,4 +397,42 @@ class SplitioMock { return mockClient; } + + void addFactoryModules() { + reflectSet( + splitio, + 'DebugLogger'.toJS, + () { + calls.add((methodName: 'DebugLogger', methodArguments: [])); + return JSObject(); + }.toJS); + reflectSet( + splitio, + 'InfoLogger'.toJS, + () { + calls.add((methodName: 'InfoLogger', methodArguments: [])); + return JSObject(); + }.toJS); + reflectSet( + splitio, + 'WarnLogger'.toJS, + () { + calls.add((methodName: 'WarnLogger', methodArguments: [])); + return JSObject(); + }.toJS); + reflectSet( + splitio, + 'ErrorLogger'.toJS, + () { + calls.add((methodName: 'ErrorLogger', methodArguments: [])); + return JSObject(); + }.toJS); + } + + void removeFactoryModules() { + reflectSet(splitio, 'DebugLogger'.toJS, null); + reflectSet(splitio, 'InfoLogger'.toJS, null); + reflectSet(splitio, 'WarnLogger'.toJS, null); + reflectSet(splitio, 'ErrorLogger'.toJS, null); + } } From aa8866db22c81547e8f48fe18acc4f3c3e4fffa4 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Mon, 12 Jan 2026 11:22:42 -0300 Subject: [PATCH 52/55] Add InLocalStorage mock and update test expectations --- splitio_web/test/splitio_web_test.dart | 9 +++++++-- splitio_web/test/utils/js_interop_test_utils.dart | 9 +++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/splitio_web/test/splitio_web_test.dart b/splitio_web/test/splitio_web_test.dart index 56d0452..4bde7a5 100644 --- a/splitio_web/test/splitio_web_test.dart +++ b/splitio_web/test/splitio_web_test.dart @@ -856,7 +856,12 @@ void main() { clearOnInit: true, ))); - expect(mock.calls[mock.calls.length - 2].methodName, 'InfoLogger'); + expect(mock.calls[mock.calls.length - 3].methodName, 'InfoLogger'); + expect(mock.calls[mock.calls.length - 2].methodName, 'InLocalStorage'); + expect( + mock.calls[mock.calls.length - 2].methodArguments.map(jsAnyToDart), [ + {'type': 'LOCALSTORAGE', 'expirationDays': 100, 'clearOnInit': true} + ]); expect(mock.calls.last.methodName, 'SplitFactory'); expect( @@ -876,7 +881,7 @@ void main() { 'urls': {}, 'sync': {}, 'debug': {}, - 'storage': {'type': 'LOCALSTORAGE', 'expirationDays': 100, 'clearOnInit': true } + 'storage': {} })); mock.removeFactoryModules(); diff --git a/splitio_web/test/utils/js_interop_test_utils.dart b/splitio_web/test/utils/js_interop_test_utils.dart index d227ba6..c278c64 100644 --- a/splitio_web/test/utils/js_interop_test_utils.dart +++ b/splitio_web/test/utils/js_interop_test_utils.dart @@ -427,6 +427,14 @@ class SplitioMock { calls.add((methodName: 'ErrorLogger', methodArguments: [])); return JSObject(); }.toJS); + reflectSet( + splitio, + 'InLocalStorage'.toJS, + (JS_ConfigurationStorage storageConfig) { + calls.add( + (methodName: 'InLocalStorage', methodArguments: [storageConfig])); + return JSObject(); + }.toJS); } void removeFactoryModules() { @@ -434,5 +442,6 @@ class SplitioMock { reflectSet(splitio, 'InfoLogger'.toJS, null); reflectSet(splitio, 'WarnLogger'.toJS, null); reflectSet(splitio, 'ErrorLogger'.toJS, null); + reflectSet(splitio, 'InLocalStorage'.toJS, null); } } From b7e882a7361aca4f569cee9249e44f27935e3eeb Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 13 Jan 2026 11:40:22 -0300 Subject: [PATCH 53/55] Update urls config test --- splitio_web/test/splitio_web_test.dart | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/splitio_web/test/splitio_web_test.dart b/splitio_web/test/splitio_web_test.dart index a0a94f8..6dcb12a 100644 --- a/splitio_web/test/splitio_web_test.dart +++ b/splitio_web/test/splitio_web_test.dart @@ -700,12 +700,12 @@ void main() { streamingEnabled: false, persistentAttributesEnabled: true, // unsupported in Web impressionListener: true, - sdkEndpoint: 'https://sdk.split-stage.io/api', - eventsEndpoint: 'https://events.split-stage.io/api', - authServiceEndpoint: 'https://auth.split-stage.io/api/v2', - streamingServiceEndpoint: 'https://streaming.split.io/sse', + sdkEndpoint: 'https://sdk.domain/api', + eventsEndpoint: 'https://events.domain/api', + authServiceEndpoint: 'https://auth.domain/api/v2', + streamingServiceEndpoint: 'https://streaming.domain/sse', telemetryServiceEndpoint: - 'https://telemetry.split-stage.io/api/v1', + 'https://telemetry.domain/api/v1', syncConfig: SyncConfig( names: ['flag_1', 'flag_2'], prefixes: ['prefix_1']), impressionsMode: ImpressionsMode.none, @@ -747,11 +747,11 @@ void main() { 'eventsPushRate': 7, }, 'urls': { - 'sdk': 'https://sdk.split-stage.io/api', - 'events': 'https://events.split-stage.io/api', - 'auth': 'https://auth.split-stage.io/api', - 'streaming': 'https://streaming.split.io', - 'telemetry': 'https://telemetry.split-stage.io/api', + 'sdk': 'https://sdk.domain/api', + 'events': 'https://events.domain/api', + 'auth': 'https://auth.domain/api', + 'streaming': 'https://streaming.domain', + 'telemetry': 'https://telemetry.domain/api', }, 'sync': { 'impressionsMode': 'NONE', From a6fcbad41863058b55906fadf03bcdf3bd495319 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 13 Jan 2026 16:34:21 -0300 Subject: [PATCH 54/55] Fix script path and JS interop conversions for WASM support. Define required Dark and Flutter minimum versions --- splitio_web/lib/splitio_web.dart | 2 +- splitio_web/lib/src/js_interop.dart | 40 +++++++------------------- splitio_web/pubspec.yaml | 4 +-- splitio_web/test/splitio_web_test.dart | 12 ++++---- 4 files changed, 20 insertions(+), 38 deletions(-) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index 640bbae..7ac70a2 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -92,7 +92,7 @@ class SplitioWeb extends SplitioPlatform { // Create and inject script tag final script = document.createElement('script') as HTMLScriptElement; script.type = 'text/javascript'; - script.src = 'packages/splitio_web/web/split-browser-1.6.0.full.min.js'; + script.src = 'assets/packages/splitio_web/web/split-browser-1.6.0.full.min.js'; // Wait for script to load final completer = Completer(); diff --git a/splitio_web/lib/src/js_interop.dart b/splitio_web/lib/src/js_interop.dart index 43356a8..46ea5c3 100644 --- a/splitio_web/lib/src/js_interop.dart +++ b/splitio_web/lib/src/js_interop.dart @@ -236,40 +236,21 @@ extension type JS_BrowserSDKPackage._(JSObject _) implements JSObject { external JSArray objectKeys(JSObject obj); @JS('Reflect.get') -external JSAny? reflectGet(JSObject target, JSAny propertyKey); +external JSAny? reflectGet(JSObject target, JSString propertyKey); @JS('Reflect.set') -external JSAny? reflectSet(JSObject target, JSAny propertyKey, JSAny? value); +external JSAny? reflectSet(JSObject target, JSString propertyKey, JSAny? value); @JS('JSON.parse') external JSObject jsonParse(JSString obj); -List jsArrayToList(JSArray obj) { - return obj.toDart.map(jsAnyToDart).toList(); -} - -Map jsObjectToMap(JSObject obj) { - return { - for (final jsKey in objectKeys(obj).toDart) - jsKey.toDart: jsAnyToDart(reflectGet(obj, jsKey)), - }; -} - -dynamic jsAnyToDart(JSAny? value) { - if (value is JSArray) { - return jsArrayToList(value); - } else if (value is JSObject) { - return jsObjectToMap(value); - } else if (value is JSString) { - return value.toDart; - } else if (value is JSNumber) { - return value.toDartDouble; - } else if (value is JSBoolean) { - return value.toDart; - } else { - return value; // JS null and undefined are null in Dart - } -} +List jsArrayToList(JSArray obj) => + (obj.dartify() as List).cast(); + +Map jsObjectToMap(JSObject obj) => + (obj.dartify() as Map).cast(); + +Object? jsAnyToDart(JSAny? value) => value.dartify(); // Conversion utils: JS SDK to Flutter SDK types @@ -283,8 +264,7 @@ Map jsTreatmentsWithConfigToMap(JSObject obj) { } SplitResult jsTreatmentWithConfigToSplitResult(JS_TreatmentWithConfig obj) { - return SplitResult(obj.treatment.toDart, - (obj.config is JSString) ? obj.config!.toDart : null); + return SplitResult(obj.treatment.toDart, obj.config?.toDart); } Prerequisite jsPrerequisiteToPrerequisite(JS_Prerequisite obj) { diff --git a/splitio_web/pubspec.yaml b/splitio_web/pubspec.yaml index d5abf73..9c26042 100644 --- a/splitio_web/pubspec.yaml +++ b/splitio_web/pubspec.yaml @@ -4,8 +4,8 @@ repository: https://github.com/splitio/flutter-sdk-plugin/tree/main/splitio_web version: 1.0.0 environment: - sdk: ">=3.3.0 <4.0.0" - flutter: ">=3.16.0" + sdk: ">=3.3.0 <4.0.0" # using Dart 3.3+ extension types for JS interop + flutter: ">=3.19.0" # lowest Flutter version that includes Dart 3.3+ flutter: plugin: diff --git a/splitio_web/test/splitio_web_test.dart b/splitio_web/test/splitio_web_test.dart index 4bde7a5..7049de5 100644 --- a/splitio_web/test/splitio_web_test.dart +++ b/splitio_web/test/splitio_web_test.dart @@ -5,10 +5,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:splitio_web/splitio_web.dart'; import 'package:splitio_web/src/js_interop.dart'; import 'package:splitio_platform_interface/split_certificate_pinning_configuration.dart'; -import 'package:splitio_platform_interface/split_configuration.dart'; -import 'package:splitio_platform_interface/split_evaluation_options.dart'; import 'package:splitio_platform_interface/split_sync_config.dart'; -import 'package:splitio_platform_interface/split_result.dart'; import 'package:splitio_platform_interface/split_rollout_cache_configuration.dart'; import 'utils/js_interop_test_utils.dart'; @@ -722,8 +719,10 @@ void main() { ))); expect(mock.calls[mock.calls.length - 5].methodName, 'SplitFactory'); + final actual = + jsAnyToDart(mock.calls[mock.calls.length - 5].methodArguments[0]); expect( - jsAnyToDart(mock.calls[mock.calls.length - 5].methodArguments[0]), + actual, equals({ 'core': { 'authorizationKey': 'api-key', @@ -773,7 +772,10 @@ void main() { 'expirationDays': 100, 'clearOnInit': true }, - 'impressionListener': {'logImpression': {}} + 'impressionListener': { + 'logImpression': (actual as Map)['impressionListener'] + ['logImpression'] + } })); expect(mock.calls[mock.calls.length - 4].methodName, 'warn'); From 009c5588d2a39688213503053b1903d473ae2389 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 14 Jan 2026 13:00:38 -0300 Subject: [PATCH 55/55] Polishing --- splitio_web/lib/splitio_web.dart | 7 ++++--- splitio_web/lib/src/js_interop.dart | 12 ------------ splitio_web/test/splitio_web_test.dart | 6 +++--- splitio_web/test/utils/js_interop_test_utils.dart | 13 +++++++++++++ 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index 5736076..4f08bb9 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -33,7 +33,7 @@ class SplitioWeb extends SplitioPlatform { required String matchingKey, required String? bucketingKey, SplitConfiguration? sdkConfiguration, - }) async { + }) { if (_initFuture == null) { _initFuture = this._init( apiKey: apiKey, @@ -41,7 +41,7 @@ class SplitioWeb extends SplitioPlatform { bucketingKey: bucketingKey, sdkConfiguration: sdkConfiguration); } - return _initFuture; + return _initFuture!; } Future _init({ @@ -92,7 +92,8 @@ class SplitioWeb extends SplitioPlatform { // Create and inject script tag final script = document.createElement('script') as HTMLScriptElement; script.type = 'text/javascript'; - script.src = 'assets/packages/splitio_web/web/split-browser-1.6.0.full.min.js'; + script.src = + 'assets/packages/splitio_web/web/split-browser-1.6.0.full.min.js'; // Wait for script to load final completer = Completer(); diff --git a/splitio_web/lib/src/js_interop.dart b/splitio_web/lib/src/js_interop.dart index 46ea5c3..95a4870 100644 --- a/splitio_web/lib/src/js_interop.dart +++ b/splitio_web/lib/src/js_interop.dart @@ -316,18 +316,6 @@ JSAny buildJsKey(String matchingKey, String? bucketingKey) { return matchingKey.toJS; } -({String matchingKey, String? bucketingKey}) buildDartKey(JSAny splitKey) { - return splitKey is JSString - ? (matchingKey: splitKey.toDart, bucketingKey: null) - : ( - matchingKey: - (reflectGet(splitKey as JSObject, 'matchingKey'.toJS) as JSString) - .toDart, - bucketingKey: - (reflectGet(splitKey, 'bucketingKey'.toJS) as JSString).toDart, - ); -} - String buildKeyString(String matchingKey, String? bucketingKey) { return bucketingKey == null ? matchingKey : '${matchingKey}_$bucketingKey'; } diff --git a/splitio_web/test/splitio_web_test.dart b/splitio_web/test/splitio_web_test.dart index a79039b..056ec3c 100644 --- a/splitio_web/test/splitio_web_test.dart +++ b/splitio_web/test/splitio_web_test.dart @@ -701,8 +701,7 @@ void main() { eventsEndpoint: 'https://events.domain/api', authServiceEndpoint: 'https://auth.domain/api/v2', streamingServiceEndpoint: 'https://streaming.domain/sse', - telemetryServiceEndpoint: - 'https://telemetry.domain/api/v1', + telemetryServiceEndpoint: 'https://telemetry.domain/api/v1', syncConfig: SyncConfig( names: ['flag_1', 'flag_2'], prefixes: ['prefix_1']), impressionsMode: ImpressionsMode.none, @@ -1062,7 +1061,8 @@ void main() { await Future.delayed(Duration.zero); // onResume is async mockClient.emit(mockClient.Event.SDK_UPDATE); - await Future.delayed(Duration.zero); // let last event deliver before cancel + await Future.delayed( + Duration.zero); // let last event deliver before cancel await subscription.cancel(); }); }); diff --git a/splitio_web/test/utils/js_interop_test_utils.dart b/splitio_web/test/utils/js_interop_test_utils.dart index c278c64..667fbc9 100644 --- a/splitio_web/test/utils/js_interop_test_utils.dart +++ b/splitio_web/test/utils/js_interop_test_utils.dart @@ -7,6 +7,19 @@ external JSPromise _promiseResolve(); @JS('Object.assign') external JSObject _objectAssign(JSObject target, JSObject source); +// Not WASM-compatible. Currently used only in tests +({String matchingKey, String? bucketingKey}) buildDartKey(JSAny splitKey) { + return splitKey is JSString + ? (matchingKey: splitKey.toDart, bucketingKey: null) + : ( + matchingKey: + (reflectGet(splitKey as JSObject, 'matchingKey'.toJS) as JSString) + .toDart, + bucketingKey: + (reflectGet(splitKey, 'bucketingKey'.toJS) as JSString).toDart, + ); +} + class SplitioMock { // JS Browser SDK API mock final JS_BrowserSDKPackage splitio = JSObject() as JS_BrowserSDKPackage;