react-native-nitro-amplitude 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.watchmanconfig +6 -0
- package/LICENSE +21 -0
- package/README.md +139 -0
- package/android/CMakeLists.txt +34 -0
- package/android/build.gradle +85 -0
- package/android/consumer-rules.pro +35 -0
- package/android/gradle.properties +4 -0
- package/android/src/main/cpp/AndroidAmplitudeAdapterCpp.cpp +126 -0
- package/android/src/main/cpp/AndroidAmplitudeAdapterCpp.hpp +48 -0
- package/android/src/main/cpp/cpp-adapter.cpp +9 -0
- package/android/src/main/java/com/nitroamplitude/AndroidAmplitudeAdapter.kt +147 -0
- package/android/src/main/java/com/nitroamplitude/NitroAmplitudePackage.kt +24 -0
- package/app.plugin.js +36 -0
- package/cpp/bindings/HybridAmplitudeContext.cpp +83 -0
- package/cpp/bindings/HybridAmplitudeContext.hpp +29 -0
- package/cpp/bindings/HybridAmplitudeStorage.cpp +163 -0
- package/cpp/bindings/HybridAmplitudeStorage.hpp +38 -0
- package/cpp/bindings/HybridAmplitudeWorker.cpp +162 -0
- package/cpp/bindings/HybridAmplitudeWorker.hpp +74 -0
- package/cpp/core/NativeAmplitudeAdapter.hpp +44 -0
- package/ios/IOSAmplitudeAdapterCpp.hpp +37 -0
- package/ios/IOSAmplitudeAdapterCpp.mm +155 -0
- package/lib/commonjs/AmplitudeContext.nitro.js +6 -0
- package/lib/commonjs/AmplitudeContext.nitro.js.map +1 -0
- package/lib/commonjs/AmplitudeStorage.nitro.js +6 -0
- package/lib/commonjs/AmplitudeStorage.nitro.js.map +1 -0
- package/lib/commonjs/AmplitudeWorker.nitro.js +6 -0
- package/lib/commonjs/AmplitudeWorker.nitro.js.map +1 -0
- package/lib/commonjs/analytics/campaign/campaign-tracker.js +105 -0
- package/lib/commonjs/analytics/campaign/campaign-tracker.js.map +1 -0
- package/lib/commonjs/analytics/campaign/types.js +6 -0
- package/lib/commonjs/analytics/campaign/types.js.map +1 -0
- package/lib/commonjs/analytics/config.js +283 -0
- package/lib/commonjs/analytics/config.js.map +1 -0
- package/lib/commonjs/analytics/cookie-migration/index.js +59 -0
- package/lib/commonjs/analytics/cookie-migration/index.js.map +1 -0
- package/lib/commonjs/analytics/index.js +95 -0
- package/lib/commonjs/analytics/index.js.map +1 -0
- package/lib/commonjs/analytics/migration/remnant-data-migration.js +177 -0
- package/lib/commonjs/analytics/migration/remnant-data-migration.js.map +1 -0
- package/lib/commonjs/analytics/nitro-transport.js +31 -0
- package/lib/commonjs/analytics/nitro-transport.js.map +1 -0
- package/lib/commonjs/analytics/plugins/context.js +140 -0
- package/lib/commonjs/analytics/plugins/context.js.map +1 -0
- package/lib/commonjs/analytics/react-native-client.js +352 -0
- package/lib/commonjs/analytics/react-native-client.js.map +1 -0
- package/lib/commonjs/analytics/storage/local-storage.js +73 -0
- package/lib/commonjs/analytics/storage/local-storage.js.map +1 -0
- package/lib/commonjs/analytics/types.js +175 -0
- package/lib/commonjs/analytics/types.js.map +1 -0
- package/lib/commonjs/analytics/typings/browser-snippet.d.js +6 -0
- package/lib/commonjs/analytics/typings/browser-snippet.d.js.map +1 -0
- package/lib/commonjs/analytics/typings/ua-parser.d.js +2 -0
- package/lib/commonjs/analytics/typings/ua-parser.d.js.map +1 -0
- package/lib/commonjs/analytics/utils/platform.js +16 -0
- package/lib/commonjs/analytics/utils/platform.js.map +1 -0
- package/lib/commonjs/analytics/version.js +8 -0
- package/lib/commonjs/analytics/version.js.map +1 -0
- package/lib/commonjs/experiment/experimentClient.js +792 -0
- package/lib/commonjs/experiment/experimentClient.js.map +1 -0
- package/lib/commonjs/experiment/factory.js +77 -0
- package/lib/commonjs/experiment/factory.js.map +1 -0
- package/lib/commonjs/experiment/gen/version.js +9 -0
- package/lib/commonjs/experiment/gen/version.js.map +1 -0
- package/lib/commonjs/experiment/index.js +143 -0
- package/lib/commonjs/experiment/index.js.map +1 -0
- package/lib/commonjs/experiment/integration/NativeExperimentReactNativeClient.js +11 -0
- package/lib/commonjs/experiment/integration/NativeExperimentReactNativeClient.js.map +1 -0
- package/lib/commonjs/experiment/integration/connector.js +67 -0
- package/lib/commonjs/experiment/integration/connector.js.map +1 -0
- package/lib/commonjs/experiment/integration/default.js +92 -0
- package/lib/commonjs/experiment/integration/default.js.map +1 -0
- package/lib/commonjs/experiment/logger/ampLogger.js +80 -0
- package/lib/commonjs/experiment/logger/ampLogger.js.map +1 -0
- package/lib/commonjs/experiment/logger/consoleLogger.js +60 -0
- package/lib/commonjs/experiment/logger/consoleLogger.js.map +1 -0
- package/lib/commonjs/experiment/storage/cache.js +197 -0
- package/lib/commonjs/experiment/storage/cache.js.map +1 -0
- package/lib/commonjs/experiment/storage/local-storage.js +25 -0
- package/lib/commonjs/experiment/storage/local-storage.js.map +1 -0
- package/lib/commonjs/experiment/stubClient.js +46 -0
- package/lib/commonjs/experiment/stubClient.js.map +1 -0
- package/lib/commonjs/experiment/transport/http.js +72 -0
- package/lib/commonjs/experiment/transport/http.js.map +1 -0
- package/lib/commonjs/experiment/types/client.js +6 -0
- package/lib/commonjs/experiment/types/client.js.map +1 -0
- package/lib/commonjs/experiment/types/config.js +67 -0
- package/lib/commonjs/experiment/types/config.js.map +1 -0
- package/lib/commonjs/experiment/types/exposure.js +2 -0
- package/lib/commonjs/experiment/types/exposure.js.map +1 -0
- package/lib/commonjs/experiment/types/logger.js +42 -0
- package/lib/commonjs/experiment/types/logger.js.map +1 -0
- package/lib/commonjs/experiment/types/source.js +54 -0
- package/lib/commonjs/experiment/types/source.js.map +1 -0
- package/lib/commonjs/experiment/types/storage.js +2 -0
- package/lib/commonjs/experiment/types/storage.js.map +1 -0
- package/lib/commonjs/experiment/types/transport.js +2 -0
- package/lib/commonjs/experiment/types/transport.js.map +1 -0
- package/lib/commonjs/experiment/types/user.js +2 -0
- package/lib/commonjs/experiment/types/user.js.map +1 -0
- package/lib/commonjs/experiment/types/variant.js +2 -0
- package/lib/commonjs/experiment/types/variant.js.map +1 -0
- package/lib/commonjs/experiment/util/backoff.js +54 -0
- package/lib/commonjs/experiment/util/backoff.js.map +1 -0
- package/lib/commonjs/experiment/util/base64.js +23 -0
- package/lib/commonjs/experiment/util/base64.js.map +1 -0
- package/lib/commonjs/experiment/util/convert.js +78 -0
- package/lib/commonjs/experiment/util/convert.js.map +1 -0
- package/lib/commonjs/experiment/util/index.js +25 -0
- package/lib/commonjs/experiment/util/index.js.map +1 -0
- package/lib/commonjs/experiment/util/platform.js +16 -0
- package/lib/commonjs/experiment/util/platform.js.map +1 -0
- package/lib/commonjs/experiment/util/randomstring.js +16 -0
- package/lib/commonjs/experiment/util/randomstring.js.map +1 -0
- package/lib/commonjs/experiment/util/userSessionExposureTracker.js +40 -0
- package/lib/commonjs/experiment/util/userSessionExposureTracker.js.map +1 -0
- package/lib/commonjs/index.js +298 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/index.web.js +22 -0
- package/lib/commonjs/index.web.js.map +1 -0
- package/lib/commonjs/native/context.js +43 -0
- package/lib/commonjs/native/context.js.map +1 -0
- package/lib/commonjs/native/http.js +62 -0
- package/lib/commonjs/native/http.js.map +1 -0
- package/lib/commonjs/native/hybrid.js +31 -0
- package/lib/commonjs/native/hybrid.js.map +1 -0
- package/lib/commonjs/native/storage.js +93 -0
- package/lib/commonjs/native/storage.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/module/AmplitudeContext.nitro.js +4 -0
- package/lib/module/AmplitudeContext.nitro.js.map +1 -0
- package/lib/module/AmplitudeStorage.nitro.js +4 -0
- package/lib/module/AmplitudeStorage.nitro.js.map +1 -0
- package/lib/module/AmplitudeWorker.nitro.js +4 -0
- package/lib/module/AmplitudeWorker.nitro.js.map +1 -0
- package/lib/module/analytics/campaign/campaign-tracker.js +100 -0
- package/lib/module/analytics/campaign/campaign-tracker.js.map +1 -0
- package/lib/module/analytics/campaign/types.js +4 -0
- package/lib/module/analytics/campaign/types.js.map +1 -0
- package/lib/module/analytics/config.js +272 -0
- package/lib/module/analytics/config.js.map +1 -0
- package/lib/module/analytics/cookie-migration/index.js +51 -0
- package/lib/module/analytics/cookie-migration/index.js.map +1 -0
- package/lib/module/analytics/index.js +34 -0
- package/lib/module/analytics/index.js.map +1 -0
- package/lib/module/analytics/migration/remnant-data-migration.js +172 -0
- package/lib/module/analytics/migration/remnant-data-migration.js.map +1 -0
- package/lib/module/analytics/nitro-transport.js +26 -0
- package/lib/module/analytics/nitro-transport.js.map +1 -0
- package/lib/module/analytics/plugins/context.js +134 -0
- package/lib/module/analytics/plugins/context.js.map +1 -0
- package/lib/module/analytics/react-native-client.js +346 -0
- package/lib/module/analytics/react-native-client.js.map +1 -0
- package/lib/module/analytics/storage/local-storage.js +68 -0
- package/lib/module/analytics/storage/local-storage.js.map +1 -0
- package/lib/module/analytics/types.js +4 -0
- package/lib/module/analytics/types.js.map +1 -0
- package/lib/module/analytics/typings/browser-snippet.d.js +4 -0
- package/lib/module/analytics/typings/browser-snippet.d.js.map +1 -0
- package/lib/module/analytics/typings/ua-parser.d.js +2 -0
- package/lib/module/analytics/typings/ua-parser.d.js.map +1 -0
- package/lib/module/analytics/utils/platform.js +10 -0
- package/lib/module/analytics/utils/platform.js.map +1 -0
- package/lib/module/analytics/version.js +4 -0
- package/lib/module/analytics/version.js.map +1 -0
- package/lib/module/experiment/experimentClient.js +788 -0
- package/lib/module/experiment/experimentClient.js.map +1 -0
- package/lib/module/experiment/factory.js +73 -0
- package/lib/module/experiment/factory.js.map +1 -0
- package/lib/module/experiment/gen/version.js +5 -0
- package/lib/module/experiment/gen/version.js.map +1 -0
- package/lib/module/experiment/index.js +16 -0
- package/lib/module/experiment/index.js.map +1 -0
- package/lib/module/experiment/integration/NativeExperimentReactNativeClient.js +7 -0
- package/lib/module/experiment/integration/NativeExperimentReactNativeClient.js.map +1 -0
- package/lib/module/experiment/integration/connector.js +61 -0
- package/lib/module/experiment/integration/connector.js.map +1 -0
- package/lib/module/experiment/integration/default.js +87 -0
- package/lib/module/experiment/integration/default.js.map +1 -0
- package/lib/module/experiment/logger/ampLogger.js +76 -0
- package/lib/module/experiment/logger/ampLogger.js.map +1 -0
- package/lib/module/experiment/logger/consoleLogger.js +55 -0
- package/lib/module/experiment/logger/consoleLogger.js.map +1 -0
- package/lib/module/experiment/storage/cache.js +187 -0
- package/lib/module/experiment/storage/cache.js.map +1 -0
- package/lib/module/experiment/storage/local-storage.js +19 -0
- package/lib/module/experiment/storage/local-storage.js.map +1 -0
- package/lib/module/experiment/stubClient.js +41 -0
- package/lib/module/experiment/stubClient.js.map +1 -0
- package/lib/module/experiment/transport/http.js +66 -0
- package/lib/module/experiment/transport/http.js.map +1 -0
- package/lib/module/experiment/types/client.js +4 -0
- package/lib/module/experiment/types/client.js.map +1 -0
- package/lib/module/experiment/types/config.js +64 -0
- package/lib/module/experiment/types/config.js.map +1 -0
- package/lib/module/experiment/types/exposure.js +2 -0
- package/lib/module/experiment/types/exposure.js.map +1 -0
- package/lib/module/experiment/types/logger.js +39 -0
- package/lib/module/experiment/types/logger.js.map +1 -0
- package/lib/module/experiment/types/source.js +51 -0
- package/lib/module/experiment/types/source.js.map +1 -0
- package/lib/module/experiment/types/storage.js +2 -0
- package/lib/module/experiment/types/storage.js.map +1 -0
- package/lib/module/experiment/types/transport.js +2 -0
- package/lib/module/experiment/types/transport.js.map +1 -0
- package/lib/module/experiment/types/user.js +2 -0
- package/lib/module/experiment/types/user.js.map +1 -0
- package/lib/module/experiment/types/variant.js +2 -0
- package/lib/module/experiment/types/variant.js.map +1 -0
- package/lib/module/experiment/util/backoff.js +49 -0
- package/lib/module/experiment/util/backoff.js.map +1 -0
- package/lib/module/experiment/util/base64.js +16 -0
- package/lib/module/experiment/util/base64.js.map +1 -0
- package/lib/module/experiment/util/convert.js +71 -0
- package/lib/module/experiment/util/convert.js.map +1 -0
- package/lib/module/experiment/util/index.js +17 -0
- package/lib/module/experiment/util/index.js.map +1 -0
- package/lib/module/experiment/util/platform.js +10 -0
- package/lib/module/experiment/util/platform.js.map +1 -0
- package/lib/module/experiment/util/randomstring.js +11 -0
- package/lib/module/experiment/util/randomstring.js.map +1 -0
- package/lib/module/experiment/util/userSessionExposureTracker.js +35 -0
- package/lib/module/experiment/util/userSessionExposureTracker.js.map +1 -0
- package/lib/module/index.js +50 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/index.web.js +14 -0
- package/lib/module/index.web.js.map +1 -0
- package/lib/module/native/context.js +35 -0
- package/lib/module/native/context.js.map +1 -0
- package/lib/module/native/http.js +57 -0
- package/lib/module/native/http.js.map +1 -0
- package/lib/module/native/hybrid.js +24 -0
- package/lib/module/native/hybrid.js.map +1 -0
- package/lib/module/native/storage.js +86 -0
- package/lib/module/native/storage.js.map +1 -0
- package/lib/typescript/AmplitudeContext.nitro.d.ts +17 -0
- package/lib/typescript/AmplitudeContext.nitro.d.ts.map +1 -0
- package/lib/typescript/AmplitudeStorage.nitro.d.ts +17 -0
- package/lib/typescript/AmplitudeStorage.nitro.d.ts.map +1 -0
- package/lib/typescript/AmplitudeWorker.nitro.d.ts +11 -0
- package/lib/typescript/AmplitudeWorker.nitro.d.ts.map +1 -0
- package/lib/typescript/analytics/campaign/campaign-tracker.d.ts +85 -0
- package/lib/typescript/analytics/campaign/campaign-tracker.d.ts.map +1 -0
- package/lib/typescript/analytics/campaign/types.d.ts +11 -0
- package/lib/typescript/analytics/campaign/types.d.ts.map +1 -0
- package/lib/typescript/analytics/config.d.ts +68 -0
- package/lib/typescript/analytics/config.d.ts.map +1 -0
- package/lib/typescript/analytics/cookie-migration/index.d.ts +6 -0
- package/lib/typescript/analytics/cookie-migration/index.d.ts.map +1 -0
- package/lib/typescript/analytics/index.d.ts +7 -0
- package/lib/typescript/analytics/index.d.ts.map +1 -0
- package/lib/typescript/analytics/migration/remnant-data-migration.d.ts +20 -0
- package/lib/typescript/analytics/migration/remnant-data-migration.d.ts.map +1 -0
- package/lib/typescript/analytics/nitro-transport.d.ts +9 -0
- package/lib/typescript/analytics/nitro-transport.d.ts.map +1 -0
- package/lib/typescript/analytics/plugins/context.d.ts +14 -0
- package/lib/typescript/analytics/plugins/context.d.ts.map +1 -0
- package/lib/typescript/analytics/react-native-client.d.ts +55 -0
- package/lib/typescript/analytics/react-native-client.d.ts.map +1 -0
- package/lib/typescript/analytics/storage/local-storage.d.ts +14 -0
- package/lib/typescript/analytics/storage/local-storage.d.ts.map +1 -0
- package/lib/typescript/analytics/types.d.ts +2 -0
- package/lib/typescript/analytics/types.d.ts.map +1 -0
- package/lib/typescript/analytics/utils/platform.d.ts +3 -0
- package/lib/typescript/analytics/utils/platform.d.ts.map +1 -0
- package/lib/typescript/analytics/version.d.ts +2 -0
- package/lib/typescript/analytics/version.d.ts.map +1 -0
- package/lib/typescript/experiment/experimentClient.d.ts +234 -0
- package/lib/typescript/experiment/experimentClient.d.ts.map +1 -0
- package/lib/typescript/experiment/factory.d.ts +11 -0
- package/lib/typescript/experiment/factory.d.ts.map +1 -0
- package/lib/typescript/experiment/gen/version.d.ts +2 -0
- package/lib/typescript/experiment/gen/version.d.ts.map +1 -0
- package/lib/typescript/experiment/index.d.ts +15 -0
- package/lib/typescript/experiment/index.d.ts.map +1 -0
- package/lib/typescript/experiment/integration/NativeExperimentReactNativeClient.d.ts +16 -0
- package/lib/typescript/experiment/integration/NativeExperimentReactNativeClient.d.ts.map +1 -0
- package/lib/typescript/experiment/integration/connector.d.ts +16 -0
- package/lib/typescript/experiment/integration/connector.d.ts.map +1 -0
- package/lib/typescript/experiment/integration/default.d.ts +20 -0
- package/lib/typescript/experiment/integration/default.d.ts.map +1 -0
- package/lib/typescript/experiment/logger/ampLogger.d.ts +47 -0
- package/lib/typescript/experiment/logger/ampLogger.d.ts.map +1 -0
- package/lib/typescript/experiment/logger/consoleLogger.d.ts +40 -0
- package/lib/typescript/experiment/logger/consoleLogger.d.ts.map +1 -0
- package/lib/typescript/experiment/storage/cache.d.ts +35 -0
- package/lib/typescript/experiment/storage/cache.d.ts.map +1 -0
- package/lib/typescript/experiment/storage/local-storage.d.ts +11 -0
- package/lib/typescript/experiment/storage/local-storage.d.ts.map +1 -0
- package/lib/typescript/experiment/stubClient.d.ts +21 -0
- package/lib/typescript/experiment/stubClient.d.ts.map +1 -0
- package/lib/typescript/experiment/transport/http.d.ts +17 -0
- package/lib/typescript/experiment/transport/http.d.ts.map +1 -0
- package/lib/typescript/experiment/types/client.d.ts +36 -0
- package/lib/typescript/experiment/types/client.d.ts.map +1 -0
- package/lib/typescript/experiment/types/config.d.ts +164 -0
- package/lib/typescript/experiment/types/config.d.ts.map +1 -0
- package/lib/typescript/experiment/types/exposure.d.ts +107 -0
- package/lib/typescript/experiment/types/exposure.d.ts.map +1 -0
- package/lib/typescript/experiment/types/logger.d.ts +67 -0
- package/lib/typescript/experiment/types/logger.d.ts.map +1 -0
- package/lib/typescript/experiment/types/source.d.ts +43 -0
- package/lib/typescript/experiment/types/source.d.ts.map +1 -0
- package/lib/typescript/experiment/types/storage.d.ts +7 -0
- package/lib/typescript/experiment/types/storage.d.ts.map +1 -0
- package/lib/typescript/experiment/types/transport.d.ts +8 -0
- package/lib/typescript/experiment/types/transport.d.ts.map +1 -0
- package/lib/typescript/experiment/types/user.d.ts +106 -0
- package/lib/typescript/experiment/types/user.d.ts.map +1 -0
- package/lib/typescript/experiment/types/variant.d.ts +33 -0
- package/lib/typescript/experiment/types/variant.d.ts.map +1 -0
- package/lib/typescript/experiment/util/backoff.d.ts +14 -0
- package/lib/typescript/experiment/util/backoff.d.ts.map +1 -0
- package/lib/typescript/experiment/util/base64.d.ts +3 -0
- package/lib/typescript/experiment/util/base64.d.ts.map +1 -0
- package/lib/typescript/experiment/util/convert.d.ts +7 -0
- package/lib/typescript/experiment/util/convert.d.ts.map +1 -0
- package/lib/typescript/experiment/util/index.d.ts +6 -0
- package/lib/typescript/experiment/util/index.d.ts.map +1 -0
- package/lib/typescript/experiment/util/platform.d.ts +3 -0
- package/lib/typescript/experiment/util/platform.d.ts.map +1 -0
- package/lib/typescript/experiment/util/randomstring.d.ts +2 -0
- package/lib/typescript/experiment/util/randomstring.d.ts.map +1 -0
- package/lib/typescript/experiment/util/userSessionExposureTracker.d.ts +16 -0
- package/lib/typescript/experiment/util/userSessionExposureTracker.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +31 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/index.web.d.ts +12 -0
- package/lib/typescript/index.web.d.ts.map +1 -0
- package/lib/typescript/native/context.d.ts +29 -0
- package/lib/typescript/native/context.d.ts.map +1 -0
- package/lib/typescript/native/http.d.ts +6 -0
- package/lib/typescript/native/http.d.ts.map +1 -0
- package/lib/typescript/native/hybrid.d.ts +8 -0
- package/lib/typescript/native/hybrid.d.ts.map +1 -0
- package/lib/typescript/native/storage.d.ts +29 -0
- package/lib/typescript/native/storage.d.ts.map +1 -0
- package/nitro.json +33 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/NitroAmplitude+autolinking.cmake +83 -0
- package/nitrogen/generated/android/NitroAmplitude+autolinking.gradle +27 -0
- package/nitrogen/generated/android/NitroAmplitudeOnLoad.cpp +69 -0
- package/nitrogen/generated/android/NitroAmplitudeOnLoad.hpp +34 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/nitroamplitude/NitroAmplitudeOnLoad.kt +35 -0
- package/nitrogen/generated/ios/NitroAmplitude+autolinking.rb +62 -0
- package/nitrogen/generated/ios/NitroAmplitude-Swift-Cxx-Bridge.cpp +17 -0
- package/nitrogen/generated/ios/NitroAmplitude-Swift-Cxx-Bridge.hpp +27 -0
- package/nitrogen/generated/ios/NitroAmplitude-Swift-Cxx-Umbrella.hpp +38 -0
- package/nitrogen/generated/ios/NitroAmplitudeAutolinking.mm +55 -0
- package/nitrogen/generated/ios/NitroAmplitudeAutolinking.swift +16 -0
- package/nitrogen/generated/shared/c++/HybridAmplitudeContextSpec.cpp +25 -0
- package/nitrogen/generated/shared/c++/HybridAmplitudeContextSpec.hpp +67 -0
- package/nitrogen/generated/shared/c++/HybridAmplitudeStorageSpec.cpp +30 -0
- package/nitrogen/generated/shared/c++/HybridAmplitudeStorageSpec.hpp +73 -0
- package/nitrogen/generated/shared/c++/HybridAmplitudeWorkerSpec.cpp +24 -0
- package/nitrogen/generated/shared/c++/HybridAmplitudeWorkerSpec.hpp +66 -0
- package/package.json +130 -0
- package/react-native-nitro-amplitude.podspec +36 -0
- package/src/AmplitudeContext.nitro.ts +21 -0
- package/src/AmplitudeStorage.nitro.ts +17 -0
- package/src/AmplitudeWorker.nitro.ts +25 -0
- package/src/analytics/campaign/campaign-tracker.ts +162 -0
- package/src/analytics/campaign/types.ts +18 -0
- package/src/analytics/config.ts +373 -0
- package/src/analytics/cookie-migration/index.ts +69 -0
- package/src/analytics/index.ts +36 -0
- package/src/analytics/migration/remnant-data-migration.ts +206 -0
- package/src/analytics/nitro-transport.ts +35 -0
- package/src/analytics/plugins/context.ts +166 -0
- package/src/analytics/react-native-client.ts +573 -0
- package/src/analytics/storage/local-storage.ts +76 -0
- package/src/analytics/types.ts +30 -0
- package/src/analytics/typings/browser-snippet.d.ts +7 -0
- package/src/analytics/typings/ua-parser.d.ts +4 -0
- package/src/analytics/utils/platform.ts +9 -0
- package/src/analytics/version.ts +1 -0
- package/src/experiment/experimentClient.ts +987 -0
- package/src/experiment/factory.ts +86 -0
- package/src/experiment/gen/version.ts +2 -0
- package/src/experiment/index.ts +14 -0
- package/src/experiment/integration/NativeExperimentReactNativeClient.ts +25 -0
- package/src/experiment/integration/connector.ts +82 -0
- package/src/experiment/integration/default.ts +107 -0
- package/src/experiment/logger/ampLogger.ts +76 -0
- package/src/experiment/logger/consoleLogger.ts +54 -0
- package/src/experiment/storage/cache.ts +271 -0
- package/src/experiment/storage/local-storage.ts +23 -0
- package/src/experiment/stubClient.ts +61 -0
- package/src/experiment/transport/http.ts +105 -0
- package/src/experiment/types/client.ts +38 -0
- package/src/experiment/types/config.ts +210 -0
- package/src/experiment/types/exposure.ts +107 -0
- package/src/experiment/types/logger.ts +71 -0
- package/src/experiment/types/source.ts +52 -0
- package/src/experiment/types/storage.ts +6 -0
- package/src/experiment/types/transport.ts +14 -0
- package/src/experiment/types/user.ts +132 -0
- package/src/experiment/types/variant.ts +36 -0
- package/src/experiment/util/backoff.ts +67 -0
- package/src/experiment/util/base64.ts +20 -0
- package/src/experiment/util/convert.ts +78 -0
- package/src/experiment/util/index.ts +23 -0
- package/src/experiment/util/platform.ts +9 -0
- package/src/experiment/util/randomstring.ts +12 -0
- package/src/experiment/util/userSessionExposureTracker.ts +47 -0
- package/src/index.ts +66 -0
- package/src/index.web.ts +19 -0
- package/src/native/context.ts +73 -0
- package/src/native/http.ts +77 -0
- package/src/native/hybrid.ts +32 -0
- package/src/native/storage.ts +107 -0
|
@@ -0,0 +1,987 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* @module experiment-react-native-client
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
topologicalSort,
|
|
8
|
+
EvaluationApi,
|
|
9
|
+
EvaluationEngine,
|
|
10
|
+
EvaluationFlag,
|
|
11
|
+
FetchError,
|
|
12
|
+
FlagApi,
|
|
13
|
+
Poller,
|
|
14
|
+
SdkFlagApi,
|
|
15
|
+
SdkEvaluationApi,
|
|
16
|
+
GetVariantsOptions,
|
|
17
|
+
} from "@amplitude/experiment-core";
|
|
18
|
+
|
|
19
|
+
import { version as PACKAGE_VERSION } from "./gen/version";
|
|
20
|
+
import { ConnectorUserProvider } from "./integration/connector";
|
|
21
|
+
import { DefaultUserProvider } from "./integration/default";
|
|
22
|
+
import { AmpLogger } from "./logger/ampLogger";
|
|
23
|
+
import { ConsoleLogger } from "./logger/consoleLogger";
|
|
24
|
+
import {
|
|
25
|
+
getFlagStorage,
|
|
26
|
+
getVariantsOptionsStorage,
|
|
27
|
+
getVariantStorage,
|
|
28
|
+
LoadStoreCache,
|
|
29
|
+
SingleValueStoreCache,
|
|
30
|
+
} from "./storage/cache";
|
|
31
|
+
import { MemoryStorage } from "./storage/local-storage";
|
|
32
|
+
import { FetchHttpClient, WrapperClient } from "./transport/http";
|
|
33
|
+
import { Client, FetchOptions } from "./types/client";
|
|
34
|
+
import { ExperimentConfig, Defaults } from "./types/config";
|
|
35
|
+
import { Exposure } from "./types/exposure";
|
|
36
|
+
import { LogLevel } from "./types/logger";
|
|
37
|
+
import { isFallback, Source, VariantSource } from "./types/source";
|
|
38
|
+
import { ExperimentUser, ExperimentUserProvider } from "./types/user";
|
|
39
|
+
import { Variant, Variants } from "./types/variant";
|
|
40
|
+
import {
|
|
41
|
+
isLocalEvaluationMode,
|
|
42
|
+
isNullOrUndefined,
|
|
43
|
+
isNullUndefinedOrEmpty,
|
|
44
|
+
} from "./util";
|
|
45
|
+
import { Backoff } from "./util/backoff";
|
|
46
|
+
import {
|
|
47
|
+
convertEvaluationVariantToVariant,
|
|
48
|
+
convertUserToContext,
|
|
49
|
+
convertVariant,
|
|
50
|
+
} from "./util/convert";
|
|
51
|
+
import { UserSessionExposureTracker } from "./util/userSessionExposureTracker";
|
|
52
|
+
|
|
53
|
+
// Configs which have been removed from the public API.
|
|
54
|
+
// May be added back in the future.
|
|
55
|
+
const fetchBackoffTimeout = 10000;
|
|
56
|
+
const fetchBackoffAttempts = 8;
|
|
57
|
+
const fetchBackoffMinMillis = 500;
|
|
58
|
+
const fetchBackoffMaxMillis = 10000;
|
|
59
|
+
const fetchBackoffScalar = 1.5;
|
|
60
|
+
const flagPollerIntervalMillis = 60000;
|
|
61
|
+
|
|
62
|
+
const euServerUrl = "https://api.lab.eu.amplitude.com";
|
|
63
|
+
const euFlagsServerUrl = "https://flag.lab.eu.amplitude.com";
|
|
64
|
+
|
|
65
|
+
type ResolvedExperimentConfig = ExperimentConfig & {
|
|
66
|
+
debug: boolean;
|
|
67
|
+
logLevel: LogLevel;
|
|
68
|
+
loggerProvider: NonNullable<ExperimentConfig["loggerProvider"]> | null;
|
|
69
|
+
instanceName: string;
|
|
70
|
+
fallbackVariant: Variant;
|
|
71
|
+
initialVariants: Variants;
|
|
72
|
+
source: Source;
|
|
73
|
+
serverUrl: string;
|
|
74
|
+
flagsServerUrl: string;
|
|
75
|
+
serverZone: "US" | "EU";
|
|
76
|
+
fetchTimeoutMillis: number;
|
|
77
|
+
retryFetchOnFailure: boolean;
|
|
78
|
+
automaticExposureTracking: boolean;
|
|
79
|
+
pollOnStart: boolean;
|
|
80
|
+
fetchOnStart: boolean;
|
|
81
|
+
automaticFetchOnAmplitudeIdentityChange: boolean;
|
|
82
|
+
userProvider: ExperimentUserProvider | null;
|
|
83
|
+
exposureTrackingProvider: NonNullable<
|
|
84
|
+
ExperimentConfig["exposureTrackingProvider"]
|
|
85
|
+
> | null;
|
|
86
|
+
httpClient: NonNullable<ExperimentConfig["httpClient"]>;
|
|
87
|
+
storage: NonNullable<ExperimentConfig["storage"]> | null;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The default {@link Client} used to fetch variations from Experiment's
|
|
92
|
+
* servers.
|
|
93
|
+
*
|
|
94
|
+
* @category Core Usage
|
|
95
|
+
*/
|
|
96
|
+
export class ExperimentClient implements Client {
|
|
97
|
+
private readonly apiKey: string;
|
|
98
|
+
private readonly config: ResolvedExperimentConfig;
|
|
99
|
+
private readonly logger: AmpLogger;
|
|
100
|
+
private readonly variants: LoadStoreCache<Variant>;
|
|
101
|
+
private readonly flags: LoadStoreCache<EvaluationFlag>;
|
|
102
|
+
private readonly flagApi: FlagApi;
|
|
103
|
+
private readonly evaluationApi: EvaluationApi;
|
|
104
|
+
private readonly engine: EvaluationEngine = new EvaluationEngine();
|
|
105
|
+
private user: ExperimentUser = {};
|
|
106
|
+
private readonly defaultUserProvider: DefaultUserProvider;
|
|
107
|
+
private readonly userSessionExposureTracker:
|
|
108
|
+
| UserSessionExposureTracker
|
|
109
|
+
| undefined;
|
|
110
|
+
private retriesBackoff: Backoff | undefined;
|
|
111
|
+
private readonly poller: Poller = new Poller(
|
|
112
|
+
() => this.pollFlags(),
|
|
113
|
+
flagPollerIntervalMillis,
|
|
114
|
+
);
|
|
115
|
+
private isRunning = false;
|
|
116
|
+
private readonly flagsAndVariantsLoadedPromise: Promise<void>[];
|
|
117
|
+
private readonly initialFlags: EvaluationFlag[] | undefined;
|
|
118
|
+
private fetchSequenceNumber = 0;
|
|
119
|
+
private storedFetchSequenceNumber = 0;
|
|
120
|
+
private readonly fetchVariantsOptions: SingleValueStoreCache<GetVariantsOptions>;
|
|
121
|
+
private readonly stopCallbacks = new Set<() => void>();
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Creates a new ExperimentClient instance.
|
|
125
|
+
*
|
|
126
|
+
* In most cases you will want to use the `initialize` factory method in
|
|
127
|
+
* {@link Experiment}.
|
|
128
|
+
*
|
|
129
|
+
* @param apiKey The Client key for the Experiment project
|
|
130
|
+
* @param config See {@link ExperimentConfig} for config options
|
|
131
|
+
*/
|
|
132
|
+
public constructor(apiKey: string, config: ExperimentConfig) {
|
|
133
|
+
this.apiKey = apiKey;
|
|
134
|
+
const serverZone = config?.serverZone ?? Defaults.serverZone ?? "US";
|
|
135
|
+
this.config = {
|
|
136
|
+
...Defaults,
|
|
137
|
+
...config,
|
|
138
|
+
debug: config?.debug ?? Defaults.debug ?? false,
|
|
139
|
+
logLevel: config?.logLevel ?? Defaults.logLevel ?? LogLevel.Error,
|
|
140
|
+
loggerProvider: config?.loggerProvider ?? Defaults.loggerProvider ?? null,
|
|
141
|
+
instanceName:
|
|
142
|
+
config?.instanceName ?? Defaults.instanceName ?? "$default_instance",
|
|
143
|
+
fallbackVariant:
|
|
144
|
+
config?.fallbackVariant ?? Defaults.fallbackVariant ?? {},
|
|
145
|
+
initialVariants:
|
|
146
|
+
config?.initialVariants ?? Defaults.initialVariants ?? {},
|
|
147
|
+
source: config?.source ?? Defaults.source ?? Source.LocalStorage,
|
|
148
|
+
serverZone,
|
|
149
|
+
serverUrl:
|
|
150
|
+
config?.serverUrl ??
|
|
151
|
+
(serverZone === "EU"
|
|
152
|
+
? euServerUrl
|
|
153
|
+
: (Defaults.serverUrl ?? "https://api.lab.amplitude.com")),
|
|
154
|
+
flagsServerUrl:
|
|
155
|
+
config?.flagsServerUrl ??
|
|
156
|
+
(serverZone === "EU"
|
|
157
|
+
? euFlagsServerUrl
|
|
158
|
+
: (Defaults.flagsServerUrl ?? "https://flag.lab.amplitude.com")),
|
|
159
|
+
fetchTimeoutMillis:
|
|
160
|
+
config?.fetchTimeoutMillis ?? Defaults.fetchTimeoutMillis ?? 10000,
|
|
161
|
+
retryFetchOnFailure:
|
|
162
|
+
config?.retryFetchOnFailure ?? Defaults.retryFetchOnFailure ?? true,
|
|
163
|
+
automaticExposureTracking:
|
|
164
|
+
config?.automaticExposureTracking ??
|
|
165
|
+
Defaults.automaticExposureTracking ??
|
|
166
|
+
true,
|
|
167
|
+
pollOnStart: config?.pollOnStart ?? Defaults.pollOnStart ?? true,
|
|
168
|
+
fetchOnStart: config?.fetchOnStart ?? Defaults.fetchOnStart ?? true,
|
|
169
|
+
automaticFetchOnAmplitudeIdentityChange:
|
|
170
|
+
config?.automaticFetchOnAmplitudeIdentityChange ??
|
|
171
|
+
Defaults.automaticFetchOnAmplitudeIdentityChange ??
|
|
172
|
+
false,
|
|
173
|
+
userProvider: config?.userProvider ?? Defaults.userProvider ?? null,
|
|
174
|
+
exposureTrackingProvider:
|
|
175
|
+
config?.exposureTrackingProvider ??
|
|
176
|
+
Defaults.exposureTrackingProvider ??
|
|
177
|
+
null,
|
|
178
|
+
httpClient: config?.httpClient ?? Defaults.httpClient ?? FetchHttpClient,
|
|
179
|
+
storage: config?.storage ?? Defaults.storage ?? null,
|
|
180
|
+
};
|
|
181
|
+
this.logger = new AmpLogger(
|
|
182
|
+
this.config.loggerProvider || new ConsoleLogger(),
|
|
183
|
+
ExperimentClient.getLogLevel(this.config),
|
|
184
|
+
);
|
|
185
|
+
this.defaultUserProvider = new DefaultUserProvider(
|
|
186
|
+
this.config.userProvider,
|
|
187
|
+
);
|
|
188
|
+
if (this.config.exposureTrackingProvider) {
|
|
189
|
+
this.userSessionExposureTracker = new UserSessionExposureTracker(
|
|
190
|
+
this.config.exposureTrackingProvider,
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
// Setup Remote APIs
|
|
194
|
+
const httpClient = new WrapperClient(
|
|
195
|
+
this.config.httpClient || FetchHttpClient,
|
|
196
|
+
);
|
|
197
|
+
this.flagApi = new SdkFlagApi(
|
|
198
|
+
this.apiKey,
|
|
199
|
+
this.config.flagsServerUrl,
|
|
200
|
+
httpClient,
|
|
201
|
+
);
|
|
202
|
+
this.evaluationApi = new SdkEvaluationApi(
|
|
203
|
+
this.apiKey,
|
|
204
|
+
this.config.serverUrl,
|
|
205
|
+
httpClient,
|
|
206
|
+
);
|
|
207
|
+
// Storage & Caching
|
|
208
|
+
const storage = this.config.storage || new MemoryStorage();
|
|
209
|
+
this.variants = getVariantStorage(
|
|
210
|
+
this.apiKey,
|
|
211
|
+
this.config.instanceName,
|
|
212
|
+
storage,
|
|
213
|
+
);
|
|
214
|
+
this.flags = getFlagStorage(this.apiKey, this.config.instanceName, storage);
|
|
215
|
+
if (this.config.initialFlags) {
|
|
216
|
+
try {
|
|
217
|
+
this.initialFlags = JSON.parse(this.config.initialFlags);
|
|
218
|
+
} catch (error) {
|
|
219
|
+
this.logger.warn(error);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
this.fetchVariantsOptions = getVariantsOptionsStorage(
|
|
223
|
+
this.apiKey,
|
|
224
|
+
this.config.instanceName,
|
|
225
|
+
storage,
|
|
226
|
+
);
|
|
227
|
+
this.flagsAndVariantsLoadedPromise = [
|
|
228
|
+
this.flags.load(this.convertInitialFlagsForStorage()),
|
|
229
|
+
this.variants.load(),
|
|
230
|
+
this.fetchVariantsOptions.load(),
|
|
231
|
+
];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Call to ensure the completion of the loading variants and flags from localStorage upon initialization.
|
|
236
|
+
*/
|
|
237
|
+
public async cacheReady(): Promise<ExperimentClient> {
|
|
238
|
+
await Promise.all(this.flagsAndVariantsLoadedPromise);
|
|
239
|
+
return this;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Start the SDK by getting flag configurations from the server and fetching
|
|
244
|
+
* variants for the user. The promise returned by this function resolves when
|
|
245
|
+
* local flag configurations have been updated, and the {@link fetch()}
|
|
246
|
+
* result has been received (if the request was made).
|
|
247
|
+
*
|
|
248
|
+
* To force this function not to fetch variants, set the {@link fetchOnStart}
|
|
249
|
+
* configuration option to `false` when initializing the SDK.
|
|
250
|
+
*
|
|
251
|
+
* Finally, this function will start polling for flag configurations at a
|
|
252
|
+
* fixed interval. To disable polling, set the {@link pollOnStart}
|
|
253
|
+
* configuration option to `false` on initialization.
|
|
254
|
+
*
|
|
255
|
+
* @param user The user to set in the SDK.
|
|
256
|
+
* @see fetchOnStart
|
|
257
|
+
* @see pollOnStart
|
|
258
|
+
* @see fetch
|
|
259
|
+
* @see variant
|
|
260
|
+
*/
|
|
261
|
+
public async start(user?: ExperimentUser): Promise<void> {
|
|
262
|
+
if (this.isRunning) {
|
|
263
|
+
return;
|
|
264
|
+
} else {
|
|
265
|
+
this.isRunning = true;
|
|
266
|
+
}
|
|
267
|
+
try {
|
|
268
|
+
this.defaultUserProvider.start();
|
|
269
|
+
this.setUser(user ?? {});
|
|
270
|
+
const flagsReadyPromise = this.doFlags();
|
|
271
|
+
const fetchOnStart = this.config.fetchOnStart ?? true;
|
|
272
|
+
if (fetchOnStart) {
|
|
273
|
+
await Promise.all([this.fetch(user), flagsReadyPromise]);
|
|
274
|
+
} else {
|
|
275
|
+
await flagsReadyPromise;
|
|
276
|
+
}
|
|
277
|
+
if (this.config.pollOnStart) {
|
|
278
|
+
this.poller.start();
|
|
279
|
+
}
|
|
280
|
+
} catch (e) {
|
|
281
|
+
this.stop();
|
|
282
|
+
throw e;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Stop background polling and retry work started by the client.
|
|
288
|
+
*/
|
|
289
|
+
public stop(): void {
|
|
290
|
+
this.stopRetries();
|
|
291
|
+
this.defaultUserProvider.stop();
|
|
292
|
+
for (const callback of this.stopCallbacks) {
|
|
293
|
+
try {
|
|
294
|
+
callback();
|
|
295
|
+
} catch (e) {
|
|
296
|
+
this.logger.warn(e);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
this.stopCallbacks.clear();
|
|
300
|
+
if (!this.isRunning) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
this.poller.stop();
|
|
304
|
+
this.isRunning = false;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Assign the given user to the SDK and asynchronously fetch all variants
|
|
309
|
+
* from the server. Subsequent calls may omit the user from the argument to
|
|
310
|
+
* use the user from the previous call.
|
|
311
|
+
*
|
|
312
|
+
* If an {@link ExperimentUserProvider} has been set, the argument user will
|
|
313
|
+
* be merged with the provider user, preferring user fields from the argument
|
|
314
|
+
* user and falling back on the provider for fields which are null or
|
|
315
|
+
* undefined.
|
|
316
|
+
*
|
|
317
|
+
* If configured, fetch retries the request in the background on failure.
|
|
318
|
+
* Variants received from a successful retry are stored in local storage for
|
|
319
|
+
* access.
|
|
320
|
+
*
|
|
321
|
+
* If you are using the `initialVariants` config option to preload this SDK
|
|
322
|
+
* from the server, you generally do not need to call `fetch`.
|
|
323
|
+
*
|
|
324
|
+
* @param user The user to fetch variants for.
|
|
325
|
+
* @param options Options for this specific fetch call.
|
|
326
|
+
* @returns Promise that resolves when the request for variants completes.
|
|
327
|
+
* @see ExperimentUser
|
|
328
|
+
* @see ExperimentUserProvider
|
|
329
|
+
*/
|
|
330
|
+
public async fetch(
|
|
331
|
+
user: ExperimentUser = this.user,
|
|
332
|
+
options?: FetchOptions,
|
|
333
|
+
): Promise<ExperimentClient> {
|
|
334
|
+
const fetchUser = user ?? this.user;
|
|
335
|
+
this.setUser(fetchUser);
|
|
336
|
+
try {
|
|
337
|
+
await this.fetchWithRetries(fetchUser, options);
|
|
338
|
+
} catch (e) {
|
|
339
|
+
this.logger.warn(e);
|
|
340
|
+
}
|
|
341
|
+
return this;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
public async fetchOrThrow(
|
|
345
|
+
user: ExperimentUser = this.user,
|
|
346
|
+
options?: FetchOptions,
|
|
347
|
+
): Promise<ExperimentClient> {
|
|
348
|
+
const fetchUser = user ?? this.user;
|
|
349
|
+
this.setUser(fetchUser);
|
|
350
|
+
await this.fetchWithRetries(fetchUser, options);
|
|
351
|
+
return this;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Returns the variant for the provided key.
|
|
356
|
+
*
|
|
357
|
+
* Access the variant from {@link Source}, falling back on the given
|
|
358
|
+
* fallback, then the configured fallbackVariant.
|
|
359
|
+
*
|
|
360
|
+
* If an {@link ExposureTrackingProvider} is configured and trackExposure is
|
|
361
|
+
* true, this function will call the provider with an {@link Exposure}.
|
|
362
|
+
* The exposure event does not count towards your event volume within Amplitude.
|
|
363
|
+
*
|
|
364
|
+
* @param key The key to get the variant for.
|
|
365
|
+
* @param fallback The highest priority fallback.
|
|
366
|
+
* @see ExperimentConfig
|
|
367
|
+
* @see ExposureTrackingProvider
|
|
368
|
+
*/
|
|
369
|
+
public variant(key: string, fallback?: string | Variant): Variant {
|
|
370
|
+
if (!this.apiKey) {
|
|
371
|
+
return { value: undefined };
|
|
372
|
+
}
|
|
373
|
+
const sourceVariant = this.variantAndSource(key, fallback);
|
|
374
|
+
if (this.config.automaticExposureTracking) {
|
|
375
|
+
this.exposureInternal(key, sourceVariant);
|
|
376
|
+
}
|
|
377
|
+
this.logger.debug(
|
|
378
|
+
`[Experiment] variant for ${key} is ${sourceVariant.variant?.value}`,
|
|
379
|
+
);
|
|
380
|
+
return sourceVariant.variant || {};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Track an exposure event for the variant associated with the flag/experiment
|
|
385
|
+
* {@link key}.
|
|
386
|
+
*
|
|
387
|
+
* This method requires that an {@link ExposureTrackingProvider} be
|
|
388
|
+
* configured when this client is initialized, either manually, or through the
|
|
389
|
+
* Amplitude Analytics SDK integration from set up using
|
|
390
|
+
* {@link Experiment.initializeWithAmplitudeAnalytics}.
|
|
391
|
+
*
|
|
392
|
+
* @param key The flag/experiment key to track an exposure for.
|
|
393
|
+
*/
|
|
394
|
+
public exposure(key: string): void {
|
|
395
|
+
const sourceVariant = this.variantAndSource(key);
|
|
396
|
+
this.exposureInternal(key, sourceVariant);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Returns all variants for the user.
|
|
401
|
+
*
|
|
402
|
+
* The primary source of variants is based on the
|
|
403
|
+
* {@link Source} configured in the {@link ExperimentConfig}.
|
|
404
|
+
*
|
|
405
|
+
* @see Source
|
|
406
|
+
* @see ExperimentConfig
|
|
407
|
+
*/
|
|
408
|
+
public all(): Variants {
|
|
409
|
+
if (!this.apiKey) {
|
|
410
|
+
return {};
|
|
411
|
+
}
|
|
412
|
+
const evaluatedVariants = this.evaluate();
|
|
413
|
+
for (const flagKey in evaluatedVariants) {
|
|
414
|
+
const flag = this.flags.get(flagKey);
|
|
415
|
+
if (!isLocalEvaluationMode(flag)) {
|
|
416
|
+
delete evaluatedVariants[flagKey];
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return {
|
|
420
|
+
...this.secondaryVariants(),
|
|
421
|
+
...this.sourceVariants(),
|
|
422
|
+
...evaluatedVariants,
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Clear all variants in the cache and storage.
|
|
428
|
+
*/
|
|
429
|
+
public clear(): void {
|
|
430
|
+
this.variants.clear();
|
|
431
|
+
void this.variants.store().catch((e) => this.logger.warn(e));
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Get a copy of the internal {@link ExperimentUser} object if it is set.
|
|
436
|
+
*
|
|
437
|
+
* @returns a copy of the internal user object if set.
|
|
438
|
+
*/
|
|
439
|
+
public getUser(): ExperimentUser {
|
|
440
|
+
if (!this.user) {
|
|
441
|
+
return {};
|
|
442
|
+
}
|
|
443
|
+
if (this.user?.user_properties) {
|
|
444
|
+
const userPropertiesCopy = { ...this.user.user_properties };
|
|
445
|
+
return { ...this.user, user_properties: userPropertiesCopy };
|
|
446
|
+
} else {
|
|
447
|
+
return { ...this.user };
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Copy in and set the user within the experiment client.
|
|
453
|
+
*
|
|
454
|
+
* @param user the user to set within the experiment client.
|
|
455
|
+
*/
|
|
456
|
+
public setUser(user: ExperimentUser): void {
|
|
457
|
+
if (!user) {
|
|
458
|
+
this.user = {};
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
if (this.user?.user_properties) {
|
|
462
|
+
const userPropertiesCopy = { ...user.user_properties };
|
|
463
|
+
this.user = { ...user, user_properties: userPropertiesCopy };
|
|
464
|
+
} else {
|
|
465
|
+
this.user = { ...user };
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Get the user provider set by {@link setUserProvider} or null if the user
|
|
471
|
+
* provider has not been set.
|
|
472
|
+
*
|
|
473
|
+
* @returns The user provider set by {@link setUserProvider} or null.
|
|
474
|
+
* @deprecated use ExperimentConfig.userProvider instead
|
|
475
|
+
*/
|
|
476
|
+
public getUserProvider(): ExperimentUserProvider {
|
|
477
|
+
return this.defaultUserProvider;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Sets a user provider that will inject identity information into the user
|
|
482
|
+
* for {@link fetch()} requests. The user provider will only set user fields
|
|
483
|
+
* in outgoing requests which are null or undefined.
|
|
484
|
+
*
|
|
485
|
+
* See {@link ExperimentUserProvider} for more details
|
|
486
|
+
* @param userProvider
|
|
487
|
+
* @deprecated use ExperimentConfig.userProvider instead
|
|
488
|
+
*/
|
|
489
|
+
public setUserProvider(userProvider: ExperimentUserProvider): Client {
|
|
490
|
+
this.defaultUserProvider.baseProvider = userProvider;
|
|
491
|
+
return this;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Enables or disables tracking of assignment events when fetching variants.
|
|
496
|
+
* @param doTrack Whether to track assignment events.
|
|
497
|
+
*/
|
|
498
|
+
public async setTracksAssignment(doTrack: boolean): Promise<void> {
|
|
499
|
+
this.fetchVariantsOptions.put({
|
|
500
|
+
...this.fetchVariantsOptions.get(),
|
|
501
|
+
trackingOption: doTrack ? "track" : "no-track",
|
|
502
|
+
});
|
|
503
|
+
await this.fetchVariantsOptions.store();
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
public addStopCallback(callback: () => void): void {
|
|
507
|
+
this.stopCallbacks.add(callback);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
public fetchOnIdentityChange(): void {
|
|
511
|
+
void this.fetch().catch((e) => this.logger.warn(e));
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
private convertInitialFlagsForStorage(): Record<string, EvaluationFlag> {
|
|
515
|
+
if (this.initialFlags) {
|
|
516
|
+
const flagsMap: Record<string, EvaluationFlag> = {};
|
|
517
|
+
this.initialFlags.forEach((flag: EvaluationFlag) => {
|
|
518
|
+
flagsMap[flag.key] = flag;
|
|
519
|
+
});
|
|
520
|
+
return flagsMap;
|
|
521
|
+
}
|
|
522
|
+
return {};
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
private mergeInitialFlagsWithStorage(): void {
|
|
526
|
+
if (this.initialFlags) {
|
|
527
|
+
this.initialFlags.forEach((flag: EvaluationFlag) => {
|
|
528
|
+
if (!this.flags.get(flag.key)) {
|
|
529
|
+
this.flags.put(flag.key, flag);
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
private evaluate(flagKeys?: string[]): Variants {
|
|
536
|
+
const user = this.addContextSync(this.user);
|
|
537
|
+
const flags = topologicalSort(this.flags.getAll(), flagKeys);
|
|
538
|
+
const context = convertUserToContext(user);
|
|
539
|
+
const evaluationVariants = this.engine.evaluate(context, flags);
|
|
540
|
+
const variants: Variants = {};
|
|
541
|
+
for (const flagKey of Object.keys(evaluationVariants)) {
|
|
542
|
+
variants[flagKey] = convertEvaluationVariantToVariant(
|
|
543
|
+
evaluationVariants[flagKey],
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
return variants;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
private variantAndSource(
|
|
550
|
+
key: string,
|
|
551
|
+
fallback?: string | Variant,
|
|
552
|
+
): SourceVariant {
|
|
553
|
+
let sourceVariant: SourceVariant = {};
|
|
554
|
+
if (this.config.source === Source.LocalStorage) {
|
|
555
|
+
sourceVariant = this.localStorageVariantAndSource(key, fallback);
|
|
556
|
+
} else if (this.config.source === Source.InitialVariants) {
|
|
557
|
+
sourceVariant = this.initialVariantsVariantAndSource(key, fallback);
|
|
558
|
+
}
|
|
559
|
+
const flag = this.flags.get(key);
|
|
560
|
+
if (flag && (isLocalEvaluationMode(flag) || !sourceVariant.variant)) {
|
|
561
|
+
sourceVariant = this.localEvaluationVariantAndSource(key, flag, fallback);
|
|
562
|
+
}
|
|
563
|
+
return sourceVariant;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* This function assumes the flag exists and is local evaluation mode. For
|
|
568
|
+
* local evaluation, fallback order goes:
|
|
569
|
+
*
|
|
570
|
+
* 1. Local evaluation
|
|
571
|
+
* 2. Inline function fallback
|
|
572
|
+
* 3. Initial variants
|
|
573
|
+
* 4. Config fallback
|
|
574
|
+
*
|
|
575
|
+
* If there is a default variant and no fallback, return the default variant.
|
|
576
|
+
*/
|
|
577
|
+
private localEvaluationVariantAndSource(
|
|
578
|
+
key: string,
|
|
579
|
+
flag: EvaluationFlag,
|
|
580
|
+
fallback?: string | Variant,
|
|
581
|
+
): SourceVariant {
|
|
582
|
+
let defaultSourceVariant: SourceVariant = {};
|
|
583
|
+
// Local evaluation
|
|
584
|
+
const variant = this.evaluate([flag.key])[key];
|
|
585
|
+
const source = VariantSource.LocalEvaluation;
|
|
586
|
+
const isLocalEvaluationDefault = variant?.metadata?.default as boolean;
|
|
587
|
+
if (!isNullOrUndefined(variant) && !isLocalEvaluationDefault) {
|
|
588
|
+
return {
|
|
589
|
+
variant: convertVariant(variant),
|
|
590
|
+
source: source,
|
|
591
|
+
hasDefaultVariant: false,
|
|
592
|
+
};
|
|
593
|
+
} else if (isLocalEvaluationDefault) {
|
|
594
|
+
defaultSourceVariant = {
|
|
595
|
+
variant: convertVariant(variant),
|
|
596
|
+
source: source,
|
|
597
|
+
hasDefaultVariant: true,
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
// Inline fallback
|
|
601
|
+
if (!isNullOrUndefined(fallback)) {
|
|
602
|
+
return {
|
|
603
|
+
variant: convertVariant(fallback),
|
|
604
|
+
source: VariantSource.FallbackInline,
|
|
605
|
+
hasDefaultVariant: defaultSourceVariant.hasDefaultVariant,
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
// Initial variants
|
|
609
|
+
const initialVariant = this.config.initialVariants[key];
|
|
610
|
+
if (!isNullOrUndefined(initialVariant)) {
|
|
611
|
+
return {
|
|
612
|
+
variant: convertVariant(initialVariant),
|
|
613
|
+
source: VariantSource.SecondaryInitialVariants,
|
|
614
|
+
hasDefaultVariant: defaultSourceVariant.hasDefaultVariant,
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
// Configured fallback, or default variant
|
|
618
|
+
const fallbackVariant = convertVariant(this.config.fallbackVariant);
|
|
619
|
+
const fallbackSourceVariant = {
|
|
620
|
+
variant: fallbackVariant,
|
|
621
|
+
source: VariantSource.FallbackConfig,
|
|
622
|
+
hasDefaultVariant: defaultSourceVariant.hasDefaultVariant,
|
|
623
|
+
};
|
|
624
|
+
if (!isNullUndefinedOrEmpty(fallbackVariant)) {
|
|
625
|
+
return fallbackSourceVariant;
|
|
626
|
+
}
|
|
627
|
+
return defaultSourceVariant;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* For Source.LocalStorage, fallback order goes:
|
|
632
|
+
*
|
|
633
|
+
* 1. Local Storage
|
|
634
|
+
* 2. Inline function fallback
|
|
635
|
+
* 3. InitialFlags
|
|
636
|
+
* 4. Config fallback
|
|
637
|
+
*
|
|
638
|
+
* If there is a default variant and no fallback, return the default variant.
|
|
639
|
+
*/
|
|
640
|
+
private localStorageVariantAndSource(
|
|
641
|
+
key: string,
|
|
642
|
+
fallback?: string | Variant,
|
|
643
|
+
): SourceVariant {
|
|
644
|
+
let defaultSourceVariant: SourceVariant = {};
|
|
645
|
+
// Local storage
|
|
646
|
+
const localStorageVariant = this.variants.get(key);
|
|
647
|
+
const isLocalStorageDefault = localStorageVariant?.metadata
|
|
648
|
+
?.default as boolean;
|
|
649
|
+
if (!isNullOrUndefined(localStorageVariant) && !isLocalStorageDefault) {
|
|
650
|
+
return {
|
|
651
|
+
variant: convertVariant(localStorageVariant),
|
|
652
|
+
source: VariantSource.LocalStorage,
|
|
653
|
+
hasDefaultVariant: false,
|
|
654
|
+
};
|
|
655
|
+
} else if (isLocalStorageDefault) {
|
|
656
|
+
defaultSourceVariant = {
|
|
657
|
+
variant: convertVariant(localStorageVariant),
|
|
658
|
+
source: VariantSource.LocalStorage,
|
|
659
|
+
hasDefaultVariant: true,
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
// Inline fallback
|
|
663
|
+
if (!isNullOrUndefined(fallback)) {
|
|
664
|
+
return {
|
|
665
|
+
variant: convertVariant(fallback),
|
|
666
|
+
source: VariantSource.FallbackInline,
|
|
667
|
+
hasDefaultVariant: defaultSourceVariant.hasDefaultVariant,
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
// Initial variants
|
|
671
|
+
const initialVariant = this.config.initialVariants[key];
|
|
672
|
+
if (!isNullOrUndefined(initialVariant)) {
|
|
673
|
+
return {
|
|
674
|
+
variant: convertVariant(initialVariant),
|
|
675
|
+
source: VariantSource.SecondaryInitialVariants,
|
|
676
|
+
hasDefaultVariant: defaultSourceVariant.hasDefaultVariant,
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
// Configured fallback, or default variant
|
|
680
|
+
const fallbackVariant = convertVariant(this.config.fallbackVariant);
|
|
681
|
+
const fallbackSourceVariant = {
|
|
682
|
+
variant: fallbackVariant,
|
|
683
|
+
source: VariantSource.FallbackConfig,
|
|
684
|
+
hasDefaultVariant: defaultSourceVariant.hasDefaultVariant,
|
|
685
|
+
};
|
|
686
|
+
if (!isNullUndefinedOrEmpty(fallbackVariant)) {
|
|
687
|
+
return fallbackSourceVariant;
|
|
688
|
+
}
|
|
689
|
+
return defaultSourceVariant;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* For Source.InitialVariants, fallback order goes:
|
|
694
|
+
*
|
|
695
|
+
* 1. Initial variants
|
|
696
|
+
* 2. Local storage
|
|
697
|
+
* 3. Inline function fallback
|
|
698
|
+
* 4. Config fallback
|
|
699
|
+
*
|
|
700
|
+
* If there is a default variant and no fallback, return the default variant.
|
|
701
|
+
*/
|
|
702
|
+
private initialVariantsVariantAndSource(
|
|
703
|
+
key: string,
|
|
704
|
+
fallback?: string | Variant,
|
|
705
|
+
): SourceVariant {
|
|
706
|
+
let defaultSourceVariant: SourceVariant = {};
|
|
707
|
+
// Initial variants
|
|
708
|
+
const initialVariantsVariant = this.config.initialVariants[key];
|
|
709
|
+
if (!isNullOrUndefined(initialVariantsVariant)) {
|
|
710
|
+
return {
|
|
711
|
+
variant: convertVariant(initialVariantsVariant),
|
|
712
|
+
source: VariantSource.InitialVariants,
|
|
713
|
+
hasDefaultVariant: false,
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
// Local storage
|
|
717
|
+
const localStorageVariant = this.variants.get(key);
|
|
718
|
+
const isLocalStorageDefault = localStorageVariant?.metadata
|
|
719
|
+
?.default as boolean;
|
|
720
|
+
if (!isNullOrUndefined(localStorageVariant) && !isLocalStorageDefault) {
|
|
721
|
+
return {
|
|
722
|
+
variant: convertVariant(localStorageVariant),
|
|
723
|
+
source: VariantSource.LocalStorage,
|
|
724
|
+
hasDefaultVariant: false,
|
|
725
|
+
};
|
|
726
|
+
} else if (isLocalStorageDefault) {
|
|
727
|
+
defaultSourceVariant = {
|
|
728
|
+
variant: convertVariant(localStorageVariant),
|
|
729
|
+
source: VariantSource.LocalStorage,
|
|
730
|
+
hasDefaultVariant: true,
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
// Inline fallback
|
|
734
|
+
if (!isNullOrUndefined(fallback)) {
|
|
735
|
+
return {
|
|
736
|
+
variant: convertVariant(fallback),
|
|
737
|
+
source: VariantSource.FallbackInline,
|
|
738
|
+
hasDefaultVariant: defaultSourceVariant.hasDefaultVariant,
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
// Configured fallback, or default variant
|
|
742
|
+
const fallbackVariant = convertVariant(this.config.fallbackVariant);
|
|
743
|
+
const fallbackSourceVariant = {
|
|
744
|
+
variant: fallbackVariant,
|
|
745
|
+
source: VariantSource.FallbackConfig,
|
|
746
|
+
hasDefaultVariant: defaultSourceVariant.hasDefaultVariant,
|
|
747
|
+
};
|
|
748
|
+
if (!isNullUndefinedOrEmpty(fallbackVariant)) {
|
|
749
|
+
return fallbackSourceVariant;
|
|
750
|
+
}
|
|
751
|
+
return defaultSourceVariant;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
private async fetchInternal(
|
|
755
|
+
user: ExperimentUser,
|
|
756
|
+
timeoutMillis: number,
|
|
757
|
+
retry: boolean,
|
|
758
|
+
options?: FetchOptions,
|
|
759
|
+
): Promise<Variants> {
|
|
760
|
+
// Don't even try to fetch variants if API key is not set
|
|
761
|
+
if (!this.apiKey) {
|
|
762
|
+
throw Error("Experiment API key is empty");
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
this.logger.debug(`[Experiment] Fetch all: retry=${retry}`);
|
|
766
|
+
|
|
767
|
+
// Proactively cancel retries if active in order to avoid unnecessary API
|
|
768
|
+
// requests. A new failure will restart the retries.
|
|
769
|
+
if (retry) {
|
|
770
|
+
this.stopRetries();
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
const sequenceNumber = ++this.fetchSequenceNumber;
|
|
774
|
+
|
|
775
|
+
try {
|
|
776
|
+
const variants = await this.doFetch(user, timeoutMillis, options);
|
|
777
|
+
await this.storeVariants(variants, sequenceNumber, options);
|
|
778
|
+
return variants;
|
|
779
|
+
} catch (e: unknown) {
|
|
780
|
+
if (retry && this.shouldRetryFetch(e)) {
|
|
781
|
+
this.startRetries(user, options);
|
|
782
|
+
}
|
|
783
|
+
throw e;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
private async fetchWithRetries(
|
|
788
|
+
user: ExperimentUser,
|
|
789
|
+
options?: FetchOptions,
|
|
790
|
+
): Promise<Variants> {
|
|
791
|
+
return await this.fetchInternal(
|
|
792
|
+
user,
|
|
793
|
+
this.config.fetchTimeoutMillis,
|
|
794
|
+
this.config.retryFetchOnFailure,
|
|
795
|
+
options,
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
private async doFetch(
|
|
800
|
+
user: ExperimentUser,
|
|
801
|
+
timeoutMillis: number,
|
|
802
|
+
options?: FetchOptions,
|
|
803
|
+
): Promise<Variants> {
|
|
804
|
+
user = await this.addContextOrWait(user, 10000);
|
|
805
|
+
this.logger.debug("[Experiment] Fetch variants for user: ", user);
|
|
806
|
+
const results = await this.evaluationApi.getVariants(user, {
|
|
807
|
+
...this.fetchVariantsOptions.get(),
|
|
808
|
+
timeoutMillis: timeoutMillis,
|
|
809
|
+
flagKeys: options?.flagKeys,
|
|
810
|
+
});
|
|
811
|
+
const variants: Variants = {};
|
|
812
|
+
for (const key of Object.keys(results)) {
|
|
813
|
+
variants[key] = convertEvaluationVariantToVariant(results[key]);
|
|
814
|
+
}
|
|
815
|
+
this.logger.debug("[Experiment] Received variants: ", variants);
|
|
816
|
+
return variants;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
private async doFlags(): Promise<void> {
|
|
820
|
+
const flags = await this.flagApi.getFlags({
|
|
821
|
+
libraryName: "experiment-js-client",
|
|
822
|
+
libraryVersion: PACKAGE_VERSION,
|
|
823
|
+
timeoutMillis: this.config.fetchTimeoutMillis,
|
|
824
|
+
});
|
|
825
|
+
this.flags.clear();
|
|
826
|
+
this.flags.putAll(flags);
|
|
827
|
+
await this.flags.store();
|
|
828
|
+
this.mergeInitialFlagsWithStorage();
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
private async pollFlags(): Promise<void> {
|
|
832
|
+
try {
|
|
833
|
+
await this.doFlags();
|
|
834
|
+
} catch (e) {
|
|
835
|
+
this.logger.warn(e);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
private async storeVariants(
|
|
840
|
+
variants: Variants,
|
|
841
|
+
sequenceNumber: number,
|
|
842
|
+
options?: FetchOptions,
|
|
843
|
+
): Promise<void> {
|
|
844
|
+
if (sequenceNumber <= this.storedFetchSequenceNumber) {
|
|
845
|
+
this.logger.debug(
|
|
846
|
+
`[Experiment] Ignoring stale fetch response (sequence ${sequenceNumber} <= ${this.storedFetchSequenceNumber})`,
|
|
847
|
+
);
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
let failedFlagKeys = options?.flagKeys ? options.flagKeys : [];
|
|
852
|
+
if (failedFlagKeys.length === 0) {
|
|
853
|
+
this.variants.clear();
|
|
854
|
+
}
|
|
855
|
+
for (const key in variants) {
|
|
856
|
+
failedFlagKeys = failedFlagKeys.filter((flagKey) => flagKey !== key);
|
|
857
|
+
this.variants.put(key, variants[key]);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
for (const key in failedFlagKeys) {
|
|
861
|
+
this.variants.remove(key);
|
|
862
|
+
}
|
|
863
|
+
await this.variants.store();
|
|
864
|
+
this.storedFetchSequenceNumber = sequenceNumber;
|
|
865
|
+
this.logger.debug("[Experiment] Stored variants: ", variants);
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
private startRetries(user: ExperimentUser, options?: FetchOptions): void {
|
|
869
|
+
this.logger.debug("[Experiment] Retry fetch");
|
|
870
|
+
this.retriesBackoff = new Backoff(
|
|
871
|
+
fetchBackoffAttempts,
|
|
872
|
+
fetchBackoffMinMillis,
|
|
873
|
+
fetchBackoffMaxMillis,
|
|
874
|
+
fetchBackoffScalar,
|
|
875
|
+
);
|
|
876
|
+
this.retriesBackoff.start(async () => {
|
|
877
|
+
await this.fetchInternal(user, fetchBackoffTimeout, false, options);
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
private stopRetries(): void {
|
|
882
|
+
if (this.retriesBackoff != null) {
|
|
883
|
+
this.retriesBackoff.cancel();
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
private addContextSync(user: ExperimentUser): ExperimentUser {
|
|
888
|
+
const providedUser = this.defaultUserProvider.getUserSync();
|
|
889
|
+
return this.mergeContext(user, providedUser);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
private async addContext(user: ExperimentUser): Promise<ExperimentUser> {
|
|
893
|
+
const providedUser = await this.defaultUserProvider.getUser();
|
|
894
|
+
return this.mergeContext(user, providedUser);
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
private async addContextOrWait(
|
|
898
|
+
user: ExperimentUser,
|
|
899
|
+
ms: number,
|
|
900
|
+
): Promise<ExperimentUser> {
|
|
901
|
+
const baseProvider = this.defaultUserProvider.baseProvider;
|
|
902
|
+
if (baseProvider instanceof ConnectorUserProvider) {
|
|
903
|
+
await baseProvider.identityReady(ms);
|
|
904
|
+
}
|
|
905
|
+
return this.addContext(user);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
private mergeContext(
|
|
909
|
+
user: ExperimentUser,
|
|
910
|
+
providedUser: ExperimentUser,
|
|
911
|
+
): ExperimentUser {
|
|
912
|
+
const mergedUserProperties = {
|
|
913
|
+
...user?.user_properties,
|
|
914
|
+
...providedUser?.user_properties,
|
|
915
|
+
};
|
|
916
|
+
return {
|
|
917
|
+
library: `experiment-react-native-client/${PACKAGE_VERSION}`,
|
|
918
|
+
...providedUser,
|
|
919
|
+
...user,
|
|
920
|
+
user_properties: mergedUserProperties,
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
private sourceVariants(): Variants {
|
|
925
|
+
if (this.config.source === Source.LocalStorage) {
|
|
926
|
+
return this.variants.getAll();
|
|
927
|
+
} else if (this.config.source === Source.InitialVariants) {
|
|
928
|
+
return this.config.initialVariants;
|
|
929
|
+
}
|
|
930
|
+
return {};
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
private secondaryVariants(): Variants {
|
|
934
|
+
if (this.config.source === Source.LocalStorage) {
|
|
935
|
+
return this.config.initialVariants;
|
|
936
|
+
} else if (this.config.source === Source.InitialVariants) {
|
|
937
|
+
return this.variants.getAll();
|
|
938
|
+
}
|
|
939
|
+
return {};
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
private exposureInternal(key: string, sourceVariant: SourceVariant): void {
|
|
943
|
+
const exposure: Exposure = { flag_key: key };
|
|
944
|
+
// Do not track exposure for fallback variants that are not associated with
|
|
945
|
+
// a default variant.
|
|
946
|
+
const fallback = isFallback(sourceVariant.source);
|
|
947
|
+
if (fallback && !sourceVariant.hasDefaultVariant) {
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
if (sourceVariant.variant?.expKey) {
|
|
951
|
+
exposure.experiment_key = sourceVariant.variant?.expKey;
|
|
952
|
+
}
|
|
953
|
+
const metadata = sourceVariant.variant?.metadata;
|
|
954
|
+
if (!fallback && !metadata?.default) {
|
|
955
|
+
if (sourceVariant.variant?.key) {
|
|
956
|
+
exposure.variant = sourceVariant.variant.key;
|
|
957
|
+
} else if (sourceVariant.variant?.value) {
|
|
958
|
+
exposure.variant = sourceVariant.variant.value;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
if (metadata) exposure.metadata = metadata;
|
|
962
|
+
const user = this.addContextSync(this.getUser());
|
|
963
|
+
this.userSessionExposureTracker?.track(exposure, user);
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
private static getLogLevel(config: ExperimentConfig): LogLevel {
|
|
967
|
+
// Backwards compatibility: if debug flag is set to true, use Debug level
|
|
968
|
+
if (config.debug === true) {
|
|
969
|
+
return LogLevel.Debug;
|
|
970
|
+
}
|
|
971
|
+
// Otherwise use the configured logLevel or default to Warn
|
|
972
|
+
return config.logLevel ?? LogLevel.Warn;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
private shouldRetryFetch(e: unknown): boolean {
|
|
976
|
+
if (e instanceof FetchError) {
|
|
977
|
+
return e.statusCode < 400 || e.statusCode >= 500 || e.statusCode === 429;
|
|
978
|
+
}
|
|
979
|
+
return true;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
type SourceVariant = {
|
|
984
|
+
variant?: Variant | undefined;
|
|
985
|
+
source?: VariantSource | undefined;
|
|
986
|
+
hasDefaultVariant?: boolean | undefined;
|
|
987
|
+
};
|