From 67fb5ce58b23d3cd20f5473b1c889fdaf2b78d9f Mon Sep 17 00:00:00 2001 From: Ben Woskow <48036130+bwoskow-ld@users.noreply.github.com> Date: Wed, 3 Feb 2021 15:16:18 -0800 Subject: [PATCH 01/32] Removed the guides link --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 03efbbd..53f6285 100644 --- a/README.md +++ b/README.md @@ -48,4 +48,3 @@ About LaunchDarkly * [docs.launchdarkly.com](https://docs.launchdarkly.com/ "LaunchDarkly Documentation") for our documentation and SDK reference guides * [apidocs.launchdarkly.com](https://apidocs.launchdarkly.com/ "LaunchDarkly API Documentation") for our API documentation * [blog.launchdarkly.com](https://blog.launchdarkly.com/ "LaunchDarkly Blog Documentation") for the latest product updates - * [Feature Flagging Guide](https://github.com/launchdarkly/featureflags/ "Feature Flagging Guide") for best practices and strategies From b6a113ce502cdc237bed7eac7a50d3e87c310878 Mon Sep 17 00:00:00 2001 From: Joe Cieslik <5600929+torchhound@users.noreply.github.com> Date: Wed, 31 Mar 2021 18:56:37 -0700 Subject: [PATCH 02/32] V4.0 (#68) * Update to iOS 5.4.0 (#48) * Bump iOS SDK version to 5.4.0 * Replace shared with get() * Replace old method names * Provide default values for EvaluationDetail * Fixed defaultValue for detail value * Changed individual nil coalesce to NSNull * Remove isDisableBackgroundPolling method (#52) * Update iOS base url (#49) * Update iOS base url * Added wrapper name and version to iOS and Android config (#50) * Added wrapper name and version to iOS and Android config * V4 not 5 * Rename wrapper * Add getVersion method (#51) * Added getVersion method * Revert package.json version bump * Fix version case Co-authored-by: Ben Woskow <48036130+bwoskow-ld@users.noreply.github.com> Co-authored-by: Ben Woskow <48036130+bwoskow-ld@users.noreply.github.com> Co-authored-by: Ben Woskow <48036130+bwoskow-ld@users.noreply.github.com> * Rename fallback to defaultValue (#53) * Rename fallback to defaultValue * Fix awkward wording defaultValue value * Fix defaultValue value line breaks * Improved resiliency when running in Android (#54) * Add new config values (#55) * Added new config values to iOS and Android * Added typescript config * PR feedback * Fix millis, add default doc * fix common build + add tests for recent introductions (#56) * fix: start background thread for identify rather than running it (#66) * Bump Android SDK version to 2.14.1 (#59) * Update Android to 2.14.1, change setBaseUri to setPollUri, change floatVariation to doubleVariation * Fix float to double * Doublevalue on non detail * Run CI against v4.0 * URL fix iOS * Add isInitialized to iOS, check initialization in configure (#60) * Added isInitialized to iOS * Add init check to configure * Base url iOS fix, v4.0 ci * Var not func iOS, remove getMap * disable auto-alias in iOS (#61) * Add configureWithTimeout method (#58) * Added configureTimeout method * Fix timeout type on iOS * Simplify timeout nil check * Added param labels, Java syntax fixes * Small fixes * Make timeout final * Remove unnecessary catch * Fix baseUrl * Fix Android config error * Test against v4.0 hello branch * Fix tests * timeout never nil inside check * Convert Int to TimeInterval * Fixed iOS startWaitSeconds * Catch LDException * Fix merge conflict in iOS bridge * Fix merge conflict in index.js * PR feedback * Fix unused timeoutClient * Remove unnecessary ConfigEntryType * Remove StringSet * If let in timeout check * Configure method now takes optional timeout parameter instead of separate method * Renames for consistency (#62) * up-leveling the override for the default polling uri so it affects android too (#63) * Added ip, avatar, and allUserAttributesPrivate (#57) * Added ip, avatar, and allUserAttributesPrivate * Updated test-types.ts, fix case typo * Special case allUserAttributesPrivate * Fix ReadableMap loading of all private * CI fix * Fix string to URL baseUrl * Change to non-default values in test, combine lines in config.yml * Simplify allAttrsPrivate if * resolve breakage with latest merge Co-authored-by: Ben Woskow * Added accessor methods for ConnectionInformation (#64) * Added ConnectionInformation accessors * Fixes from manual testing * PR feedback * Fix allUserAttributesPrivate * Improve typescript docs * Minor fixes (#66) Fixes `floatVariation` to keep double precision and fix build warnings. * Fix track metricValue on iOS (#67) * Remove CI checkout for release Co-authored-by: Ben Woskow <48036130+bwoskow-ld@users.noreply.github.com> Co-authored-by: Ed Costello Co-authored-by: Ben Woskow Co-authored-by: Gavin Whelan --- README.md | 2 +- android/build.gradle | 2 +- .../LaunchdarklyReactNativeClientModule.java | 425 ++++++++++++------ index.d.ts | 259 +++++++++-- index.js | 151 ++++--- ios/LaunchdarklyReactNativeClient.podspec | 2 +- ios/LaunchdarklyReactNativeClient.swift | 378 ++++++++++------ ios/LaunchdarklyReactNativeClientBridge.m | 68 +-- test-types.ts | 34 +- 9 files changed, 883 insertions(+), 438 deletions(-) diff --git a/README.md b/README.md index 53f6285..8b88cbc 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ LaunchDarkly overview Supported versions ------------------------- -This SDK is compatible with React Native 0.62.x and Xcode 11.4 and is tested in Android 27 and iOS 12.4. Earlier versions of this SDK are compatible with prior versions of React Native, Android, and iOS. +This SDK is compatible with React Native 0.62.x and Xcode 12 and is tested in Android 27 and iOS 13.5. Earlier versions of this SDK are compatible with prior versions of React Native, Android, and iOS. Getting started --------------- diff --git a/android/build.gradle b/android/build.gradle index 96c8c73..8eeeced 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -49,7 +49,7 @@ allprojects { dependencies { implementation 'com.facebook.react:react-native:+' - implementation 'com.launchdarkly:launchdarkly-android-client-sdk:2.10.0' + implementation 'com.launchdarkly:launchdarkly-android-client-sdk:2.14.1' implementation 'com.jakewharton.timber:timber:4.7.1' implementation "com.google.code.gson:gson:2.8.5" } diff --git a/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java b/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java index ccb0a15..4190b76 100644 --- a/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java +++ b/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java @@ -36,6 +36,7 @@ import com.launchdarkly.android.EvaluationDetail; import com.launchdarkly.android.EvaluationReason; import com.launchdarkly.android.LDFailure; +import com.launchdarkly.android.LaunchDarklyException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -68,7 +69,7 @@ public class LaunchdarklyReactNativeClientModule extends ReactContextBaseJavaMod */ enum ConfigMapping { CONFIG_MOBILE_KEY("mobileKey", ConfigEntryType.String, "setMobileKey"), - CONFIG_BASE_URI("baseUri", ConfigEntryType.Uri, "setBaseUri"), + CONFIG_BASE_URI("pollUri", ConfigEntryType.Uri, "setPollUri"), CONFIG_EVENTS_URI("eventsUri", ConfigEntryType.UriMobile, "setEventsUri"), CONFIG_STREAM_URI("streamUri", ConfigEntryType.Uri, "setStreamUri"), CONFIG_EVENTS_CAPACITY("eventsCapacity", ConfigEntryType.Integer, "setEventsCapacity"), @@ -81,7 +82,12 @@ enum ConfigMapping { CONFIG_DISABLE_BACKGROUND_UPDATING("disableBackgroundUpdating", ConfigEntryType.Boolean, "setDisableBackgroundUpdating"), CONFIG_OFFLINE("offline", ConfigEntryType.Boolean, "setOffline"), CONFIG_PRIVATE_ATTRIBUTES("privateAttributeNames", ConfigEntryType.StringSet, "setPrivateAttributeNames"), - CONFIG_EVALUATION_REASONS("evaluationReasons", ConfigEntryType.Boolean, "setEvaluationReasons"); + CONFIG_EVALUATION_REASONS("evaluationReasons", ConfigEntryType.Boolean, "setEvaluationReasons"), + CONFIG_WRAPPER_NAME("wrapperName", ConfigEntryType.String, "setWrapperName"), + CONFIG_WRAPPER_VERSION("wrapperVersion", ConfigEntryType.String, "setWrapperVersion"), + CONFIG_MAX_CACHED_USERS("maxCachedUsers", ConfigEntryType.Integer, "setMaxCachedUsers"), + CONFIG_DIAGNOSTIC_OPT_OUT("diagnosticOptOut", ConfigEntryType.Boolean, "setDiagnosticOptOut"), + CONFIG_DIAGNOSTIC_RECORDING_INTERVAL("diagnosticRecordingIntervalMillis", ConfigEntryType.Integer, "setDiagnosticRecordingIntervalMillis"); final String key; final ConfigEntryType type; @@ -98,9 +104,9 @@ void loadFromMap(ReadableMap map, LDConfig.Builder builder) { try { setter.invoke(builder, type.getFromMap(map, key)); } catch (IllegalAccessException e) { - e.printStackTrace(); + Timber.w(e); } catch (InvocationTargetException e) { - e.printStackTrace(); + Timber.w(e); } } } @@ -149,9 +155,9 @@ void loadFromMap(ReadableMap map, LDUser.Builder builder, Set privateAtt setter.invoke(builder, type.getFromMap(map, key)); } } catch (IllegalAccessException e) { - e.printStackTrace(); + Timber.w(e); } catch (InvocationTargetException e) { - e.printStackTrace(); + Timber.w(e); } } } @@ -214,19 +220,38 @@ public Map getConstants() { * configuration. * * @param config LDConfig configuration, @see configBuild - * @param userConfig LDUser configuration, @see userBuild + * @param user LDUser configuration, @see userBuild * @param promise Either rejected if an error was encountered, otherwise resolved with null * once client is initialized. */ @ReactMethod - public void configure(ReadableMap config, ReadableMap userConfig, final Promise promise) { + public void configure(ReadableMap config, ReadableMap user, final Promise promise) { + internalConfigure(config, user, null, promise); + } + + /** + * React Method called from JavaScript to initialize the LDClient using the supplied + * configuration with a timeout. + * + * @param config LDConfig configuration, @see configBuild + * @param user LDUser configuration, @see userBuild + * @param timeout Integer that blocks until the latest feature flags have been retrieved from LaunchDarkly + * @param promise Either rejected if an error was encountered, otherwise resolved with null + * once client is initialized. + */ + @ReactMethod + public void configureWithTimeout(ReadableMap config, ReadableMap user, Integer timeout, final Promise promise) { + internalConfigure(config, user, timeout, promise); + } + + private void internalConfigure(ReadableMap config, ReadableMap user, final Integer timeout, final Promise promise) { if (ldClient != null) { promise.reject(ERROR_INIT, "Client was already initialized"); return; } final LDConfig.Builder ldConfigBuilder = configBuild(config); - final LDUser.Builder userBuilder = userBuild(userConfig); + final LDUser.Builder userBuilder = userBuild(user); if (ldConfigBuilder == null) { promise.reject(ERROR_INIT, "Client could not be built using supplied configuration"); @@ -238,6 +263,12 @@ public void configure(ReadableMap config, ReadableMap userConfig, final Promise return; } + if (config.hasKey("allUserAttributesPrivate") + && config.getType("allUserAttributesPrivate").equals(ConfigEntryType.Boolean.getReadableType()) + && config.getBoolean("allUserAttributesPrivate")) { + ldConfigBuilder.allAttributesPrivate(); + } + final Application application = (Application) getReactApplicationContext().getApplicationContext(); if (application != null) { @@ -245,13 +276,20 @@ public void configure(ReadableMap config, ReadableMap userConfig, final Promise @Override public void run() { try { - ldClient = LDClient.init(application, ldConfigBuilder.build(), userBuilder.build()).get(); + if (timeout != null) { + ldClient = LDClient.init(application, ldConfigBuilder.build(), userBuilder.build(), timeout).get(); + } else { + ldClient = LDClient.init(application, ldConfigBuilder.build(), userBuilder.build()).get(); + } promise.resolve(null); } catch (InterruptedException e) { - e.printStackTrace(); + Timber.w(e); promise.reject(ERROR_INIT, e); } catch (ExecutionException e) { - e.printStackTrace(); + Timber.w(e); + promise.reject(ERROR_INIT, e); + } catch (LaunchDarklyException e) { + Timber.w(e); promise.reject(ERROR_INIT, e); } } @@ -259,7 +297,7 @@ public void run() { background.start(); } else { - Timber.e("Couldn't initialize LaunchDarklyModule because the application was null"); + Timber.e("Couldn't initialize the LaunchDarkly module because the application was null"); promise.reject(ERROR_INIT, "Couldn't acquire application context"); } } @@ -396,89 +434,89 @@ private LDUser.Builder userBuild(ReadableMap options) { @ReactMethod public void boolVariation(String flagKey, Promise promise) { - boolVariationFallback(flagKey, null, promise); + boolVariationDefaultValue(flagKey, null, promise); } /** - * Looks up the current value for a flag, in the case of any issues, returns the given fallback + * Looks up the current value for a flag, in the case of any issues, returns the given default * value. * * @param flagKey The lookup key of the flag. - * @param fallback A fallback value to return if current value could not be acquired. + * @param defaultValue A default value to return if current value could not be acquired. * @param promise Used to return the result to React Native */ @ReactMethod - public void boolVariationFallback(String flagKey, Boolean fallback, Promise promise) { + public void boolVariationDefaultValue(String flagKey, Boolean defaultValue, Promise promise) { try { - promise.resolve(ldClient.boolVariation(flagKey, fallback)); + promise.resolve(ldClient.boolVariation(flagKey, defaultValue)); } catch (Exception e) { - promise.resolve(fallback); + promise.resolve(defaultValue); } } @ReactMethod public void intVariation(String flagKey, Promise promise) { - intVariationFallback(flagKey, null, promise); + intVariationDefaultValue(flagKey, null, promise); } /** - * Looks up the current value for a flag, in the case of any issues, returns the given fallback + * Looks up the current value for a flag, in the case of any issues, returns the given default * value. * * @param flagKey The lookup key of the flag. - * @param fallback A fallback value to return if current value could not be acquired. + * @param defaultValue A default value to return if current value could not be acquired. * @param promise Used to return the result to React Native */ @ReactMethod - public void intVariationFallback(String flagKey, Integer fallback, Promise promise) { + public void intVariationDefaultValue(String flagKey, Integer defaultValue, Promise promise) { try { - promise.resolve(ldClient.intVariation(flagKey, fallback)); + promise.resolve(ldClient.intVariation(flagKey, defaultValue)); } catch (Exception e) { - promise.resolve(fallback); + promise.resolve(defaultValue); } } @ReactMethod public void floatVariation(String flagKey, Promise promise) { - floatVariationFallback(flagKey, null, promise); + floatVariationDefaultValue(flagKey, null, promise); } /** - * Looks up the current value for a flag, in the case of any issues, returns the given fallback + * Looks up the current value for a flag, in the case of any issues, returns the given default * value. * * @param flagKey The lookup key of the flag. - * @param fallback A fallback value to return if current value could not be acquired. + * @param defaultValue A default value to return if current value could not be acquired. * @param promise Used to return the result to React Native */ @ReactMethod - public void floatVariationFallback(String flagKey, Float fallback, Promise promise) { + public void floatVariationDefaultValue(String flagKey, Double defaultValue, Promise promise) { try { - promise.resolve(ldClient.floatVariation(flagKey, fallback)); + promise.resolve(ldClient.doubleVariation(flagKey, defaultValue)); } catch (Exception e) { - promise.resolve(fallback); + promise.resolve(defaultValue); } } @ReactMethod public void stringVariation(String flagKey, Promise promise) { - stringVariationFallback(flagKey, null, promise); + stringVariationDefaultValue(flagKey, null, promise); } /** - * Looks up the current value for a flag, in the case of any issues, returns the given fallback + * Looks up the current value for a flag, in the case of any issues, returns the given default * value. * * @param flagKey The lookup key of the flag. - * @param fallback A fallback value to return if current value could not be acquired. + * @param defaultValue A default value to return if current value could not be acquired. * @param promise Used to return the result to React Native */ @ReactMethod - public void stringVariationFallback(String flagKey, String fallback, Promise promise) { + public void stringVariationDefaultValue(String flagKey, String defaultValue, Promise promise) { try { - promise.resolve(ldClient.stringVariation(flagKey, fallback)); + promise.resolve(ldClient.stringVariation(flagKey, defaultValue)); } catch (Exception e) { - promise.resolve(fallback); + promise.resolve(defaultValue); } } @@ -495,83 +533,83 @@ public void jsonVariationNone(String flagKey, Promise promise) { } /** - * Looks up the current value for a flag, in the case of any issues, returns the given fallback + * Looks up the current value for a flag, in the case of any issues, returns the given default * value. * * @param flagKey The lookup key of the flag. - * @param fallback A fallback value to return if current value could not be acquired. + * @param defaultValue A default value to return if current value could not be acquired. * @param promise Used to return the result to React Native */ @ReactMethod - public void jsonVariationNumber(String flagKey, Double fallback, Promise promise) { - jsonVariationBase(flagKey, new JsonPrimitive(fallback), promise); + public void jsonVariationNumber(String flagKey, Double defaultValue, Promise promise) { + jsonVariationBase(flagKey, new JsonPrimitive(defaultValue), promise); } /** - * Looks up the current value for a flag, in the case of any issues, returns the given fallback + * Looks up the current value for a flag, in the case of any issues, returns the given default * value. * * @param flagKey The lookup key of the flag. - * @param fallback A fallback value to return if current value could not be acquired. + * @param defaultValue A default value to return if current value could not be acquired. * @param promise Used to return the result to React Native */ @ReactMethod - public void jsonVariationBool(String flagKey, Boolean fallback, Promise promise) { - jsonVariationBase(flagKey, new JsonPrimitive(fallback), promise); + public void jsonVariationBool(String flagKey, Boolean defaultValue, Promise promise) { + jsonVariationBase(flagKey, new JsonPrimitive(defaultValue), promise); } /** - * Looks up the current value for a flag, in the case of any issues, returns the given fallback + * Looks up the current value for a flag, in the case of any issues, returns the given default * value. * * @param flagKey The lookup key of the flag. - * @param fallback A fallback value to return if current value could not be acquired. + * @param defaultValue A default value to return if current value could not be acquired. * @param promise Used to return the result to React Native */ @ReactMethod - public void jsonVariationString(String flagKey, String fallback, Promise promise) { - jsonVariationBase(flagKey, new JsonPrimitive(fallback), promise); + public void jsonVariationString(String flagKey, String defaultValue, Promise promise) { + jsonVariationBase(flagKey, new JsonPrimitive(defaultValue), promise); } /** - * Looks up the current value for a flag, in the case of any issues, returns the given fallback + * Looks up the current value for a flag, in the case of any issues, returns the given default * value. * * @param flagKey The lookup key of the flag. - * @param fallback A fallback value to return if current value could not be acquired. + * @param defaultValue A default value to return if current value could not be acquired. * @param promise Used to return the result to React Native */ @ReactMethod - public void jsonVariationArray(String flagKey, ReadableArray fallback, Promise promise) { - jsonVariationBase(flagKey, toJsonArray(fallback), promise); + public void jsonVariationArray(String flagKey, ReadableArray defaultValue, Promise promise) { + jsonVariationBase(flagKey, toJsonArray(defaultValue), promise); } /** - * Looks up the current value for a flag, in the case of any issues, returns the given fallback + * Looks up the current value for a flag, in the case of any issues, returns the given default * value. * * @param flagKey The lookup key of the flag. - * @param fallback A fallback value to return if current value could not be acquired. + * @param defaultValue A default value to return if current value could not be acquired. * @param promise Used to return the result to React Native */ @ReactMethod - public void jsonVariationObject(String flagKey, ReadableMap fallback, Promise promise) { - jsonVariationBase(flagKey, toJsonObject(fallback), promise); + public void jsonVariationObject(String flagKey, ReadableMap defaultValue, Promise promise) { + jsonVariationBase(flagKey, toJsonObject(defaultValue), promise); } @ReactMethod public void boolVariationDetail(String flagKey, Promise promise) { - boolVariationDetailFallback(flagKey, null, promise); + boolVariationDetailDefaultValue(flagKey, null, promise); } @ReactMethod - public void boolVariationDetailFallback(String flagKey, Boolean fallback, Promise promise) { + public void boolVariationDetailDefaultValue(String flagKey, Boolean defaultValue, Promise promise) { EvaluationDetail detailResult; try { - detailResult = ldClient.boolVariationDetail(flagKey, fallback); + detailResult = ldClient.boolVariationDetail(flagKey, defaultValue); } catch (Exception e) { - e.printStackTrace(); - detailResult = new EvaluationDetail(EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION), null, fallback); + Timber.w(e); + detailResult = new EvaluationDetail(EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION), null, defaultValue); } JsonObject jsonObject = gson.toJsonTree(detailResult).getAsJsonObject(); WritableMap detailMap = fromJsonObject(jsonObject); @@ -580,17 +618,17 @@ public void boolVariationDetailFallback(String flagKey, Boolean fallback, Promis @ReactMethod public void intVariationDetail(String flagKey, Promise promise) { - intVariationDetailFallback(flagKey, null, promise); + intVariationDetailDefaultValue(flagKey, null, promise); } @ReactMethod - public void intVariationDetailFallback(String flagKey, Integer fallback, Promise promise) { + public void intVariationDetailDefaultValue(String flagKey, Integer defaultValue, Promise promise) { EvaluationDetail detailResult; try { - detailResult = ldClient.intVariationDetail(flagKey, fallback); + detailResult = ldClient.intVariationDetail(flagKey, defaultValue); } catch (Exception e) { - e.printStackTrace(); - detailResult = new EvaluationDetail(EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION), null, fallback); + Timber.w(e); + detailResult = new EvaluationDetail(EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION), null, defaultValue); } JsonObject jsonObject = gson.toJsonTree(detailResult).getAsJsonObject(); WritableMap detailMap = fromJsonObject(jsonObject); @@ -599,17 +637,17 @@ public void intVariationDetailFallback(String flagKey, Integer fallback, Promise @ReactMethod public void floatVariationDetail(String flagKey, Promise promise) { - floatVariationDetailFallback(flagKey, null, promise); + floatVariationDetailDefaultValue(flagKey, null, promise); } @ReactMethod - public void floatVariationDetailFallback(String flagKey, Float fallback, Promise promise) { - EvaluationDetail detailResult; + public void floatVariationDetailDefaultValue(String flagKey, Double defaultValue, Promise promise) { + EvaluationDetail detailResult; try { - detailResult = ldClient.floatVariationDetail(flagKey, fallback); + detailResult = ldClient.doubleVariationDetail(flagKey, defaultValue); } catch (Exception e) { - e.printStackTrace(); - detailResult = new EvaluationDetail(EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION), null, fallback); + Timber.w(e); + detailResult = new EvaluationDetail(EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION), null, defaultValue); } JsonObject jsonObject = gson.toJsonTree(detailResult).getAsJsonObject(); WritableMap detailMap = fromJsonObject(jsonObject); @@ -618,17 +656,17 @@ public void floatVariationDetailFallback(String flagKey, Float fallback, Promise @ReactMethod public void stringVariationDetail(String flagKey, Promise promise) { - stringVariationDetailFallback(flagKey, null, promise); + stringVariationDetailDefaultValue(flagKey, null, promise); } @ReactMethod - public void stringVariationDetailFallback(String flagKey, String fallback, Promise promise) { + public void stringVariationDetailDefaultValue(String flagKey, String defaultValue, Promise promise) { EvaluationDetail detailResult; try { - detailResult = ldClient.stringVariationDetail(flagKey, fallback); + detailResult = ldClient.stringVariationDetail(flagKey, defaultValue); } catch (Exception e) { - e.printStackTrace(); - detailResult = new EvaluationDetail(EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION), null, fallback); + Timber.w(e); + detailResult = new EvaluationDetail(EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION), null, defaultValue); } JsonObject jsonObject = gson.toJsonTree(detailResult).getAsJsonObject(); WritableMap detailMap = fromJsonObject(jsonObject); @@ -641,53 +679,53 @@ public void jsonVariationDetailNone(String flagKey, Promise promise) { } @ReactMethod - public void jsonVariationDetailNumber(String flagKey, Double fallback, Promise promise) { - jsonVariationDetailBase(flagKey, new JsonPrimitive(fallback), promise); + public void jsonVariationDetailNumber(String flagKey, Double defaultValue, Promise promise) { + jsonVariationDetailBase(flagKey, new JsonPrimitive(defaultValue), promise); } @ReactMethod - public void jsonVariationDetailBool(String flagKey, Boolean fallback, Promise promise) { - jsonVariationDetailBase(flagKey, new JsonPrimitive(fallback), promise); + public void jsonVariationDetailBool(String flagKey, Boolean defaultValue, Promise promise) { + jsonVariationDetailBase(flagKey, new JsonPrimitive(defaultValue), promise); } @ReactMethod - public void jsonVariationDetailString(String flagKey, String fallback, Promise promise) { - jsonVariationDetailBase(flagKey, new JsonPrimitive(fallback), promise); + public void jsonVariationDetailString(String flagKey, String defaultValue, Promise promise) { + jsonVariationDetailBase(flagKey, new JsonPrimitive(defaultValue), promise); } @ReactMethod - public void jsonVariationDetailArray(String flagKey, ReadableArray fallback, Promise promise) { - jsonVariationDetailBase(flagKey, toJsonArray(fallback), promise); + public void jsonVariationDetailArray(String flagKey, ReadableArray defaultValue, Promise promise) { + jsonVariationDetailBase(flagKey, toJsonArray(defaultValue), promise); } @ReactMethod - public void jsonVariationDetailObject(String flagKey, ReadableMap fallback, Promise promise) { - jsonVariationDetailBase(flagKey, toJsonObject(fallback), promise); + public void jsonVariationDetailObject(String flagKey, ReadableMap defaultValue, Promise promise) { + jsonVariationDetailBase(flagKey, toJsonObject(defaultValue), promise); } /** * Helper for jsonVariation methods. * * @param flagKey The lookup key of the flag. - * @param fallback A fallback value to return if the current value could not be acquired. + * @param defaultValue A default value to return if the current value could not be acquired. * @param promise Used to return the result to React Native. */ - private void jsonVariationBase(String flagKey, JsonElement fallback, Promise promise) { + private void jsonVariationBase(String flagKey, JsonElement defaultValue, Promise promise) { try { - JsonElement jsonElement = ldClient.jsonVariation(flagKey, fallback); + JsonElement jsonElement = ldClient.jsonVariation(flagKey, defaultValue); resolveJsonElement(promise, jsonElement); } catch (Exception e) { - resolveJsonElement(promise, fallback); + resolveJsonElement(promise, defaultValue); } } - private void jsonVariationDetailBase(String flagKey, JsonElement fallback, Promise promise) { + private void jsonVariationDetailBase(String flagKey, JsonElement defaultValue, Promise promise) { EvaluationDetail jsonElementDetail; try { - jsonElementDetail = ldClient.jsonVariationDetail(flagKey, fallback); + jsonElementDetail = ldClient.jsonVariationDetail(flagKey, defaultValue); } catch (Exception e) { - e.printStackTrace(); - jsonElementDetail = new EvaluationDetail(EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION), null, fallback); + Timber.w(e); + jsonElementDetail = new EvaluationDetail(EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION), null, defaultValue); } resolveJsonElementDetail(promise, jsonElementDetail); } @@ -731,6 +769,11 @@ private void resolveJsonElementDetail(Promise promise, EvaluationDetail flags = ldClient.allFlags(); // Convert map of all flags into WritableMap for React Native @@ -790,7 +833,11 @@ public void allFlags(Promise promise) { */ @ReactMethod public void trackNumber(String eventName, Double data) { - ldClient.track(eventName, new JsonPrimitive(data)); + try { + ldClient.track(eventName, new JsonPrimitive(data)); + } catch (Exception e) { + Timber.w(e); + } } /** @@ -804,7 +851,11 @@ public void trackNumber(String eventName, Double data) { */ @ReactMethod public void trackBool(String eventName, Boolean data) { - ldClient.track(eventName, new JsonPrimitive(data)); + try { + ldClient.track(eventName, new JsonPrimitive(data)); + } catch (Exception e) { + Timber.w(e); + } } /** @@ -818,7 +869,11 @@ public void trackBool(String eventName, Boolean data) { */ @ReactMethod public void trackString(String eventName, String data) { - ldClient.track(eventName, new JsonPrimitive(data)); + try { + ldClient.track(eventName, new JsonPrimitive(data)); + } catch (Exception e) { + Timber.w(e); + } } /** @@ -832,7 +887,11 @@ public void trackString(String eventName, String data) { */ @ReactMethod public void trackArray(String eventName, ReadableArray data) { - ldClient.track(eventName, toJsonArray(data)); + try { + ldClient.track(eventName, toJsonArray(data)); + } catch (Exception e) { + Timber.w(e); + } } /** @@ -846,7 +905,11 @@ public void trackArray(String eventName, ReadableArray data) { */ @ReactMethod public void trackObject(String eventName, ReadableMap data) { - ldClient.track(eventName, toJsonObject(data)); + try { + ldClient.track(eventName, toJsonObject(data)); + } catch (Exception e) { + Timber.w(e); + } } /** @@ -856,37 +919,65 @@ public void trackObject(String eventName, ReadableMap data) { */ @ReactMethod public void track(String eventName) { - ldClient.track(eventName); + try { + ldClient.track(eventName); + } catch (Exception e) { + Timber.w(e); + } } @ReactMethod public void trackNumberMetricValue(String eventName, Double data, Double metricValue) { - ldClient.track(eventName, new JsonPrimitive(data), metricValue); + try { + ldClient.track(eventName, new JsonPrimitive(data), metricValue); + } catch (Exception e) { + Timber.w(e); + } } @ReactMethod public void trackBoolMetricValue(String eventName, Boolean data, Double metricValue) { - ldClient.track(eventName, new JsonPrimitive(data), metricValue); + try { + ldClient.track(eventName, new JsonPrimitive(data), metricValue); + } catch (Exception e) { + Timber.w(e); + } } @ReactMethod public void trackStringMetricValue(String eventName, String data, Double metricValue) { - ldClient.track(eventName, new JsonPrimitive(data), metricValue); + try { + ldClient.track(eventName, new JsonPrimitive(data), metricValue); + } catch (Exception e) { + Timber.w(e); + } } @ReactMethod public void trackArrayMetricValue(String eventName, ReadableArray data, Double metricValue) { - ldClient.track(eventName, toJsonArray(data), metricValue); + try { + ldClient.track(eventName, toJsonArray(data), metricValue); + } catch (Exception e) { + Timber.w(e); + } } @ReactMethod public void trackObjectMetricValue(String eventName, ReadableMap data, Double metricValue) { - ldClient.track(eventName, toJsonObject(data), metricValue); + try { + ldClient.track(eventName, toJsonObject(data), metricValue); + } catch (Exception e) { + Timber.w(e); + } } @ReactMethod public void trackMetricValue(String eventName, Double metricValue) { - ldClient.track(eventName, new JsonPrimitive(""), metricValue); + try { + ldClient.track(eventName, new JsonPrimitive(""), metricValue); + } catch (Exception e) { + Timber.w(e); + } } /** @@ -895,8 +986,12 @@ public void trackMetricValue(String eventName, Double metricValue) { */ @ReactMethod public void setOffline(Promise promise) { - ldClient.setOffline(); - promise.resolve(true); + try { + ldClient.setOffline(); + promise.resolve(true); + } catch (Exception e) { + promise.reject(ERROR_UNKNOWN, e); + } } /** @@ -919,8 +1014,12 @@ public void isOffline(Promise promise) { */ @ReactMethod public void setOnline(Promise promise) { - ldClient.setOnline(); - promise.resolve(true); + try { + ldClient.setOnline(); + promise.resolve(true); + } catch (Exception e) { + promise.reject(ERROR_UNKNOWN, e); + } } /** @@ -931,6 +1030,11 @@ public void setOnline(Promise promise) { */ @ReactMethod public void isInitialized(Promise promise) { + if (ldClient == null) { + promise.resolve(false); + return; + } + try { boolean result = ldClient.isInitialized(); promise.resolve(result); @@ -944,7 +1048,11 @@ public void isInitialized(Promise promise) { */ @ReactMethod public void flush() { - ldClient.flush(); + try { + ldClient.flush(); + } catch (Exception e) { + Timber.w(e); + } } /** @@ -981,32 +1089,51 @@ public void run() { ldClient.identify(userBuilder.build()).get(); promise.resolve(null); } catch (InterruptedException e) { - e.printStackTrace(); + Timber.w(e); promise.reject(ERROR_IDENTIFY, "Identify Interrupted"); } catch (ExecutionException e) { - e.printStackTrace(); + Timber.w(e); promise.reject(ERROR_IDENTIFY, "Exception while executing identify"); + } catch (Exception e) { + Timber.w(e); + promise.reject(ERROR_UNKNOWN, e); } } }); - background.run(); + background.start(); } @ReactMethod - public void isDisableBackgroundPolling(Promise promise) { + public void getConnectionMode(Promise promise) { try { - boolean result = ldClient.isDisableBackgroundPolling(); - promise.resolve(result); + promise.resolve(ldClient.getConnectionInformation().getConnectionMode().name()); } catch (Exception e) { promise.reject(ERROR_UNKNOWN, e); } } @ReactMethod - public void getConnectionInformation(Promise promise) { + public void getLastSuccessfulConnection(Promise promise) { try { - ConnectionInformation result = ldClient.getConnectionInformation(); - promise.resolve(result); + promise.resolve(ldClient.getConnectionInformation().getLastSuccessfulConnection().intValue()); + } catch (Exception e) { + promise.reject(ERROR_UNKNOWN, e); + } + } + + @ReactMethod + public void getLastFailedConnection(Promise promise) { + try { + promise.resolve(ldClient.getConnectionInformation().getLastFailedConnection().intValue()); + } catch (Exception e) { + promise.reject(ERROR_UNKNOWN, e); + } + } + + @ReactMethod + public void getLastFailure(Promise promise) { + try { + promise.resolve(ldClient.getConnectionInformation().getLastFailure().getFailureType().name()); } catch (Exception e) { promise.reject(ERROR_UNKNOWN, e); } @@ -1026,15 +1153,23 @@ public void onFeatureFlagChange(String flagKey) { } }; - ldClient.registerFeatureFlagListener(flagKey, listener); - listeners.put(flagKey, listener); + try { + ldClient.registerFeatureFlagListener(flagKey, listener); + listeners.put(flagKey, listener); + } catch (Exception e) { + Timber.w(e); + } } @ReactMethod public void unregisterFeatureFlagListener(String flagKey) { - if (listeners.containsKey(flagKey)) { - ldClient.unregisterFeatureFlagListener(flagKey, listeners.get(flagKey)); - listeners.remove(flagKey); + try { + if (listeners.containsKey(flagKey)) { + ldClient.unregisterFeatureFlagListener(flagKey, listeners.get(flagKey)); + listeners.remove(flagKey); + } + } catch (Exception e) { + Timber.w(e); } } @@ -1055,15 +1190,23 @@ public void onConnectionModeChanged(ConnectionInformation connectionInfo) { public void onInternalFailure(LDFailure ldFailure) {} }; - ldClient.registerStatusListener(listener); - connectionModeListeners.put(listenerId, listener); + try { + ldClient.registerStatusListener(listener); + connectionModeListeners.put(listenerId, listener); + } catch (Exception e) { + Timber.w(e); + } } @ReactMethod public void unregisterCurrentConnectionModeListener(String listenerId) { - if (connectionModeListeners.containsKey(listenerId)) { - ldClient.unregisterStatusListener(connectionModeListeners.get(listenerId)); - connectionModeListeners.remove(listenerId); + try { + if (connectionModeListeners.containsKey(listenerId)) { + ldClient.unregisterStatusListener(connectionModeListeners.get(listenerId)); + connectionModeListeners.remove(listenerId); + } + } catch (Exception e) { + Timber.w(e); } } @@ -1081,15 +1224,23 @@ public void onChange(List flagKeys) { } }; - ldClient.registerAllFlagsListener(listener); - allFlagsListeners.put(listenerId, listener); + try { + ldClient.registerAllFlagsListener(listener); + allFlagsListeners.put(listenerId, listener); + } catch (Exception e) { + Timber.w(e); + } } @ReactMethod public void unregisterAllFlagsListener(String listenerId) { - if (allFlagsListeners.containsKey(listenerId)) { - ldClient.unregisterAllFlagsListener(allFlagsListeners.get(listenerId)); - allFlagsListeners.remove(listenerId); + try { + if (allFlagsListeners.containsKey(listenerId)) { + ldClient.unregisterAllFlagsListener(allFlagsListeners.get(listenerId)); + allFlagsListeners.remove(listenerId); + } + } catch (Exception e) { + Timber.w(e); } } diff --git a/index.d.ts b/index.d.ts index aef0a23..9c01977 100644 --- a/index.d.ts +++ b/index.d.ts @@ -11,7 +11,7 @@ declare module 'launchdarkly-react-native-client-sdk' { /** * Configuration options for the LaunchDarkly React Native SDK. */ - export type LDClientConfig = { + export type LDConfig = { /** * The mobile SDK key associated with your LaunchDarkly environment. * @@ -21,11 +21,11 @@ declare module 'launchdarkly-react-native-client-sdk' { mobileKey: string; /** - * The base URI for the LaunchDarkly server. + * The base URI for the LaunchDarkly polling server. * * Most users should use the default value. */ - baseUri?: string; + pollUri?: string; /** * The base URI for the LaunchDarkly streaming server. @@ -145,12 +145,34 @@ declare module 'launchdarkly-react-native-client-sdk' { * such information is not sent unless you set this option to true. */ evaluationReasons?: boolean; + + /** + * Setting for the maximum number of locally cached users. Default is 5 users. + */ + maxCachedUsers?: number; + + /** + * Setting for whether sending diagnostic data about the SDK is disabled. The default is false. + */ + diagnosticOptOut?: boolean; + + /** + * The time interval between sending periodic diagnostic data. The default is 900000 (15 minutes). + */ + diagnosticRecordingIntervalMillis?: number; + + /** + * Whether to treat all user attributes as private for event reporting for all users. + * The SDK will not include private attribute values in analytics events, but private attribute names will be sent. + * The default is false. + */ + allUserAttributesPrivate?: boolean; }; /** * A LaunchDarkly user object. */ - export type LDUserConfig = { + export type LDUser = { /** * A unique string identifying a user. @@ -201,6 +223,16 @@ declare module 'launchdarkly-react-native-client-sdk' { * Any additional attributes associated with the user. */ custom?: { [key: string]: any }; + + /** + * The IP address associated with the user. + */ + ip?: string; + + /** + * The avatar associated with the user. + */ + avatar?: string; }; /** @@ -284,6 +316,105 @@ declare module 'launchdarkly-react-native-client-sdk' { */ ERROR = 'ERROR', } + + /** + * Describes what state of connection to LaunchDarkly the SDK is in. + */ + export enum LDConnectionMode { + /** + * The SDK is either connected to the flag stream, or is actively attempting to acquire a connection. + */ + STREAMING = 'STREAMING', + + /** + * The SDK was configured with streaming disabled, and is in foreground polling mode. + */ + POLLING = 'POLLING', + + /** + * (Android specific enum value) The SDK has detected the application is in the background and has transitioned to + * battery conscious background polling. + */ + BACKGROUND_POLLING = 'BACKGROUND_POLLING', + + /** + * (Android specific enum value) The SDK was configured with background polling disabled. The SDK has detected + * the application is in the background and is not attempting to update the flag cache. + */ + BACKGROUND_DISABLED = 'BACKGROUND_DISABLED', + + /** + * The SDK has detected that the mobile device does not have an active network connection so has ceased flag update + * attempts until the network status changes. + */ + OFFLINE = 'OFFLINE', + + /** + * (Android specific enum value) The SDK has been explicitly set offline, either in the initial configuration, + * by setOffline(), or as a result of failed authentication to LaunchDarkly. + * The SDK will stay offline unless setOnline() is called. + */ + SET_OFFLINE = 'SET_OFFLINE', + + /** + * (Android specific enum value) The shutdown state indicates the SDK has been permanently shutdown as a result of + * a call to close(). + */ + SHUTDOWN = 'SHUTDOWN', + + /** + * (iOS specific enum value) The SDK is attempting to connect to LaunchDarkly by streaming. + */ + ESTABLISHING_STREAMING_CONNECTION = 'ESTABLISHING_STREAMING_CONNECTION', + } + + /** + * Describes why a connection request to LaunchDarkly failed. + */ + export enum LDFailureReason { + /** + * This indicates when no error has been recorded. + */ + NONE = 'NONE', + + /** + * This indicates when there is an internal error in the stream request. + */ + UKNOWN_ERROR ='UNKNOWN_ERROR', + + /** + * (iOS specific enum value) This indicates when an incorrect mobile key is provided. + */ + UNAUTHORIZED = 'UNAUTHORIZED', + + /** + * (iOS specific enum value) This indicates when an error with an HTTP error code is present. + */ + HTTP_ERROR = 'HTTP_ERROR', + + /** + * (Android specific enum value) This indicates the LDFailure is an instance of LDInvalidResponseCodeFailure. + * See Android documentation for more details. + */ + UNEXPECTED_RESPONSE_CODE = 'UNEXPECTED_RESPONSE_CODE', + + /** + * (Android specific enum value) An event was received through the stream was had an unknown event name. + * This could indicate a newer SDK is available if new event types have become available through the + * flag stream since the SDKs release. + */ + UNEXPECTED_STREAM_ELEMENT_TYPE = 'UNEXPECTED_STREAM_ELEMENT_TYPE', + + /** + * (Android specific enum value) A network request for polling, or the EventSource stream reported a failure. + */ + NETWORK_FAILURE = 'NETWORK_FAILURE', + + /** + * (Android specific enum value) A response body received either through polling or streaming was unable to be parsed. + */ + INVALID_RESPONSE_BODY = 'INVALID_RESPONSE_BODY', + } /** * The flag is off and therefore returned its configured off value. @@ -408,184 +539,196 @@ declare module 'launchdarkly-react-native-client-sdk' { export default class LDClient { constructor(); + /** + * Returns the SDK version. + * + * @returns + * A string containing the SDK version. + */ + getVersion(): string; + /** * Initialize the SDK to work with the specified client configuration options and on behalf of the specified user. + * Will block for a number of seconds represented until flags are received from LaunchDarkly if the timeout parameter + * is passed. * * This should only be called once at application start time. * * @param config * the client configuration options - * @param userConfig + * @param user * the user + * @param timeout + * (Optional) A number representing how long to wait for flags */ - configure(config: LDClientConfig, userConfig: LDUserConfig): Promise; + configure(config: LDConfig, user: LDUser, timeout?: number): Promise; /** * Determines the variation of a boolean feature flag for the current user. * * @param flagKey * The unique key of the feature flag. - * @param fallback + * @param defaultValue * The default value of the flag, to be used if the value is not available from LaunchDarkly. * @returns * A promise containing the flag's value. */ - boolVariation(flagKey: string, fallback: boolean): Promise; + boolVariation(flagKey: string, defaultValue: boolean): Promise; /** * Determines the variation of an integer feature flag for the current user. * * @param flagKey * The unique key of the feature flag. - * @param fallback + * @param defaultValue * The default value of the flag, to be used if the value is not available from LaunchDarkly. * @returns * A promise containing the flag's value. */ - intVariation(flagKey: string, fallback: number): Promise; + intVariation(flagKey: string, defaultValue: number): Promise; /** * Determines the variation of a floating-point feature flag for the current user. * * @param flagKey * The unique key of the feature flag. - * @param fallback + * @param defaultValue * The default value of the flag, to be used if the value is not available from LaunchDarkly. * @returns * A promise containing the flag's value. */ - floatVariation(flagKey: string, fallback: number): Promise; + floatVariation(flagKey: string, defaultValue: number): Promise; /** * Determines the variation of a string feature flag for the current user. * * @param flagKey * The unique key of the feature flag. - * @param fallback + * @param defaultValue * The default value of the flag, to be used if the value is not available from LaunchDarkly. * @returns * A promise containing the flag's value. */ - stringVariation(flagKey: string, fallback: string): Promise; + stringVariation(flagKey: string, defaultValue: string): Promise; /** * Determines the variation of a JSON feature flag for the current user. * * @param flagKey * The unique key of the feature flag. - * @param fallback + * @param defaultValue * The default value of the flag, to be used if the value is not available from LaunchDarkly. * @returns * A promise containing the flag's value. */ jsonVariation( flagKey: string, - fallback: Record, + defaultValue: Record, ): Promise>; /** * Determines the variation of a boolean feature flag for a user, along with information about how it was * calculated. * - * Note that this will only work if you have set `evaluationReasons` to true in [[LDClientConfig]]. + * Note that this will only work if you have set `evaluationReasons` to true in [[LDConfig]]. * Otherwise, the `reason` property of the result will be null. * * For more information, see the [SDK reference guide](https://docs.launchdarkly.com/sdk/concepts/evaluation-reasons). * * @param flagKey * The unique key of the feature flag. - * @param fallback + * @param defaultValue * The default value of the flag, to be used if the value is not available from LaunchDarkly. * @returns * A promise containing an [[LDEvaluationDetail]] object containing the value and explanation. */ boolVariationDetail( flagKey: string, - fallback: boolean, + defaultValue: boolean, ): Promise>; /** * Determines the variation of an integer feature flag for a user, along with information about how it was * calculated. * - * Note that this will only work if you have set `evaluationReasons` to true in [[LDClientConfig]]. + * Note that this will only work if you have set `evaluationReasons` to true in [[LDConfig]]. * Otherwise, the `reason` property of the result will be null. * * For more information, see the [SDK reference guide](https://docs.launchdarkly.com/sdk/concepts/evaluation-reasons). * * @param flagKey * The unique key of the feature flag. - * @param fallback + * @param defaultValue * The default value of the flag, to be used if the value is not available from LaunchDarkly. * @returns * A promise containing an [[LDEvaluationDetail]] object containing the value and explanation. */ intVariationDetail( flagKey: string, - fallback: number, + defaultValue: number, ): Promise>; /** * Determines the variation of a floating-point feature flag for a user, along with information about how it was * calculated. * - * Note that this will only work if you have set `evaluationReasons` to true in [[LDClientConfig]]. + * Note that this will only work if you have set `evaluationReasons` to true in [[LDConfig]]. * Otherwise, the `reason` property of the result will be null. * * For more information, see the [SDK reference guide](https://docs.launchdarkly.com/sdk/concepts/evaluation-reasons). * * @param flagKey * The unique key of the feature flag. - * @param fallback + * @param defaultValue * The default value of the flag, to be used if the value is not available from LaunchDarkly. * @returns * A promise containing an [[LDEvaluationDetail]] object containing the value and explanation. */ floatVariationDetail( flagKey: string, - fallback: number, + defaultValue: number, ): Promise>; /** * Determines the variation of a string feature flag for a user, along with information about how it was * calculated. * - * Note that this will only work if you have set `evaluationReasons` to true in [[LDClientConfig]]. + * Note that this will only work if you have set `evaluationReasons` to true in [[LDConfig]]. * Otherwise, the `reason` property of the result will be null. * * For more information, see the [SDK reference guide](https://docs.launchdarkly.com/sdk/concepts/evaluation-reasons). * * @param flagKey * The unique key of the feature flag. - * @param fallback + * @param defaultValue * The default value of the flag, to be used if the value is not available from LaunchDarkly. * @returns * A promise containing an [[LDEvaluationDetail]] object containing the value and explanation. */ stringVariationDetail( flagKey: string, - fallback: string, + defaultValue: string, ): Promise>; /** * Determines the variation of a JSON feature flag for a user, along with information about how it was * calculated. * - * Note that this will only work if you have set `evaluationReasons` to true in [[LDClientConfig]]. + * Note that this will only work if you have set `evaluationReasons` to true in [[LDConfig]]. * Otherwise, the `reason` property of the result will be null. * * For more information, see the [SDK reference guide](https://docs.launchdarkly.com/sdk/concepts/evaluation-reasons). * * @param flagKey * The unique key of the feature flag. - * @param fallback + * @param defaultValue * The default value of the flag, to be used if the value is not available from LaunchDarkly. * @returns * A promise containing an [[LDEvaluationDetail]] object containing the value and explanation. */ jsonVariationDetail( flagKey: string, - fallback: Record, + defaultValue: Record, ): Promise>>; /** @@ -593,6 +736,7 @@ declare module 'launchdarkly-react-native-client-sdk' { * * @returns * A promise containing an object in which each key is a feature flag key and each value is the flag value. + * The promise will be rejected if the SDK has not yet completed initialization. * Note that there is no way to specify a default value for each flag as there is with the * `*Variation` methods, so any flag that cannot be evaluated will have a null value. */ @@ -612,7 +756,7 @@ declare module 'launchdarkly-react-native-client-sdk' { /** * Checks whether the client has been put into offline mode. This is true only if [[setOffline]] - * was called, or if the configuration had [[LDClientConfig.offline]] set to true, + * was called, or if the configuration had [[LDConfig.offline]] set to true, * not if the client is simply offline due to a loss of network connectivity. * * @returns @@ -655,20 +799,12 @@ declare module 'launchdarkly-react-native-client-sdk' { * A promise contianing true if the client is initialized or offline */ isInitialized(): Promise; - - /** - * Checks whether the `disableBackgroundUpdating` property of [[LDClientConfig]] was set to true. - * - * @returns - * A promise containing true if background polling is disabled - */ - isDisableBackgroundPolling(): Promise; /** * Flushes all pending analytics events. * * Normally, batches of events are delivered in the background at intervals determined by the - * `eventsFlushIntervalMillis` property of [[LDClientConfig]]. Calling `flush` triggers an + * `eventsFlushIntervalMillis` property of [[LDConfig]]. Calling `flush` triggers an * immediate delivery. */ flush(): void; @@ -683,12 +819,12 @@ declare module 'launchdarkly-react-native-client-sdk' { /** * Sets the current user, retrieves flags for that user, then sends an Identify Event to LaunchDarkly. * - * @param userConfig + * @param user * The user for evaluation and event reporting * @returns * A promise indicating when this operation is complete (meaning that flags are ready for evaluation). */ - identify(userConfig: LDUserConfig): Promise; + identify(user: LDUser): Promise; /** * Registers a callback to be called when the flag with key `flagKey` changes from its current value. @@ -715,14 +851,43 @@ declare module 'launchdarkly-react-native-client-sdk' { flagKey: string, callback: (flagKey: string) => void, ): void; - + + /** + * Returns the current state of the connection to LaunchDarkly. + * + * @returns + * A promise containing a LDConnectionMode enum value representing the status of the connection to LaunchDarkly. + */ + getConnectionMode(): Promise; + + /** + * Returns the most recent successful flag cache update in millis from the epoch + * or null if flags have never been retrieved. + * + * @returns + * A promise containing a number representing the status of the connection to LaunchDarkly, + * or null if a successful connection has yet to be established. + */ + getLastSuccessfulConnection(): Promise; + + /** + * Most recent unsuccessful flag cache update attempt in millis from the epoch + * or null if flag update has never been attempted. + * + * @returns + * A promise containing a number representing the status of the connection to LaunchDarkly, + * or null if a failed connection has yet to occur. + */ + getLastFailedConnection(): Promise; + /** - * Gets an object from the client representing the current state of the client's connection. + * Returns the most recent connection failure reason or null. * * @returns - * A promise containing an object representing the status of the connection to LaunchDarkly. + * A promise containing a LDFailureReason enum value representing the reason for the most recently failed + * connection to LaunchDarkly, or null if a failed connection has yet to occur. */ - getConnectionInformation(): Promise; + getLastFailure(): Promise; /** * Registers a callback to be called on connection status updates. diff --git a/index.js b/index.js index 94b110e..32464cd 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,5 @@ import { NativeModules, NativeEventEmitter, Platform } from 'react-native'; +import { version } from './package.json'; let LaunchdarklyReactNativeClient = NativeModules.LaunchdarklyReactNativeClient; @@ -13,110 +14,124 @@ export default class LDClient { this.eventEmitter.addListener(LaunchdarklyReactNativeClient.CONNECTION_MODE_PREFIX, body => this._connectionModeUpdateListener(body)); } - configure(config, userConfig) { + getVersion() { + return String(version); + } + + configure(config, user, timeout) { + if (this.isInitialized() == true) { + Promise.reject('LaunchDarkly SDK already initialized'); + } const configWithOverriddenDefaults = Object.assign({ backgroundPollingIntervalMillis: 3600000, // the iOS SDK defaults this to 900000 - disableBackgroundUpdating: false // the iOS SDK defaults this to true + disableBackgroundUpdating: false, // the iOS SDK defaults this to true + pollUri: 'https://clientsdk.launchdarkly.com', + wrapperName: 'react-native-client-sdk', + wrapperVersion: this.getVersion() }, config); - return LaunchdarklyReactNativeClient.configure(configWithOverriddenDefaults, this._addUserOverrides(userConfig)); + if (timeout == undefined) { + return LaunchdarklyReactNativeClient.configure(configWithOverriddenDefaults, this._addUserOverrides(user)); + } else { + return LaunchdarklyReactNativeClient.configureWithTimeout(configWithOverriddenDefaults, this._addUserOverrides(user), timeout); + } } - boolVariation(flagKey, fallback) { - if (fallback == undefined) { + boolVariation(flagKey, defaultValue) { + if (defaultValue == undefined) { return LaunchdarklyReactNativeClient.boolVariation(flagKey); } else { - return LaunchdarklyReactNativeClient.boolVariationFallback(flagKey, fallback); + return LaunchdarklyReactNativeClient.boolVariationDefaultValue(flagKey, defaultValue); } } - intVariation(flagKey, fallback) { - if (fallback == undefined) { + intVariation(flagKey, defaultValue) { + if (defaultValue == undefined) { return LaunchdarklyReactNativeClient.intVariation(flagKey); } else { - return LaunchdarklyReactNativeClient.intVariationFallback(flagKey, fallback); + return LaunchdarklyReactNativeClient.intVariationDefaultValue(flagKey, defaultValue); } } - floatVariation(flagKey, fallback) { - if (fallback == undefined) { + floatVariation(flagKey, defaultValue) { + if (defaultValue == undefined) { return LaunchdarklyReactNativeClient.floatVariation(flagKey); } else { - return LaunchdarklyReactNativeClient.floatVariationFallback(flagKey, fallback); + return LaunchdarklyReactNativeClient.floatVariationDefaultValue(flagKey, defaultValue); } } - stringVariation(flagKey, fallback) { - if (fallback == undefined) { + stringVariation(flagKey, defaultValue) { + if (defaultValue == undefined) { return LaunchdarklyReactNativeClient.stringVariation(flagKey); } else { - return LaunchdarklyReactNativeClient.stringVariationFallback(flagKey, fallback); + return LaunchdarklyReactNativeClient.stringVariationDefaultValue(flagKey, defaultValue); } } - jsonVariation(flagKey, fallback) { - if (fallback == undefined) { + jsonVariation(flagKey, defaultValue) { + if (defaultValue == undefined) { return LaunchdarklyReactNativeClient.jsonVariationNone(flagKey); - } else if (typeof fallback === 'number') { - return LaunchdarklyReactNativeClient.jsonVariationNumber(flagKey, fallback); - } else if (typeof fallback === 'boolean') { - return LaunchdarklyReactNativeClient.jsonVariationBool(flagKey, fallback); - } else if (typeof fallback === 'string') { - return LaunchdarklyReactNativeClient.jsonVariationString(flagKey, fallback); - } else if (Array.isArray(fallback)) { - return LaunchdarklyReactNativeClient.jsonVariationArray(flagKey, fallback); + } else if (typeof defaultValue === 'number') { + return LaunchdarklyReactNativeClient.jsonVariationNumber(flagKey, defaultValue); + } else if (typeof defaultValue === 'boolean') { + return LaunchdarklyReactNativeClient.jsonVariationBool(flagKey, defaultValue); + } else if (typeof defaultValue === 'string') { + return LaunchdarklyReactNativeClient.jsonVariationString(flagKey, defaultValue); + } else if (Array.isArray(defaultValue)) { + return LaunchdarklyReactNativeClient.jsonVariationArray(flagKey, defaultValue); } else { // Should be an object - return LaunchdarklyReactNativeClient.jsonVariationObject(flagKey, fallback); + return LaunchdarklyReactNativeClient.jsonVariationObject(flagKey, defaultValue); } } - boolVariationDetail(flagKey, fallback) { - if (fallback == undefined) { + boolVariationDetail(flagKey, defaultValue) { + if (defaultValue == undefined) { return LaunchdarklyReactNativeClient.boolVariationDetail(flagKey); } else { - return LaunchdarklyReactNativeClient.boolVariationDetailFallback(flagKey, fallback); + return LaunchdarklyReactNativeClient.boolVariationDetailDefaultValue(flagKey, defaultValue); } } - intVariationDetail(flagKey, fallback) { - if (fallback == undefined) { + intVariationDetail(flagKey, defaultValue) { + if (defaultValue == undefined) { return LaunchdarklyReactNativeClient.intVariationDetail(flagKey); } else { - return LaunchdarklyReactNativeClient.intVariationDetailFallback(flagKey, fallback); + return LaunchdarklyReactNativeClient.intVariationDetailDefaultValue(flagKey, defaultValue); } } - floatVariationDetail(flagKey, fallback) { - if (fallback == undefined) { + floatVariationDetail(flagKey, defaultValue) { + if (defaultValue == undefined) { return LaunchdarklyReactNativeClient.floatVariationDetail(flagKey); } else { - return LaunchdarklyReactNativeClient.floatVariationDetailFallback(flagKey, fallback); + return LaunchdarklyReactNativeClient.floatVariationDetailDefaultValue(flagKey, defaultValue); } } - stringVariationDetail(flagKey, fallback) { - if (fallback == undefined) { + stringVariationDetail(flagKey, defaultValue) { + if (defaultValue == undefined) { return LaunchdarklyReactNativeClient.stringVariationDetail(flagKey); } else { - return LaunchdarklyReactNativeClient.stringVariationDetailFallback(flagKey, fallback); + return LaunchdarklyReactNativeClient.stringVariationDetailDefaultValue(flagKey, defaultValue); } } - jsonVariationDetail(flagKey, fallback) { - if (fallback == undefined) { + jsonVariationDetail(flagKey, defaultValue) { + if (defaultValue == undefined) { return LaunchdarklyReactNativeClient.jsonVariatioDetailNone(flagKey); - } else if (typeof fallback === 'number') { - return LaunchdarklyReactNativeClient.jsonVariationDetailNumber(flagKey, fallback); - } else if (typeof fallback === 'boolean') { - return LaunchdarklyReactNativeClient.jsonVariationDetailBool(flagKey, fallback); - } else if (typeof fallback === 'string') { - return LaunchdarklyReactNativeClient.jsonVariationDetailString(flagKey, fallback); - } else if (Array.isArray(fallback)) { - return LaunchdarklyReactNativeClient.jsonVariationDetailArray(flagKey, fallback); + } else if (typeof defaultValue === 'number') { + return LaunchdarklyReactNativeClient.jsonVariationDetailNumber(flagKey, defaultValue); + } else if (typeof defaultValue === 'boolean') { + return LaunchdarklyReactNativeClient.jsonVariationDetailBool(flagKey, defaultValue); + } else if (typeof defaultValue === 'string') { + return LaunchdarklyReactNativeClient.jsonVariationDetailString(flagKey, defaultValue); + } else if (Array.isArray(defaultValue)) { + return LaunchdarklyReactNativeClient.jsonVariationDetailArray(flagKey, defaultValue); } else { // Should be an object - return LaunchdarklyReactNativeClient.jsonVariationDetailObject(flagKey, fallback); + return LaunchdarklyReactNativeClient.jsonVariationDetailObject(flagKey, defaultValue); } } @@ -171,11 +186,7 @@ export default class LDClient { } isInitialized() { - if(Platform.OS === 'android') { - return LaunchdarklyReactNativeClient.isInitialized(); - } else { - return Promise.reject("Function is not available on this platform"); - } + return LaunchdarklyReactNativeClient.isInitialized(); } flush() { @@ -186,18 +197,14 @@ export default class LDClient { LaunchdarklyReactNativeClient.close(); } - identify(userConfig) { - return LaunchdarklyReactNativeClient.identify(this._addUserOverrides(userConfig)); + identify(user) { + return LaunchdarklyReactNativeClient.identify(this._addUserOverrides(user)); } - _addUserOverrides(userConfig) { + _addUserOverrides(user) { return Object.assign({ anonymous: false // the iOS SDK defaults this to true - }, userConfig); - } - - isDisableBackgroundPolling() { - return LaunchdarklyReactNativeClient.isDisableBackgroundPolling(); + }, user); } _flagUpdateListener(changedFlag) { @@ -253,10 +260,6 @@ export default class LDClient { } } - getConnectionInformation() { - return LaunchdarklyReactNativeClient.getConnectionInformation(); - } - registerCurrentConnectionModeListener(listenerId, callback) { if (typeof callback !== "function") { return; @@ -290,4 +293,20 @@ export default class LDClient { LaunchdarklyReactNativeClient.unregisterAllFlagsListener(listenerId); delete this.allFlagsListeners[listenerId]; } + + getConnectionMode() { + return LaunchdarklyReactNativeClient.getConnectionMode(); + } + + getLastSuccessfulConnection() { + return LaunchdarklyReactNativeClient.getLastSuccessfulConnection(); + } + + getLastFailedConnection() { + return LaunchdarklyReactNativeClient.getLastFailedConnection(); + } + + getLastFailure() { + return LaunchdarklyReactNativeClient.getLastFailure(); + } } diff --git a/ios/LaunchdarklyReactNativeClient.podspec b/ios/LaunchdarklyReactNativeClient.podspec index 269706b..2310b7c 100644 --- a/ios/LaunchdarklyReactNativeClient.podspec +++ b/ios/LaunchdarklyReactNativeClient.podspec @@ -16,6 +16,6 @@ Pod::Spec.new do |s| s.swift_version = "5.0" s.dependency "React" - s.dependency "LaunchDarkly", "4.5.0" + s.dependency "LaunchDarkly", "5.4.0" end diff --git a/ios/LaunchdarklyReactNativeClient.swift b/ios/LaunchdarklyReactNativeClient.swift index 92daacb..f3966b8 100644 --- a/ios/LaunchdarklyReactNativeClient.swift +++ b/ios/LaunchdarklyReactNativeClient.swift @@ -24,14 +24,32 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { return false } - @objc func configure(_ config: NSDictionary, userConfig: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + @objc func configure(_ config: NSDictionary, user: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void { + internalConfigure(config: config, user: user, timeout: nil, resolve: resolve, reject: reject) + } + + @objc func configureWithTimeout(_ config: NSDictionary, user: NSDictionary, timeout: Int, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void { + internalConfigure(config: config, user: user, timeout: timeout, resolve: resolve, reject: reject) + } + private func internalConfigure(config: NSDictionary, user: NSDictionary, timeout: Int?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void { let config = configBuild(config: config) - let user = userBuild(userConfig: userConfig) + let user = userBuild(userDict: user) if config != nil && user != nil { - LDClient.shared.startCompleteWhenFlagsReceived(config: config!, user: user, completion: {() -> Void in + if let timeoutUnwrapped = timeout { + let startWaitSeconds: TimeInterval = Double(timeoutUnwrapped) + LDClient.start(config: config!, user: user, startWaitSeconds: startWaitSeconds) { timedOut in + if timedOut { + reject(self.ERROR_INIT, "SDK initialization timed out", nil) + } else { + resolve(nil) + } + } + } else { + LDClient.start(config: config!, user: user, completion: {() -> Void in resolve(nil)}) + } } } @@ -45,8 +63,8 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { let safeKey = mobileKey as! String var ldConfig = LDConfig(mobileKey: safeKey) - if config["baseUri"] != nil { - ldConfig.baseUrl = URL.init(string: config["baseUri"] as! String)! + if config["pollUri"] != nil { + ldConfig.baseUrl = URL.init(string: config["pollUri"] as! String)! } if config["eventsUri"] != nil { @@ -100,327 +118,356 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { if config["evaluationReasons"] != nil { ldConfig.evaluationReasons = config["evaluationReasons"] as! Bool } + + ldConfig.wrapperName = config["wrapperName"] as? String + ldConfig.wrapperVersion = config["wrapperVersion"] as? String + + if config["maxCachedUsers"] != nil { + ldConfig.maxCachedUsers = config["maxCachedUsers"] as! Int + } + + if config["diagnosticOptOut"] != nil { + ldConfig.diagnosticOptOut = config["diagnosticOptOut"] as! Bool + } + + if config["diagnosticRecordingIntervalMillis"] != nil { + ldConfig.diagnosticRecordingInterval = TimeInterval(config["diagnosticRecordingIntervalMillis"] as! Float / 1000) + } + + if config["allUserAttributesPrivate"] != nil { + ldConfig.allUserAttributesPrivate = config["allUserAttributesPrivate"] as! Bool + } + + ldConfig.autoAliasingOptOut = true return ldConfig } - private func userBuild(userConfig: NSDictionary) -> LDUser? { + private func userBuild(userDict: NSDictionary) -> LDUser? { var user = LDUser() - user.key = userConfig["key"] as! String + user.key = userDict["key"] as! String - if userConfig["name"] != nil { - user.name = userConfig["name"] as? String + if userDict["name"] != nil { + user.name = userDict["name"] as? String } - if userConfig["firstName"] != nil { - user.firstName = userConfig["firstName"] as? String + if userDict["firstName"] != nil { + user.firstName = userDict["firstName"] as? String } - if userConfig["lastName"] != nil { - user.lastName = userConfig["lastName"] as? String + if userDict["lastName"] != nil { + user.lastName = userDict["lastName"] as? String } - if userConfig["email"] != nil { - user.email = userConfig["email"] as? String + if userDict["email"] != nil { + user.email = userDict["email"] as? String } - if userConfig["anonymous"] != nil { - user.isAnonymous = userConfig["anonymous"] as! Bool + if userDict["anonymous"] != nil { + user.isAnonymous = userDict["anonymous"] as! Bool } - if userConfig["country"] != nil { - user.country = userConfig["country"] as? String + if userDict["country"] != nil { + user.country = userDict["country"] as? String + } + + if userDict["ip"] != nil { + user.ipAddress = userDict["ip"] as? String + } + + if userDict["avatar"] != nil { + user.avatar = userDict["avatar"] as? String } - if userConfig["privateAttributeNames"] != nil { - user.privateAttributes = userConfig["privateAttributeNames"] as? [String] + if userDict["privateAttributeNames"] != nil { + user.privateAttributes = userDict["privateAttributeNames"] as? [String] } - if let customAttributes = userConfig["custom"] as! [String: Any]? { + if let customAttributes = userDict["custom"] as! [String: Any]? { user.custom = customAttributes } return user } - @objc func boolVariationFallback(_ flagKey: String, fallback: ObjCBool, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.shared.variation(forKey: flagKey, fallback: fallback.boolValue) as Bool) + @objc func boolVariationDefaultValue(_ flagKey: String, defaultValue: ObjCBool, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + resolve(LDClient.get()!.variation(forKey: flagKey, defaultValue: defaultValue.boolValue) as Bool) } - @objc func intVariationFallback(_ flagKey: String, fallback: Int, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.shared.variation(forKey: flagKey, fallback: fallback) as Int) + @objc func intVariationDefaultValue(_ flagKey: String, defaultValue: Int, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + resolve(LDClient.get()!.variation(forKey: flagKey, defaultValue: defaultValue) as Int) } - @objc func floatVariationFallback(_ flagKey: String, fallback: CGFloat, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.shared.variation(forKey: flagKey, fallback: Double(fallback)) as Double) + @objc func floatVariationDefaultValue(_ flagKey: String, defaultValue: CGFloat, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + resolve(LDClient.get()!.variation(forKey: flagKey, defaultValue: Double(defaultValue)) as Double) } - @objc func stringVariationFallback(_ flagKey: String, fallback: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.shared.variation(forKey: flagKey, fallback: fallback) as String) + @objc func stringVariationDefaultValue(_ flagKey: String, defaultValue: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + resolve(LDClient.get()!.variation(forKey: flagKey, defaultValue: defaultValue) as String) } @objc func boolVariation(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let boolFlagValue: Bool? = LDClient.shared.variation(forKey: flagKey) + let boolFlagValue: Bool? = LDClient.get()!.variation(forKey: flagKey) resolve(boolFlagValue) } @objc func intVariation(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let intFlagValue: Int? = LDClient.shared.variation(forKey: flagKey) + let intFlagValue: Int? = LDClient.get()!.variation(forKey: flagKey) resolve(intFlagValue) } @objc func floatVariation(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let floatFlagValue: Double? = LDClient.shared.variation(forKey: flagKey) + let floatFlagValue: Double? = LDClient.get()!.variation(forKey: flagKey) resolve(floatFlagValue) } @objc func stringVariation(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let stringFlagValue: String? = LDClient.shared.variation(forKey: flagKey) + let stringFlagValue: String? = LDClient.get()!.variation(forKey: flagKey) resolve(stringFlagValue) } @objc func jsonVariationNone(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let jsonFlagValue: Dictionary? = LDClient.shared.variation(forKey: flagKey) + let jsonFlagValue: Dictionary? = LDClient.get()!.variation(forKey: flagKey) resolve(jsonFlagValue) } - @objc func jsonVariationNumber(_ flagKey: String, fallback: Double, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.shared.variation(forKey: flagKey, fallback: fallback) as Double) + @objc func jsonVariationNumber(_ flagKey: String, defaultValue: Double, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + resolve(LDClient.get()!.variation(forKey: flagKey, defaultValue: defaultValue) as Double) } - @objc func jsonVariationBool(_ flagKey: String, fallback: Bool, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.shared.variation(forKey: flagKey, fallback: fallback) as Bool) + @objc func jsonVariationBool(_ flagKey: String, defaultValue: Bool, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + resolve(LDClient.get()!.variation(forKey: flagKey, defaultValue: defaultValue) as Bool) } - @objc func jsonVariationString(_ flagKey: String, fallback: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.shared.variation(forKey: flagKey, fallback: fallback) as String) + @objc func jsonVariationString(_ flagKey: String, defaultValue: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + resolve(LDClient.get()!.variation(forKey: flagKey, defaultValue: defaultValue) as String) } - @objc func jsonVariationArray(_ flagKey: String, fallback: Array, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.shared.variation(forKey: flagKey, fallback: fallback) as Array) + @objc func jsonVariationArray(_ flagKey: String, defaultValue: Array, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + resolve(LDClient.get()!.variation(forKey: flagKey, defaultValue: defaultValue) as Array) } - @objc func jsonVariationObject(_ flagKey: String, fallback: NSDictionary, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.shared.variation(forKey: flagKey, fallback: fallback.swiftDictionary) as NSDictionary) + @objc func jsonVariationObject(_ flagKey: String, defaultValue: NSDictionary, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + resolve(LDClient.get()!.variation(forKey: flagKey, defaultValue: defaultValue.swiftDictionary) as NSDictionary) } - @objc func boolVariationDetailFallback(_ flagKey: String, fallback: ObjCBool, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail = LDClient.shared.variationDetail(forKey: flagKey, fallback: fallback.boolValue) + @objc func boolVariationDetailDefaultValue(_ flagKey: String, defaultValue: ObjCBool, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let detail = LDClient.get()!.variationDetail(forKey: flagKey, defaultValue: defaultValue.boolValue) let jsonObject: NSDictionary = [ - "value": detail.value, - "variationIndex": detail.variationIndex, - "reason": detail.reason + "value": (detail.value as Any?) ?? NSNull(), + "variationIndex": (detail.variationIndex as Any?) ?? NSNull(), + "reason": (detail.reason as Any?) ?? NSNull() ] resolve(jsonObject) } - @objc func intVariationDetailFallback(_ flagKey: String, fallback: Int, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail = LDClient.shared.variationDetail(forKey: flagKey, fallback: fallback) + @objc func intVariationDetailDefaultValue(_ flagKey: String, defaultValue: Int, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let detail = LDClient.get()!.variationDetail(forKey: flagKey, defaultValue: defaultValue) let jsonObject: NSDictionary = [ - "value": detail.value, - "variationIndex": detail.variationIndex, - "reason": detail.reason + "value": (detail.value as Any?) ?? NSNull(), + "variationIndex": (detail.variationIndex as Any?) ?? NSNull(), + "reason": (detail.reason as Any?) ?? NSNull() ] resolve(jsonObject) } - @objc func floatVariationDetailFallback(_ flagKey: String, fallback: CGFloat, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail = LDClient.shared.variationDetail(forKey: flagKey, fallback: Double(fallback)) + @objc func floatVariationDetailDefaultValue(_ flagKey: String, defaultValue: CGFloat, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let detail = LDClient.get()!.variationDetail(forKey: flagKey, defaultValue: Double(defaultValue)) let jsonObject: NSDictionary = [ - "value": detail.value, - "variationIndex": detail.variationIndex, - "reason": detail.reason + "value": (detail.value as Any?) ?? NSNull(), + "variationIndex": (detail.variationIndex as Any?) ?? NSNull(), + "reason": (detail.reason as Any?) ?? NSNull() ] resolve(jsonObject) } - @objc func stringVariationDetailFallback(_ flagKey: String, fallback: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail = LDClient.shared.variationDetail(forKey: flagKey, fallback: fallback) + @objc func stringVariationDetailDefaultValue(_ flagKey: String, defaultValue: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let detail = LDClient.get()!.variationDetail(forKey: flagKey, defaultValue: defaultValue) let jsonObject: NSDictionary = [ - "value": detail.value, - "variationIndex": detail.variationIndex, - "reason": detail.reason + "value": (detail.value as Any?) ?? NSNull(), + "variationIndex": (detail.variationIndex as Any?) ?? NSNull(), + "reason": (detail.reason as Any?) ?? NSNull() ] resolve(jsonObject) } @objc func boolVariationDetail(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail: EvaluationDetail = LDClient.shared.variationDetail(forKey: flagKey) + let detail: LDEvaluationDetail = LDClient.get()!.variationDetail(forKey: flagKey) let jsonObject: NSDictionary = [ - "value": detail.value, - "variationIndex": detail.variationIndex, - "reason": detail.reason + "value": (detail.value as Any?) ?? NSNull(), + "variationIndex": (detail.variationIndex as Any?) ?? NSNull(), + "reason": (detail.reason as Any?) ?? NSNull() ] resolve(jsonObject) } @objc func intVariationDetail(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail: EvaluationDetail = LDClient.shared.variationDetail(forKey: flagKey) + let detail: LDEvaluationDetail = LDClient.get()!.variationDetail(forKey: flagKey) let jsonObject: NSDictionary = [ - "value": detail.value, - "variationIndex": detail.variationIndex, - "reason": detail.reason + "value": (detail.value as Any?) ?? NSNull(), + "variationIndex": (detail.variationIndex as Any?) ?? NSNull(), + "reason": (detail.reason as Any?) ?? NSNull() ] resolve(jsonObject) } @objc func floatVariationDetail(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail: EvaluationDetail = LDClient.shared.variationDetail(forKey: flagKey) + let detail: LDEvaluationDetail = LDClient.get()!.variationDetail(forKey: flagKey) let jsonObject: NSDictionary = [ - "value": detail.value, - "variationIndex": detail.variationIndex, - "reason": detail.reason + "value": (detail.value as Any?) ?? NSNull(), + "variationIndex": (detail.variationIndex as Any?) ?? NSNull(), + "reason": (detail.reason as Any?) ?? NSNull() ] resolve(jsonObject) } @objc func stringVariationDetail(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail: EvaluationDetail = LDClient.shared.variationDetail(forKey: flagKey) + let detail: LDEvaluationDetail = LDClient.get()!.variationDetail(forKey: flagKey) let jsonObject: NSDictionary = [ - "value": detail.value, - "variationIndex": detail.variationIndex, - "reason": detail.reason + "value": (detail.value as Any?) ?? NSNull(), + "variationIndex": (detail.variationIndex as Any?) ?? NSNull(), + "reason": (detail.reason as Any?) ?? NSNull() ] resolve(jsonObject) } @objc func jsonVariationDetailNone(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail: EvaluationDetail?> = LDClient.shared.variationDetail(forKey: flagKey) + let detail: LDEvaluationDetail?> = LDClient.get()!.variationDetail(forKey: flagKey) let jsonObject: NSDictionary = [ - "value": detail.value, - "variationIndex": detail.variationIndex, - "reason": detail.reason + "value": (detail.value as Any?) ?? NSNull(), + "variationIndex": (detail.variationIndex as Any?) ?? NSNull(), + "reason": (detail.reason as Any?) ?? NSNull() ] resolve(jsonObject) } - @objc func jsonVariationDetailNumber(_ flagKey: String, fallback: Double, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail = LDClient.shared.variationDetail(forKey: flagKey, fallback: fallback) + @objc func jsonVariationDetailNumber(_ flagKey: String, defaultValue: Double, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let detail = LDClient.get()!.variationDetail(forKey: flagKey, defaultValue: defaultValue) let jsonObject: NSDictionary = [ - "value": detail.value, - "variationIndex": detail.variationIndex, - "reason": detail.reason + "value": (detail.value as Any?) ?? NSNull(), + "variationIndex": (detail.variationIndex as Any?) ?? NSNull(), + "reason": (detail.reason as Any?) ?? NSNull() ] resolve(jsonObject) } - @objc func jsonVariationDetailBool(_ flagKey: String, fallback: Bool, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail = LDClient.shared.variationDetail(forKey: flagKey, fallback: fallback) + @objc func jsonVariationDetailBool(_ flagKey: String, defaultValue: Bool, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let detail = LDClient.get()!.variationDetail(forKey: flagKey, defaultValue: defaultValue) let jsonObject: NSDictionary = [ - "value": detail.value, - "variationIndex": detail.variationIndex, - "reason": detail.reason + "value": (detail.value as Any?) ?? NSNull(), + "variationIndex": (detail.variationIndex as Any?) ?? NSNull(), + "reason": (detail.reason as Any?) ?? NSNull() ] resolve(jsonObject) } - @objc func jsonVariationDetailString(_ flagKey: String, fallback: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail = LDClient.shared.variationDetail(forKey: flagKey, fallback: fallback) + @objc func jsonVariationDetailString(_ flagKey: String, defaultValue: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let detail = LDClient.get()!.variationDetail(forKey: flagKey, defaultValue: defaultValue) let jsonObject: NSDictionary = [ - "value": detail.value, - "variationIndex": detail.variationIndex, - "reason": detail.reason + "value": (detail.value as Any?) ?? NSNull(), + "variationIndex": (detail.variationIndex as Any?) ?? NSNull(), + "reason": (detail.reason as Any?) ?? NSNull() ] resolve(jsonObject) } - @objc func jsonVariationDetailArray(_ flagKey: String, fallback: Array, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail = LDClient.shared.variationDetail(forKey: flagKey, fallback: fallback) + @objc func jsonVariationDetailArray(_ flagKey: String, defaultValue: Array, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let detail = LDClient.get()!.variationDetail(forKey: flagKey, defaultValue: defaultValue) let jsonObject: NSDictionary = [ - "value": detail.value, - "variationIndex": detail.variationIndex, - "reason": detail.reason + "value": (detail.value as Any?) ?? NSNull(), + "variationIndex": (detail.variationIndex as Any?) ?? NSNull(), + "reason": (detail.reason as Any?) ?? NSNull() ] resolve(jsonObject) } - @objc func jsonVariationDetailObject(_ flagKey: String, fallback: NSDictionary, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail = LDClient.shared.variationDetail(forKey: flagKey, fallback: fallback.swiftDictionary) + @objc func jsonVariationDetailObject(_ flagKey: String, defaultValue: NSDictionary, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let detail = LDClient.get()!.variationDetail(forKey: flagKey, defaultValue: defaultValue.swiftDictionary) let jsonObject: NSDictionary = [ - "value": detail.value, - "variationIndex": detail.variationIndex, - "reason": detail.reason + "value": (detail.value as Any?) ?? NSNull(), + "variationIndex": (detail.variationIndex as Any?) ?? NSNull(), + "reason": (detail.reason as Any?) ?? NSNull() ] resolve(jsonObject) } @objc func trackNumber(_ eventName: String, data: NSNumber) -> Void { - try? LDClient.shared.trackEvent(key: eventName, data: data) + try? LDClient.get()!.track(key: eventName, data: data) } @objc func trackBool(_ eventName: String, data: ObjCBool) -> Void { - try? LDClient.shared.trackEvent(key: eventName, data: data.boolValue) + try? LDClient.get()!.track(key: eventName, data: data.boolValue) } @objc func trackString(_ eventName: String, data: String) -> Void { - try? LDClient.shared.trackEvent(key: eventName, data: data) + try? LDClient.get()!.track(key: eventName, data: data) } @objc func trackArray(_ eventName: String, data: NSArray) -> Void { - try? LDClient.shared.trackEvent(key: eventName, data: data) + try? LDClient.get()!.track(key: eventName, data: data) } @objc func trackObject(_ eventName: String, data: NSDictionary) -> Void { - try? LDClient.shared.trackEvent(key: eventName, data: data.swiftDictionary) + try? LDClient.get()!.track(key: eventName, data: data.swiftDictionary) } @objc func track(_ eventName: String) -> Void { - try? LDClient.shared.trackEvent(key: eventName) + try? LDClient.get()!.track(key: eventName) } - @objc func trackNumberMetricValue(_ eventName: String, data: NSNumber, metricValue: Double) -> Void { - try? LDClient.shared.trackEvent(key: eventName, data: data, metricValue: metricValue) + @objc func trackNumberMetricValue(_ eventName: String, data: NSNumber, metricValue: NSNumber) -> Void { + try? LDClient.get()!.track(key: eventName, data: data, metricValue: Double(truncating: metricValue)) } - @objc func trackBoolMetricValue(_ eventName: String, data: ObjCBool, metricValue: Double) -> Void { - try? LDClient.shared.trackEvent(key: eventName, data: data.boolValue, metricValue: metricValue) + @objc func trackBoolMetricValue(_ eventName: String, data: ObjCBool, metricValue: NSNumber) -> Void { + try? LDClient.get()!.track(key: eventName, data: data.boolValue, metricValue: Double(truncating: metricValue)) } - @objc func trackStringMetricValue(_ eventName: String, data: String, metricValue: Double) -> Void { - try? LDClient.shared.trackEvent(key: eventName, data: data, metricValue: metricValue) + @objc func trackStringMetricValue(_ eventName: String, data: String, metricValue: NSNumber) -> Void { + try? LDClient.get()!.track(key: eventName, data: data, metricValue: Double(truncating: metricValue)) } - @objc func trackArrayMetricValue(_ eventName: String, data: NSArray, metricValue: Double) -> Void { - try? LDClient.shared.trackEvent(key: eventName, data: data, metricValue: metricValue) + @objc func trackArrayMetricValue(_ eventName: String, data: NSArray, metricValue: NSNumber) -> Void { + try? LDClient.get()!.track(key: eventName, data: data, metricValue: Double(truncating: metricValue)) } - @objc func trackObjectMetricValue(_ eventName: String, data: NSDictionary, metricValue: Double) -> Void { - try? LDClient.shared.trackEvent(key: eventName, data: data.swiftDictionary, metricValue: metricValue) + @objc func trackObjectMetricValue(_ eventName: String, data: NSDictionary, metricValue: NSNumber) -> Void { + try? LDClient.get()!.track(key: eventName, data: data.swiftDictionary, metricValue: Double(truncating: metricValue)) } - @objc func trackMetricValue(_ eventName: String, metricValue: Double) -> Void { - try? LDClient.shared.trackEvent(key: eventName, metricValue: metricValue) + @objc func trackMetricValue(_ eventName: String, metricValue: NSNumber) -> Void { + try? LDClient.get()!.track(key: eventName, metricValue: Double(truncating: metricValue)) } @objc func setOffline(_ resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - LDClient.shared.setOnline(false) { + LDClient.get()!.setOnline(false) { return resolve(true) } } @objc func isOffline(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(!LDClient.shared.isOnline) + resolve(!LDClient.get()!.isOnline) } @objc func setOnline(_ resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - LDClient.shared.setOnline(true) { + LDClient.get()!.setOnline(true) { return resolve(true) } } @objc func flush() -> Void { - LDClient.shared.reportEvents() + LDClient.get()!.flush() } @objc func close(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - LDClient.shared.stop() + LDClient.get()!.close() resolve(true) } @objc func identify(_ options: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let user = userBuild(userConfig: options) + let user = userBuild(userDict: options) if let usr = user { - LDClient.shared.identify(user: usr) { + LDClient.get()!.identify(user: usr) { resolve(nil) } } else { @@ -430,7 +477,7 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { @objc func allFlags(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { var allFlagsDict: [String: Any] = [:] - if let allFlags = LDClient.shared.allFlagValues { + if let allFlags = LDClient.get()!.allFlags { for (key, value) in allFlags { allFlagsDict[key] = value } @@ -445,7 +492,7 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { } else { return } - LDClient.shared.observe(keys: [flagKey], owner: flagChangeOwner, handler: { (changedFlags) in + LDClient.get()!.observe(keys: [flagKey], owner: flagChangeOwner, handler: { (changedFlags) in if changedFlags[flagKey] != nil && self.bridge != nil { self.sendEvent(withName: self.FLAG_PREFIX, body: ["flagKey": flagKey]) } @@ -459,19 +506,52 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { } else { return } - LDClient.shared.stopObserving(owner: owner) + LDClient.get()!.stopObserving(owner: owner) } @objc func unregisterFeatureFlagListener(_ flagKey: String) -> Void { unregisterListener(flagKey) } - @objc func isDisableBackgroundPolling(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.shared.config.enableBackgroundUpdates) + @objc func getConnectionMode(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let connectionInformation = LDClient.get()!.getConnectionInformation() + var connectionMode: String + switch connectionInformation.currentConnectionMode { + case .streaming: + connectionMode = "STREAMING" + case .polling: + connectionMode = "POLLING" + case .offline: + connectionMode = "OFFLINE" + case .establishingStreamingConnection: + connectionMode = "ESTABLISHING_STREAMING_CONNECTION" + } + resolve(connectionMode) } - - @objc func getConnectionInformation(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.shared.getConnectionInformation()) + + // lastKnownFlagValidity is nil if either no connection has ever been successfully made or if the SDK has an active streaming connection. It will have a value if 1) in polling mode and at least one poll has completed successfully, or 2) if in streaming mode whenever the streaming connection closes. + @objc func getLastSuccessfulConnection(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + resolve(LDClient.get()!.getConnectionInformation().lastKnownFlagValidity ?? 0) + } + + @objc func getLastFailedConnection(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + resolve(LDClient.get()!.getConnectionInformation().lastFailedConnection ?? 0) + } + + @objc func getLastFailure(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let connectionInformation = LDClient.get()!.getConnectionInformation() + var failureReason: String + switch connectionInformation.lastConnectionFailureReason { + case .unauthorized: + failureReason = "UNAUTHORIZED" + case .none: + failureReason = "NONE" + case .httpError: + failureReason = "HTTP_ERROR" + case .unknownError: + failureReason = "UNKNOWN_ERROR" + } + resolve(failureReason) } @objc func registerCurrentConnectionModeListener(_ listenerId: String) -> Void { @@ -481,7 +561,7 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { } else { return } - LDClient.shared.observeCurrentConnectionMode(owner: currentConnectionModeOwner, handler: { (connectionMode) in + LDClient.get()!.observeCurrentConnectionMode(owner: currentConnectionModeOwner, handler: { (connectionMode) in if self.bridge != nil { self.sendEvent(withName: self.CONNECTION_MODE_PREFIX, body: ["connectionMode": connectionMode]) } @@ -499,7 +579,7 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { } else { return } - LDClient.shared.observeAll(owner: flagChangeOwner, handler: { (changedFlags) in + LDClient.get()!.observeAll(owner: flagChangeOwner, handler: { (changedFlags) in if self.bridge != nil { self.sendEvent(withName: self.ALL_FLAGS_PREFIX, body: ["flagKeys": changedFlags.keys.description]) } @@ -509,6 +589,14 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { @objc func unregisterAllFlagsListener(_ listenerId: String) -> Void { unregisterListener(listenerId) } + + @objc func isInitialized(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + if LDClient.get() == nil { + resolve(false) + } else { + resolve(LDClient.get()!.isInitialized) + } + } } extension NSDictionary { diff --git a/ios/LaunchdarklyReactNativeClientBridge.m b/ios/LaunchdarklyReactNativeClientBridge.m index cdcbd3b..af3931a 100644 --- a/ios/LaunchdarklyReactNativeClientBridge.m +++ b/ios/LaunchdarklyReactNativeClientBridge.m @@ -3,15 +3,17 @@ @interface RCT_EXTERN_MODULE(LaunchdarklyReactNativeClient, RCTEventEmitter) -RCT_EXTERN_METHOD(configure:(NSDictionary *)config userConfig:(NSDictionary *)userConfig resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(configure:(NSDictionary *)config user:(NSDictionary *)user resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(boolVariationFallback:(NSString *)flagKey fallback:(BOOL *)fallback resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(configureWithTimeout:(NSDictionary *)config userConfig:(NSDictionary *)userConfig timeout:(NSInteger *)timeout resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(intVariationFallback:(NSString *)flagKey fallback:(NSInteger *)fallback resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(boolVariationDefaultValue:(NSString *)flagKey defaultValue:(BOOL *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(floatVariationFallback:(NSString *)flagKey fallback:(CGFloat *)fallback resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(intVariationDefaultValue:(NSString *)flagKey defaultValue:(NSInteger *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(stringVariationFallback:(NSString *)flagKey fallback:(NSString *)fallback resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(floatVariationDefaultValue:(NSString *)flagKey defaultValue:(CGFloat *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(stringVariationDefaultValue:(NSString *)flagKey defaultValue:(NSString *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) RCT_EXTERN_METHOD(boolVariation:(NSString *)flagKey resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) @@ -23,23 +25,23 @@ @interface RCT_EXTERN_MODULE(LaunchdarklyReactNativeClient, RCTEventEmitter) RCT_EXTERN_METHOD(jsonVariationNone:(NSString *)flagKey resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(jsonVariationNumber:(NSString *)flagKey fallback:(NSNumber *)fallback resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(jsonVariationNumber:(NSString *)flagKey defaultValue:(NSNumber *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(jsonVariationBool:(NSString *)flagKey fallback:(BOOL *)fallback resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(jsonVariationBool:(NSString *)flagKey defaultValue:(BOOL *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(jsonVariationString:(NSString *)flagKey fallback:(NSString *)fallback resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(jsonVariationString:(NSString *)flagKey defaultValue:(NSString *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(jsonVariationArray:(NSString *)flagKey fallback:(NSArray *)fallback resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(jsonVariationArray:(NSString *)flagKey defaultValue:(NSArray *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(jsonVariationObject:(NSString *)flagKey fallback:(NSDictionary *)fallback resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(jsonVariationObject:(NSString *)flagKey defaultValue:(NSDictionary *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(boolVariationDetailFallback:(NSString *)flagKey fallback:(BOOL *)fallback resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(boolVariationDetailDefaultValue:(NSString *)flagKey defaultValue:(BOOL *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(intVariationDetailFallback:(NSString *)flagKey fallback:(NSInteger *)fallback resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(intVariationDetailDefaultValue:(NSString *)flagKey defaultValue:(NSInteger *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(floatVariationDetailFallback:(NSString *)flagKey fallback:(CGFloat *)fallback resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(floatVariationDetailDefaultValue:(NSString *)flagKey defaultValue:(CGFloat *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(stringVariationDetailFallback:(NSString *)flagKey fallback:(NSString *)fallback resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(stringVariationDetailDefaultValue:(NSString *)flagKey defaultValue:(NSString *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) RCT_EXTERN_METHOD(boolVariationDetail:(NSString *)flagKey resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) @@ -51,21 +53,21 @@ @interface RCT_EXTERN_MODULE(LaunchdarklyReactNativeClient, RCTEventEmitter) RCT_EXTERN_METHOD(jsonVariationDetailNone:(NSString *)flagKey resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(jsonVariationDetailNumber:(NSString *)flagKey fallback:(NSNumber *)fallback resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(jsonVariationDetailNumber:(NSString *)flagKey defaultValue:(NSNumber *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(jsonVariationDetailBool:(NSString *)flagKey fallback:(BOOL *)fallback resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(jsonVariationDetailBool:(NSString *)flagKey defaultValue:(BOOL *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(jsonVariationDetailString:(NSString *)flagKey fallback:(NSString *)fallback resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(jsonVariationDetailString:(NSString *)flagKey defaultValue:(NSString *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(jsonVariationDetailArray:(NSString *)flagKey fallback:(NSArray *)fallback resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(jsonVariationDetailArray:(NSString *)flagKey defaultValue:(NSArray *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(jsonVariationDetailObject:(NSString *)flagKey fallback:(NSDictionary *)fallback resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(jsonVariationDetailObject:(NSString *)flagKey defaultValue:(NSDictionary *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) RCT_EXTERN_METHOD(trackBool:(NSString *)eventName data:(BOOL *)data) RCT_EXTERN_METHOD(trackArray:(NSString *)eventName data:(NSArray *)data) -RCT_EXTERN_METHOD(trackNumber:(NSString *)eventName data:(NSNumber *)data) +RCT_EXTERN_METHOD(trackNumber:(NSString *)eventName data:(NSNumber * _Nonnull)data) RCT_EXTERN_METHOD(trackString:(NSString *)eventName data:(NSString *)data) @@ -73,17 +75,17 @@ @interface RCT_EXTERN_MODULE(LaunchdarklyReactNativeClient, RCTEventEmitter) RCT_EXTERN_METHOD(track:(NSString *)eventName) -RCT_EXTERN_METHOD(trackBoolMetricValue:(NSString *)eventName data:(BOOL *)data metricValue:(NSNumber *)metricValue) +RCT_EXTERN_METHOD(trackBoolMetricValue:(NSString *)eventName data:(BOOL *)data metricValue:(NSNumber * _Nonnull)metricValue) -RCT_EXTERN_METHOD(trackArrayMetricValue:(NSString *)eventName data:(NSArray *)data metricValue:(NSNumber *)metricValue) +RCT_EXTERN_METHOD(trackArrayMetricValue:(NSString *)eventName data:(NSArray *)data metricValue:(NSNumber * _Nonnull)metricValue) -RCT_EXTERN_METHOD(trackNumberMetricValue:(NSString *)eventName data:(NSNumber *)data metricValue:(NSNumber *)metricValue) +RCT_EXTERN_METHOD(trackNumberMetricValue:(NSString *)eventName data:(NSNumber * _Nonnull)data metricValue:(NSNumber * _Nonnull)metricValue) -RCT_EXTERN_METHOD(trackStringMetricValue:(NSString *)eventName data:(NSString *)data metricValue:(NSNumber *)metricValue) +RCT_EXTERN_METHOD(trackStringMetricValue:(NSString *)eventName data:(NSString *)data metricValue:(NSNumber * _Nonnull)metricValue) -RCT_EXTERN_METHOD(trackObjectMetricValue:(NSString *)eventName data:(NSDictionary *)data metricValue:(NSNumber *)metricValue) +RCT_EXTERN_METHOD(trackObjectMetricValue:(NSString *)eventName data:(NSDictionary *)data metricValue:(NSNumber * _Nonnull)metricValue) -RCT_EXTERN_METHOD(trackMetricValue:(NSString *)eventName metricValue:(NSNumber *)metricValue) +RCT_EXTERN_METHOD(trackMetricValue:(NSString *)eventName metricValue:(NSNumber * _Nonnull)metricValue) RCT_EXTERN_METHOD(setOffline:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) @@ -103,10 +105,6 @@ @interface RCT_EXTERN_MODULE(LaunchdarklyReactNativeClient, RCTEventEmitter) RCT_EXTERN_METHOD(unregisterFeatureFlagListener:(NSString *)flagKey) -RCT_EXTERN_METHOD(isDisableBackgroundPolling:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) - -RCT_EXTERN_METHOD(getConnectionInformation:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) - RCT_EXTERN_METHOD(registerCurrentConnectionModeListener:(NSString *)listenerId) RCT_EXTERN_METHOD(unregisterCurrentConnectionModeListener:(NSString *)listenerId) @@ -115,4 +113,14 @@ @interface RCT_EXTERN_MODULE(LaunchdarklyReactNativeClient, RCTEventEmitter) RCT_EXTERN_METHOD(unregisterAllFlagsListener:(NSString *)listenerId) +RCT_EXTERN_METHOD(isInitialized:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(getConnectionMode:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(getLastSuccessfulConnection:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(getLastFailedConnection:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(getLastFailure:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) + @end diff --git a/test-types.ts b/test-types.ts index 31d99a4..f311656 100644 --- a/test-types.ts +++ b/test-types.ts @@ -2,12 +2,14 @@ // This file exists only so that we can run the TypeScript compiler in the CI build // to validate our index.d.ts file. The code will not actually be run. -import LDClient, { - LDClientConfig, +import LDClient, { + LDConnectionMode, + LDConfig, LDEvaluationDetail, LDEvaluationReason, + LDFailureReason, LDFlagSet, - LDUserConfig, + LDUser, } from 'launchdarkly-react-native-client-sdk'; async function tests() { @@ -20,12 +22,12 @@ async function tests() { 'f': [ 1, 2 ] }; - const configWithKeyOnly: LDClientConfig = { + const configWithKeyOnly: LDConfig = { mobileKey: '' }; - const configWithAllOptions: LDClientConfig = { + const configWithAllOptions: LDConfig = { mobileKey: '', - baseUri: '', + pollUri: '', streamUri: '', eventsUri: '', eventsCapacity: 1, @@ -39,9 +41,13 @@ async function tests() { offline: true, debugMode: true, evaluationReasons: true, + maxCachedUsers: 6, + diagnosticOptOut: true, + diagnosticRecordingIntervalMillis: 100000, + allUserAttributesPrivate: true, }; - const userWithKeyOnly: LDUserConfig = { key: 'user' }; - const user: LDUserConfig = { + const userWithKeyOnly: LDUser = { key: 'user' }; + const user: LDUser = { key: 'user', name: 'name', firstName: 'first', @@ -51,10 +57,14 @@ async function tests() { country: 'us', privateAttributeNames: [ 'name', 'email' ], custom: jsonObj, + avatar: 'avatar', + ip: '192.0.2.1', }; const client: LDClient = new LDClient(); + const timeoutClient: LDClient = new LDClient(); const configure: null = await client.configure(configWithAllOptions, user); + const configureWithTimeout: null = await timeoutClient.configure(configWithAllOptions, user, 10); const identify: null = await client.identify(user); const boolFlagValue: boolean = await client.boolVariation('key', false); @@ -94,7 +104,6 @@ async function tests() { const setOnline: boolean = await client.setOnline(); const isOffline: boolean = await client.isOffline(); const isInitialized: boolean = await client.isInitialized(); - const isDisableBackgroundPolling: boolean = await client.isDisableBackgroundPolling(); const callback = function(_: string): void { }; const registerFeatureFlagListener: void = client.registerFeatureFlagListener('key', callback); @@ -104,10 +113,15 @@ async function tests() { const registerCurrentConnectionModeListener: void = client.registerCurrentConnectionModeListener('id', callback); const unregisterCurrentConnectionModeListener: void = client.unregisterCurrentConnectionModeListener('id'); - const getConnectionInformation: any = await client.getConnectionInformation(); + const getConnectionMode: LDConnectionMode = await client.getConnectionMode(); + const getSuccessfulConnection: number | null = await client.getLastSuccessfulConnection(); + const getFailedConnection: number | null = await client.getLastFailedConnection(); + const getFailureReason: LDFailureReason | null = await client.getLastFailure(); const flush: void = await client.flush(); const close: void = await client.close(); + + const version: String = client.getVersion(); }; tests(); \ No newline at end of file From 510e581573891b9423ab2ea826a673aa59108248 Mon Sep 17 00:00:00 2001 From: Joe Cieslik <5600929+torchhound@users.noreply.github.com> Date: Tue, 6 Apr 2021 18:17:06 -0700 Subject: [PATCH 03/32] Update iOS SDK dependency to fix Throttler behavior (#69) --- ios/LaunchdarklyReactNativeClient.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/LaunchdarklyReactNativeClient.podspec b/ios/LaunchdarklyReactNativeClient.podspec index f0fa03b..0dd5206 100644 --- a/ios/LaunchdarklyReactNativeClient.podspec +++ b/ios/LaunchdarklyReactNativeClient.podspec @@ -16,6 +16,6 @@ Pod::Spec.new do |s| s.swift_version = "5.0" s.dependency "React" - s.dependency "LaunchDarkly", "5.4.0" + s.dependency "LaunchDarkly", "5.4.1" end From 9df202f82bcdfc6df5d64f7b0046dde56008e9ff Mon Sep 17 00:00:00 2001 From: Joe Cieslik <5600929+torchhound@users.noreply.github.com> Date: Tue, 13 Apr 2021 18:36:01 -0700 Subject: [PATCH 04/32] V4.1.0 (#72) * Bump react native to 0.64 (#70) * Remove unnecessary React peer dependency (#71) --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 91e4317..ed56f65 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,7 @@ }, "homepage": "https://docs.launchdarkly.com/sdk/client-side/react-native", "peerDependencies": { - "react-native": "~0.63", - "react": "~16.13.0" + "react-native": "~0.64" }, "devDependencies": { "metro-react-native-babel-preset": "0.59.0", From 35b61e1bf850a8669a00823a54758ad07eddfbff Mon Sep 17 00:00:00 2001 From: Joe Cieslik <5600929+torchhound@users.noreply.github.com> Date: Fri, 16 Apr 2021 11:31:51 -0700 Subject: [PATCH 05/32] Fixed jsonVariationDetail parsing on Android and fixed a typo in jsonVariationDetailNone (#73) --- .../reactnative/LaunchdarklyReactNativeClientModule.java | 7 +++++-- index.js | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java b/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java index 4190b76..610cd52 100644 --- a/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java +++ b/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java @@ -758,8 +758,11 @@ private void resolveJsonElement(Promise promise, JsonElement jsonElement) { } private void resolveJsonElementDetail(Promise promise, EvaluationDetail jsonElementDetail) { - JsonElement jsonElement = jsonElementDetail.getValue(); - resolveJsonElement(promise, jsonElement); + JsonObject jsonObject = new JsonObject(); + jsonObject.add("value", jsonElementDetail.getValue()); + jsonObject.addProperty("variationIndex", jsonElementDetail.getVariationIndex()); + jsonObject.add("reason", gson.toJsonTree(jsonElementDetail.getReason())); + resolveJsonElement(promise, jsonObject); } /** diff --git a/index.js b/index.js index 32464cd..b443f29 100644 --- a/index.js +++ b/index.js @@ -120,7 +120,7 @@ export default class LDClient { jsonVariationDetail(flagKey, defaultValue) { if (defaultValue == undefined) { - return LaunchdarklyReactNativeClient.jsonVariatioDetailNone(flagKey); + return LaunchdarklyReactNativeClient.jsonVariationDetailNone(flagKey); } else if (typeof defaultValue === 'number') { return LaunchdarklyReactNativeClient.jsonVariationDetailNumber(flagKey, defaultValue); } else if (typeof defaultValue === 'boolean') { From 82ba5fe7239e6c082b3c16706267e6f7878696d4 Mon Sep 17 00:00:00 2001 From: Ben Woskow Date: Fri, 23 Apr 2021 16:37:01 -0700 Subject: [PATCH 06/32] Releasing version 4.0.2 --- CHANGELOG.md | 4 ++++ .../reactnative/LaunchdarklyReactNativeClientModule.java | 7 +++++-- index.js | 2 +- ios/LaunchdarklyReactNativeClient.podspec | 2 +- package.json | 2 +- 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a67973e..7737888 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to the LaunchDarkly React Native SDK will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org). +## [4.0.2] - 2021-04-23 +### Fixed: +- Android: Fixed an issue where the `jsonVariationDetail` method in `LDClient` returned `Promise>` instead of the declared return type of `Promise>>`. + ## [4.0.1] - 2021-04-06 ### Fixed: - iOS: Internal throttling logic would sometimes delay new poll or stream connections even when there were no recent connections. This caused switching active user contexts using `identify` to sometimes delay retrieving the most recent flags, and therefore delay the completion of the returned Promise. diff --git a/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java b/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java index 4190b76..610cd52 100644 --- a/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java +++ b/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java @@ -758,8 +758,11 @@ private void resolveJsonElement(Promise promise, JsonElement jsonElement) { } private void resolveJsonElementDetail(Promise promise, EvaluationDetail jsonElementDetail) { - JsonElement jsonElement = jsonElementDetail.getValue(); - resolveJsonElement(promise, jsonElement); + JsonObject jsonObject = new JsonObject(); + jsonObject.add("value", jsonElementDetail.getValue()); + jsonObject.addProperty("variationIndex", jsonElementDetail.getVariationIndex()); + jsonObject.add("reason", gson.toJsonTree(jsonElementDetail.getReason())); + resolveJsonElement(promise, jsonObject); } /** diff --git a/index.js b/index.js index 32464cd..b443f29 100644 --- a/index.js +++ b/index.js @@ -120,7 +120,7 @@ export default class LDClient { jsonVariationDetail(flagKey, defaultValue) { if (defaultValue == undefined) { - return LaunchdarklyReactNativeClient.jsonVariatioDetailNone(flagKey); + return LaunchdarklyReactNativeClient.jsonVariationDetailNone(flagKey); } else if (typeof defaultValue === 'number') { return LaunchdarklyReactNativeClient.jsonVariationDetailNumber(flagKey, defaultValue); } else if (typeof defaultValue === 'boolean') { diff --git a/ios/LaunchdarklyReactNativeClient.podspec b/ios/LaunchdarklyReactNativeClient.podspec index 66fca63..a327d78 100644 --- a/ios/LaunchdarklyReactNativeClient.podspec +++ b/ios/LaunchdarklyReactNativeClient.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "LaunchdarklyReactNativeClient" - s.version = "4.0.1" + s.version = "4.0.2" s.summary = "LaunchdarklyReactNativeClient" s.description = <<-DESC LaunchdarklyReactNativeClient diff --git a/package.json b/package.json index 91e4317..30af045 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "launchdarkly-react-native-client-sdk", - "version": "4.0.1", + "version": "4.0.2", "description": "", "main": "index.js", "types": "index.d.ts", From cae667b956fac61bd0486c60f96e3343e79ab737 Mon Sep 17 00:00:00 2001 From: Joe Cieslik <5600929+torchhound@users.noreply.github.com> Date: Tue, 27 Apr 2021 17:05:00 -0700 Subject: [PATCH 07/32] Removes Typescript enums and replaces them with types that extend string (#74) * Added ts flag for testing * Testing bwoskow typescript enum patch * Remove todo --- index.d.ts | 220 +++++++++++++---------------------------------------- 1 file changed, 53 insertions(+), 167 deletions(-) diff --git a/index.d.ts b/index.d.ts index 9c01977..1bd1d8e 100644 --- a/index.d.ts +++ b/index.d.ts @@ -237,190 +237,45 @@ declare module 'launchdarkly-react-native-client-sdk' { /** * Describes the kind of error which occurred when a flag evaluation was calculated. + * + * This extends String for backwards compatibility. In a future major release, references to this type + * may be replaced with String references. */ - export enum LDEvaluationReasonErrorKind { - /** - * The client is not able to establish a connection to LaunchDarkly yet. - * - * If there is a persistent feature store, the store does not yet contain flag data. - */ - CLIENT_NOT_READY = 'CLIENT_NOT_READY', - - /** - * The flag key did not match any known flag. - */ - FLAG_NOT_FOUND = 'FLAG_NOT_FOUND', - - /** - * The user object or user key was not provided. - */ - USER_NOT_SPECIFIED = 'USER_NOT_SPECIFIED', - - /** - * There was an internal inconsistency in the flag data. For example, a rule specified a nonexistent variation. - * - * This is an unusual condition that might require assistance from LaunchDarkly's Support team. - */ - MALFORMED_FLAG = 'MALFORMED_FLAG', - - /** - * The application code requested the flag value with a different data type than it actually is. - * - * For example, the code asked for a boolean when the flag type is actually a string. - */ - WRONG_TYPE = 'WRONG_TYPE', - - /** - * An unexpected error stopped flag evaluation. This could happen if you are using a persistent feature store and the database stops working. - * - * When this happens, the SDK always prints the specific error to the log. - */ - EXCEPTION = 'EXCEPTION', + export interface LDEvaluationReasonErrorKind extends String { } /** * Describes the reason behind how a flag evaluation was calculated. + * + * This extends String for backwards compatibility. In a future major release, references to this type + * may be replaced with String references. */ - export enum LDEvaluationReasonKind { - /** - * The flag is off and therefore returned its configured off value. - * - * This value appears on the dashboard next to "If targeting is off, serve:". - */ - OFF = 'OFF', - - /** - * The flag is on, but the user did not match any targets or rules, so it returned the value that appears on the dashboard under "Default rule." - */ - FALLTHROUGH = 'FALLTHROUGH', - - /** - * The user key was specifically targeted for this flag in the "Target individual users" section. - */ - TARGET_MATCH = 'TARGET_MATCH', - - /** - * The user who encountered the flag matched one of the flag's rules. - */ - RULE_MATCH = 'RULE_MATCH', - - /** - * The flag had at least one prerequisite flag that either was off or did not return the desired variation. - * - * Because of this, the flag returned its "off" value. - */ - PREREQUISITE_FAILED = 'PREREQUISITE_FAILED', - - /** - * The flag could not be evaluated, so the default value was returned. - */ - ERROR = 'ERROR', + export interface LDEvaluationReasonKind extends String { } /** * Describes what state of connection to LaunchDarkly the SDK is in. + * + * This extends String for backwards compatibility. In a future major release, references to this type + * may be replaced with String references. */ - export enum LDConnectionMode { - /** - * The SDK is either connected to the flag stream, or is actively attempting to acquire a connection. - */ - STREAMING = 'STREAMING', - - /** - * The SDK was configured with streaming disabled, and is in foreground polling mode. - */ - POLLING = 'POLLING', - - /** - * (Android specific enum value) The SDK has detected the application is in the background and has transitioned to - * battery conscious background polling. - */ - BACKGROUND_POLLING = 'BACKGROUND_POLLING', - - /** - * (Android specific enum value) The SDK was configured with background polling disabled. The SDK has detected - * the application is in the background and is not attempting to update the flag cache. - */ - BACKGROUND_DISABLED = 'BACKGROUND_DISABLED', - - /** - * The SDK has detected that the mobile device does not have an active network connection so has ceased flag update - * attempts until the network status changes. - */ - OFFLINE = 'OFFLINE', - - /** - * (Android specific enum value) The SDK has been explicitly set offline, either in the initial configuration, - * by setOffline(), or as a result of failed authentication to LaunchDarkly. - * The SDK will stay offline unless setOnline() is called. - */ - SET_OFFLINE = 'SET_OFFLINE', - - /** - * (Android specific enum value) The shutdown state indicates the SDK has been permanently shutdown as a result of - * a call to close(). - */ - SHUTDOWN = 'SHUTDOWN', - - /** - * (iOS specific enum value) The SDK is attempting to connect to LaunchDarkly by streaming. - */ - ESTABLISHING_STREAMING_CONNECTION = 'ESTABLISHING_STREAMING_CONNECTION', + export interface LDConnectionMode extends String { } /** * Describes why a connection request to LaunchDarkly failed. + * + * This extends String for backwards compatibility. In a future major release, references to this type + * may be replaced with String references. */ - export enum LDFailureReason { - /** - * This indicates when no error has been recorded. - */ - NONE = 'NONE', - - /** - * This indicates when there is an internal error in the stream request. - */ - UKNOWN_ERROR ='UNKNOWN_ERROR', - - /** - * (iOS specific enum value) This indicates when an incorrect mobile key is provided. - */ - UNAUTHORIZED = 'UNAUTHORIZED', - - /** - * (iOS specific enum value) This indicates when an error with an HTTP error code is present. - */ - HTTP_ERROR = 'HTTP_ERROR', - - /** - * (Android specific enum value) This indicates the LDFailure is an instance of LDInvalidResponseCodeFailure. - * See Android documentation for more details. - */ - UNEXPECTED_RESPONSE_CODE = 'UNEXPECTED_RESPONSE_CODE', - - /** - * (Android specific enum value) An event was received through the stream was had an unknown event name. - * This could indicate a newer SDK is available if new event types have become available through the - * flag stream since the SDKs release. - */ - UNEXPECTED_STREAM_ELEMENT_TYPE = 'UNEXPECTED_STREAM_ELEMENT_TYPE', - - /** - * (Android specific enum value) A network request for polling, or the EventSource stream reported a failure. - */ - NETWORK_FAILURE = 'NETWORK_FAILURE', - - /** - * (Android specific enum value) A response body received either through polling or streaming was unable to be parsed. - */ - INVALID_RESPONSE_BODY = 'INVALID_RESPONSE_BODY', + export interface LDFailureReason extends String { } /** * The flag is off and therefore returned its configured off value. */ export type LDEvaluationReasonOff = { - kind: LDEvaluationReasonKind.OFF; + kind: 'OFF'; }; /** @@ -428,21 +283,21 @@ declare module 'launchdarkly-react-native-client-sdk' { * on the dashboard under "Default rule." */ export type LDEvaluationReasonFallthrough = { - kind: LDEvaluationReasonKind.FALLTHROUGH; + kind: 'FALLTHROUGH'; }; /** * The user key was specifically targeted for this flag in the "Target individual users" section. */ export type LDEvaluationReasonTargetMatch = { - kind: LDEvaluationReasonKind.TARGET_MATCH; + kind: 'TARGET_MATCH'; }; /** * The user who encountered the flag matched one of the flag's rules. */ export type LDEvaluationReasonRuleMatch = { - kind: LDEvaluationReasonKind.RULE_MATCH; + kind: 'RULE_MATCH'; /** * The positional index of the matched rule (0 for the first rule). @@ -460,7 +315,7 @@ declare module 'launchdarkly-react-native-client-sdk' { * Because of this, the flag returned its "off" value. */ export type LDEvaluationReasonPrerequisiteFailed = { - kind: LDEvaluationReasonKind.PREREQUISITE_FAILED; + kind: 'PREREQUISITE_FAILED'; /** * The key of the prerequisite flag that failed. @@ -472,10 +327,19 @@ declare module 'launchdarkly-react-native-client-sdk' { * The flag could not be evaluated, so the default value was returned. */ export type LDEvaluationReasonError = { - kind: LDEvaluationReasonKind.ERROR; + kind: 'ERROR'; /** * The kind of error which occurred. + * + * Kinds of errors include: + * + * - `'CLIENT_NOT_READY'`: The client is not able to establish a connection to LaunchDarkly yet. If there is a persistent feature store, the store does not yet contain flag data. + * - `'FLAG_NOT_FOUND'`: The flag key did not match any known flag. + * - `'USER_NOT_SPECIFIED'`: The user object or user key was not provided. + * - `'MALFORMED_FLAG'`: There was an internal inconsistency in the flag data. For example, a rule specified a nonexistent variation. This is an unusual condition that might require assistance from LaunchDarkly's Support team. + * - `'WRONG_TYPE'`: The application code requested the flag value with a different data type than it actually is. For example, the code asked for a boolean when the flag type is actually a string. + * - `'EXCEPTION'`: An unexpected error stopped flag evaluation. This could happen if you are using a persistent feature store and the database stops working. When this happens, the SDK always prints the specific error to the log. */ errorKind: LDEvaluationReasonErrorKind; }; @@ -854,7 +718,18 @@ declare module 'launchdarkly-react-native-client-sdk' { /** * Returns the current state of the connection to LaunchDarkly. + * + * States include: * + * - `'STREAMING'`: The SDK is either connected to the flag stream, or is actively attempting to acquire a connection. + * - `'POLLING'`: The SDK was configured with streaming disabled, and is in foreground polling mode. + * - `'BACKGROUND_POLLING'`: (Android specific enum value) The SDK has detected the application is in the background and has transitioned to battery conscious background polling. + * - `'BACKGROUND_DISABLED'`: (Android specific enum value) The SDK was configured with background polling disabled. The SDK has detected the application is in the background and is not attempting to update the flag cache. + * - `'OFFLINE'`: The SDK has detected that the mobile device does not have an active network connection so has ceased flag update attempts until the network status changes. + * - `'SET_OFFLINE'`: (Android specific enum value) The SDK has been explicitly set offline, either in the initial configuration, by setOffline(), or as a result of failed authentication to LaunchDarkly. The SDK will stay offline unless setOnline() is called. + * - `'SHUTDOWN'`: (Android specific enum value) The shutdown state indicates the SDK has been permanently shutdown as a result of a call to close(). + * - `'ESTABLISHING_STREAMING_CONNECTION'`: (iOS specific enum value) The SDK is attempting to connect to LaunchDarkly by streaming. + * * @returns * A promise containing a LDConnectionMode enum value representing the status of the connection to LaunchDarkly. */ @@ -882,7 +757,18 @@ declare module 'launchdarkly-react-native-client-sdk' { /** * Returns the most recent connection failure reason or null. + * + * Reasons include: * + * - `'NONE'`: This indicates when no error has been recorded. + * - `'UNKNOWN_ERROR'`: This indicates when there is an internal error in the stream request. + * - `'UNAUTHORIZED'`: (iOS specific enum value) This indicates when an incorrect mobile key is provided. + * - `'HTTP_ERROR'`: (iOS specific enum value) This indicates when an error with an HTTP error code is present. + * - `'UNEXPECTED_RESPONSE_CODE'`: (Android specific enum value) This indicates the LDFailure is an instance of LDInvalidResponseCodeFailure. See Android documentation for more details. + * - `'UNEXPECTED_STREAM_ELEMENT_TYPE'`: (Android specific enum value) An event was received through the stream was had an unknown event name. This could indicate a newer SDK is available if new event types have become available through the flag stream since the SDKs release. + * - `'NETWORK_FAILURE'`: (Android specific enum value) A network request for polling, or the EventSource stream reported a failure. + * - `'INVALID_RESPONSE_BODY'`: (Android specific enum value) A response body received either through polling or streaming was unable to be parsed. + * * @returns * A promise containing a LDFailureReason enum value representing the reason for the most recently failed * connection to LaunchDarkly, or null if a failed connection has yet to occur. From f975de366125d14965d8904ce60ad4bb36282401 Mon Sep 17 00:00:00 2001 From: Ben Woskow Date: Wed, 28 Apr 2021 09:11:01 -0700 Subject: [PATCH 08/32] Releasing version 4.0.3 --- CHANGELOG.md | 4 + index.d.ts | 220 ++++++---------------- ios/LaunchdarklyReactNativeClient.podspec | 2 +- package-lock.json | 2 +- package.json | 6 +- 5 files changed, 62 insertions(+), 172 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7737888..05ea3ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to the LaunchDarkly React Native SDK will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org). +## [4.0.3] - 2021-04-28 +### Fixed: +- The `LDEvaluationReasonErrorKind`, `LDEvaluationReasonKind`, `LDConnectionMode`, and `LDFailureReason` enum TypeScript types were undefined when evaluated at runtime due to being defined in an ambient context. + ## [4.0.2] - 2021-04-23 ### Fixed: - Android: Fixed an issue where the `jsonVariationDetail` method in `LDClient` returned `Promise>` instead of the declared return type of `Promise>>`. diff --git a/index.d.ts b/index.d.ts index 9c01977..1bd1d8e 100644 --- a/index.d.ts +++ b/index.d.ts @@ -237,190 +237,45 @@ declare module 'launchdarkly-react-native-client-sdk' { /** * Describes the kind of error which occurred when a flag evaluation was calculated. + * + * This extends String for backwards compatibility. In a future major release, references to this type + * may be replaced with String references. */ - export enum LDEvaluationReasonErrorKind { - /** - * The client is not able to establish a connection to LaunchDarkly yet. - * - * If there is a persistent feature store, the store does not yet contain flag data. - */ - CLIENT_NOT_READY = 'CLIENT_NOT_READY', - - /** - * The flag key did not match any known flag. - */ - FLAG_NOT_FOUND = 'FLAG_NOT_FOUND', - - /** - * The user object or user key was not provided. - */ - USER_NOT_SPECIFIED = 'USER_NOT_SPECIFIED', - - /** - * There was an internal inconsistency in the flag data. For example, a rule specified a nonexistent variation. - * - * This is an unusual condition that might require assistance from LaunchDarkly's Support team. - */ - MALFORMED_FLAG = 'MALFORMED_FLAG', - - /** - * The application code requested the flag value with a different data type than it actually is. - * - * For example, the code asked for a boolean when the flag type is actually a string. - */ - WRONG_TYPE = 'WRONG_TYPE', - - /** - * An unexpected error stopped flag evaluation. This could happen if you are using a persistent feature store and the database stops working. - * - * When this happens, the SDK always prints the specific error to the log. - */ - EXCEPTION = 'EXCEPTION', + export interface LDEvaluationReasonErrorKind extends String { } /** * Describes the reason behind how a flag evaluation was calculated. + * + * This extends String for backwards compatibility. In a future major release, references to this type + * may be replaced with String references. */ - export enum LDEvaluationReasonKind { - /** - * The flag is off and therefore returned its configured off value. - * - * This value appears on the dashboard next to "If targeting is off, serve:". - */ - OFF = 'OFF', - - /** - * The flag is on, but the user did not match any targets or rules, so it returned the value that appears on the dashboard under "Default rule." - */ - FALLTHROUGH = 'FALLTHROUGH', - - /** - * The user key was specifically targeted for this flag in the "Target individual users" section. - */ - TARGET_MATCH = 'TARGET_MATCH', - - /** - * The user who encountered the flag matched one of the flag's rules. - */ - RULE_MATCH = 'RULE_MATCH', - - /** - * The flag had at least one prerequisite flag that either was off or did not return the desired variation. - * - * Because of this, the flag returned its "off" value. - */ - PREREQUISITE_FAILED = 'PREREQUISITE_FAILED', - - /** - * The flag could not be evaluated, so the default value was returned. - */ - ERROR = 'ERROR', + export interface LDEvaluationReasonKind extends String { } /** * Describes what state of connection to LaunchDarkly the SDK is in. + * + * This extends String for backwards compatibility. In a future major release, references to this type + * may be replaced with String references. */ - export enum LDConnectionMode { - /** - * The SDK is either connected to the flag stream, or is actively attempting to acquire a connection. - */ - STREAMING = 'STREAMING', - - /** - * The SDK was configured with streaming disabled, and is in foreground polling mode. - */ - POLLING = 'POLLING', - - /** - * (Android specific enum value) The SDK has detected the application is in the background and has transitioned to - * battery conscious background polling. - */ - BACKGROUND_POLLING = 'BACKGROUND_POLLING', - - /** - * (Android specific enum value) The SDK was configured with background polling disabled. The SDK has detected - * the application is in the background and is not attempting to update the flag cache. - */ - BACKGROUND_DISABLED = 'BACKGROUND_DISABLED', - - /** - * The SDK has detected that the mobile device does not have an active network connection so has ceased flag update - * attempts until the network status changes. - */ - OFFLINE = 'OFFLINE', - - /** - * (Android specific enum value) The SDK has been explicitly set offline, either in the initial configuration, - * by setOffline(), or as a result of failed authentication to LaunchDarkly. - * The SDK will stay offline unless setOnline() is called. - */ - SET_OFFLINE = 'SET_OFFLINE', - - /** - * (Android specific enum value) The shutdown state indicates the SDK has been permanently shutdown as a result of - * a call to close(). - */ - SHUTDOWN = 'SHUTDOWN', - - /** - * (iOS specific enum value) The SDK is attempting to connect to LaunchDarkly by streaming. - */ - ESTABLISHING_STREAMING_CONNECTION = 'ESTABLISHING_STREAMING_CONNECTION', + export interface LDConnectionMode extends String { } /** * Describes why a connection request to LaunchDarkly failed. + * + * This extends String for backwards compatibility. In a future major release, references to this type + * may be replaced with String references. */ - export enum LDFailureReason { - /** - * This indicates when no error has been recorded. - */ - NONE = 'NONE', - - /** - * This indicates when there is an internal error in the stream request. - */ - UKNOWN_ERROR ='UNKNOWN_ERROR', - - /** - * (iOS specific enum value) This indicates when an incorrect mobile key is provided. - */ - UNAUTHORIZED = 'UNAUTHORIZED', - - /** - * (iOS specific enum value) This indicates when an error with an HTTP error code is present. - */ - HTTP_ERROR = 'HTTP_ERROR', - - /** - * (Android specific enum value) This indicates the LDFailure is an instance of LDInvalidResponseCodeFailure. - * See Android documentation for more details. - */ - UNEXPECTED_RESPONSE_CODE = 'UNEXPECTED_RESPONSE_CODE', - - /** - * (Android specific enum value) An event was received through the stream was had an unknown event name. - * This could indicate a newer SDK is available if new event types have become available through the - * flag stream since the SDKs release. - */ - UNEXPECTED_STREAM_ELEMENT_TYPE = 'UNEXPECTED_STREAM_ELEMENT_TYPE', - - /** - * (Android specific enum value) A network request for polling, or the EventSource stream reported a failure. - */ - NETWORK_FAILURE = 'NETWORK_FAILURE', - - /** - * (Android specific enum value) A response body received either through polling or streaming was unable to be parsed. - */ - INVALID_RESPONSE_BODY = 'INVALID_RESPONSE_BODY', + export interface LDFailureReason extends String { } /** * The flag is off and therefore returned its configured off value. */ export type LDEvaluationReasonOff = { - kind: LDEvaluationReasonKind.OFF; + kind: 'OFF'; }; /** @@ -428,21 +283,21 @@ declare module 'launchdarkly-react-native-client-sdk' { * on the dashboard under "Default rule." */ export type LDEvaluationReasonFallthrough = { - kind: LDEvaluationReasonKind.FALLTHROUGH; + kind: 'FALLTHROUGH'; }; /** * The user key was specifically targeted for this flag in the "Target individual users" section. */ export type LDEvaluationReasonTargetMatch = { - kind: LDEvaluationReasonKind.TARGET_MATCH; + kind: 'TARGET_MATCH'; }; /** * The user who encountered the flag matched one of the flag's rules. */ export type LDEvaluationReasonRuleMatch = { - kind: LDEvaluationReasonKind.RULE_MATCH; + kind: 'RULE_MATCH'; /** * The positional index of the matched rule (0 for the first rule). @@ -460,7 +315,7 @@ declare module 'launchdarkly-react-native-client-sdk' { * Because of this, the flag returned its "off" value. */ export type LDEvaluationReasonPrerequisiteFailed = { - kind: LDEvaluationReasonKind.PREREQUISITE_FAILED; + kind: 'PREREQUISITE_FAILED'; /** * The key of the prerequisite flag that failed. @@ -472,10 +327,19 @@ declare module 'launchdarkly-react-native-client-sdk' { * The flag could not be evaluated, so the default value was returned. */ export type LDEvaluationReasonError = { - kind: LDEvaluationReasonKind.ERROR; + kind: 'ERROR'; /** * The kind of error which occurred. + * + * Kinds of errors include: + * + * - `'CLIENT_NOT_READY'`: The client is not able to establish a connection to LaunchDarkly yet. If there is a persistent feature store, the store does not yet contain flag data. + * - `'FLAG_NOT_FOUND'`: The flag key did not match any known flag. + * - `'USER_NOT_SPECIFIED'`: The user object or user key was not provided. + * - `'MALFORMED_FLAG'`: There was an internal inconsistency in the flag data. For example, a rule specified a nonexistent variation. This is an unusual condition that might require assistance from LaunchDarkly's Support team. + * - `'WRONG_TYPE'`: The application code requested the flag value with a different data type than it actually is. For example, the code asked for a boolean when the flag type is actually a string. + * - `'EXCEPTION'`: An unexpected error stopped flag evaluation. This could happen if you are using a persistent feature store and the database stops working. When this happens, the SDK always prints the specific error to the log. */ errorKind: LDEvaluationReasonErrorKind; }; @@ -854,7 +718,18 @@ declare module 'launchdarkly-react-native-client-sdk' { /** * Returns the current state of the connection to LaunchDarkly. + * + * States include: * + * - `'STREAMING'`: The SDK is either connected to the flag stream, or is actively attempting to acquire a connection. + * - `'POLLING'`: The SDK was configured with streaming disabled, and is in foreground polling mode. + * - `'BACKGROUND_POLLING'`: (Android specific enum value) The SDK has detected the application is in the background and has transitioned to battery conscious background polling. + * - `'BACKGROUND_DISABLED'`: (Android specific enum value) The SDK was configured with background polling disabled. The SDK has detected the application is in the background and is not attempting to update the flag cache. + * - `'OFFLINE'`: The SDK has detected that the mobile device does not have an active network connection so has ceased flag update attempts until the network status changes. + * - `'SET_OFFLINE'`: (Android specific enum value) The SDK has been explicitly set offline, either in the initial configuration, by setOffline(), or as a result of failed authentication to LaunchDarkly. The SDK will stay offline unless setOnline() is called. + * - `'SHUTDOWN'`: (Android specific enum value) The shutdown state indicates the SDK has been permanently shutdown as a result of a call to close(). + * - `'ESTABLISHING_STREAMING_CONNECTION'`: (iOS specific enum value) The SDK is attempting to connect to LaunchDarkly by streaming. + * * @returns * A promise containing a LDConnectionMode enum value representing the status of the connection to LaunchDarkly. */ @@ -882,7 +757,18 @@ declare module 'launchdarkly-react-native-client-sdk' { /** * Returns the most recent connection failure reason or null. + * + * Reasons include: * + * - `'NONE'`: This indicates when no error has been recorded. + * - `'UNKNOWN_ERROR'`: This indicates when there is an internal error in the stream request. + * - `'UNAUTHORIZED'`: (iOS specific enum value) This indicates when an incorrect mobile key is provided. + * - `'HTTP_ERROR'`: (iOS specific enum value) This indicates when an error with an HTTP error code is present. + * - `'UNEXPECTED_RESPONSE_CODE'`: (Android specific enum value) This indicates the LDFailure is an instance of LDInvalidResponseCodeFailure. See Android documentation for more details. + * - `'UNEXPECTED_STREAM_ELEMENT_TYPE'`: (Android specific enum value) An event was received through the stream was had an unknown event name. This could indicate a newer SDK is available if new event types have become available through the flag stream since the SDKs release. + * - `'NETWORK_FAILURE'`: (Android specific enum value) A network request for polling, or the EventSource stream reported a failure. + * - `'INVALID_RESPONSE_BODY'`: (Android specific enum value) A response body received either through polling or streaming was unable to be parsed. + * * @returns * A promise containing a LDFailureReason enum value representing the reason for the most recently failed * connection to LaunchDarkly, or null if a failed connection has yet to occur. diff --git a/ios/LaunchdarklyReactNativeClient.podspec b/ios/LaunchdarklyReactNativeClient.podspec index a327d78..bbaf530 100644 --- a/ios/LaunchdarklyReactNativeClient.podspec +++ b/ios/LaunchdarklyReactNativeClient.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "LaunchdarklyReactNativeClient" - s.version = "4.0.2" + s.version = "4.0.3" s.summary = "LaunchdarklyReactNativeClient" s.description = <<-DESC LaunchdarklyReactNativeClient diff --git a/package-lock.json b/package-lock.json index 7fd9168..202cd92 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "launchdarkly-react-native-client-sdk", - "version": "4.0.1", + "version": "4.0.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 30af045..dc932ea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "launchdarkly-react-native-client-sdk", - "version": "4.0.2", + "version": "4.0.3", "description": "", "main": "index.js", "types": "index.d.ts", @@ -22,8 +22,8 @@ }, "homepage": "https://docs.launchdarkly.com/sdk/client-side/react-native", "peerDependencies": { - "react-native": "~0.63", - "react": "~16.13.0" + "react": "~16.13.0", + "react-native": "~0.63" }, "devDependencies": { "metro-react-native-babel-preset": "0.59.0", From 5ad049957be1f7350d936f0f163c69d8af09ee05 Mon Sep 17 00:00:00 2001 From: Joe Cieslik <5600929+torchhound@users.noreply.github.com> Date: Mon, 10 May 2021 17:44:27 -0700 Subject: [PATCH 09/32] Multi Environment (#65) * Update to iOS 5.4.0 (#48) * Bump iOS SDK version to 5.4.0 * Replace shared with get() * Replace old method names * Provide default values for EvaluationDetail * Fixed defaultValue for detail value * Changed individual nil coalesce to NSNull * Remove isDisableBackgroundPolling method (#52) * Update iOS base url (#49) * Update iOS base url * Add protocol * Added wrapper name and version to iOS and Android config (#50) * Added wrapper name and version to iOS and Android config * V4 not 5 * Rename wrapper * Add getVersion method (#51) * Added getVersion method * Revert package.json version bump * Fix version case * Fix minor PR feedback * Update index.d.ts Co-authored-by: Ben Woskow <48036130+bwoskow-ld@users.noreply.github.com> Co-authored-by: Ben Woskow <48036130+bwoskow-ld@users.noreply.github.com> Co-authored-by: Ben Woskow <48036130+bwoskow-ld@users.noreply.github.com> * Rename fallback to defaultValue (#53) * Rename fallback to defaultValue * Fix awkward wording defaultValue value * Fix defaultValue value line breaks * Improved resiliency when running in Android (#54) * Add new config values (#55) * Added new config values to iOS and Android * Added typescript config * PR feedback * Fix millis, add default doc * fix common build + add tests for recent introductions (#56) * Added secondaryMobileKeys and getForMobileKey * Added primaryEnvironmentName constant * Fix Android build errors * Fix build errors * Fix Android config error * CI test fixes * Catch LD exception without crashing * fix: start background thread for identify rather than running it (#66) * Bump Android SDK version to 2.14.1 (#59) * Update Android to 2.14.1, change setBaseUri to setPollUri, change floatVariation to doubleVariation * Fix float to double * Doublevalue on non detail * Run CI against v4.0 * URL fix iOS * Add isInitialized to iOS, check initialization in configure (#60) * Added isInitialized to iOS * Add init check to configure * Base url iOS fix, v4.0 ci * Var not func iOS, remove getMap * disable auto-alias in iOS (#61) * Add configureWithTimeout method (#58) * Added configureTimeout method * Fix timeout type on iOS * Simplify timeout nil check * Added param labels, Java syntax fixes * Small fixes * Make timeout final * Remove unnecessary catch * Fix baseUrl * Fix Android config error * Test against v4.0 hello branch * Fix tests * timeout never nil inside check * Convert Int to TimeInterval * Fixed iOS startWaitSeconds * Catch LDException * Fix merge conflict in iOS bridge * Fix merge conflict in index.js * PR feedback * Fix unused timeoutClient * Remove unnecessary ConfigEntryType * Remove StringSet * If let in timeout check * Configure method now takes optional timeout parameter instead of separate method * Renames for consistency (#62) * up-leveling the override for the default polling uri so it affects android too (#63) * Added ip, avatar, and allUserAttributesPrivate (#57) * Added ip, avatar, and allUserAttributesPrivate * Updated test-types.ts, fix case typo * Special case allUserAttributesPrivate * Fix ReadableMap loading of all private * CI fix * Fix string to URL baseUrl * Change to non-default values in test, combine lines in config.yml * Simplify allAttrsPrivate if * resolve breakage with latest merge Co-authored-by: Ben Woskow * Multi environment works in Android, fixed bad config type * Fix allUserAttributesPrivate * Fix typescript test * No StringSet for wrapper, change to ldClient for isInitialized * Forgot nil check * environment param * Select environment via method parameter * Check for environment undefined in JS * iOS error fixes * Fix iOS bridge, add missing param and try to Android * Remove v4.0 branch hello rn for CI * Fixed all flags and better long conversion * A bunch of let to const, change default to test in multi env ts test, remove ldClient var from iOS * Remove internal comments from Android * Added back 2 Android comments, Double to NSNumber reversion, var to let reversion, ternary to function * Get default environment name from underlying SDKs * Cast NSNumber to Double to fix compilation error * getVersion public, remove ldClient from Android * Use get() * Remove unnecessary get() * Try instead of null check * as! Double to doubleValue, toIntExact to intValue, change Android env name, improve try * Fix listeners in multiple environments, change continue to comment in Android try/catch init * Add semicolon in between environment and flagKey or listenerId string concat * Environment concatenation function * Swift param labels * Added self, remove conditional cast to Any * Remove unnecessary nil coalescing * Capitalize ldClient * getForMobileKey Android * Removed unnecessary try, added final keyword * Revert some nonnull and Double types * Inline primaryEnvironmentName iOS Co-authored-by: Ben Woskow <48036130+bwoskow-ld@users.noreply.github.com> Co-authored-by: Ben Woskow --- .../LaunchdarklyReactNativeClientModule.java | 697 +++++------------- index.d.ts | 89 ++- index.js | 234 +++--- ios/LaunchdarklyReactNativeClient.swift | 360 ++++----- ios/LaunchdarklyReactNativeClientBridge.m | 104 +-- test-types.ts | 7 + 6 files changed, 658 insertions(+), 833 deletions(-) diff --git a/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java b/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java index 610cd52..97961e4 100644 --- a/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java +++ b/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java @@ -50,23 +50,8 @@ import timber.log.Timber; -/** - * Module bound with React Native to be called from JavaScript - */ public class LaunchdarklyReactNativeClientModule extends ReactContextBaseJavaModule { - /** - * An enum of all the supported configuration entries for LDClient configuration. - * - *

- * Each enum value has a lookup key, entry type, and internal setter associated with it. The - * lookup key is used to get the configuration value from a ReadableMap (JsonObject passed over - * the react native bridge). The entry type specifies the base type looked up from the - * ReadableMap as well as any additional conversion needed before setting the internal LDConfig - * option, see @see ConfigEntryType for more. The internal setter is a String name of the setter - * method used to pass the parsed configuration value into a LDConfig builder used for LDClient - * setup. - */ enum ConfigMapping { CONFIG_MOBILE_KEY("mobileKey", ConfigEntryType.String, "setMobileKey"), CONFIG_BASE_URI("pollUri", ConfigEntryType.Uri, "setPollUri"), @@ -87,7 +72,8 @@ enum ConfigMapping { CONFIG_WRAPPER_VERSION("wrapperVersion", ConfigEntryType.String, "setWrapperVersion"), CONFIG_MAX_CACHED_USERS("maxCachedUsers", ConfigEntryType.Integer, "setMaxCachedUsers"), CONFIG_DIAGNOSTIC_OPT_OUT("diagnosticOptOut", ConfigEntryType.Boolean, "setDiagnosticOptOut"), - CONFIG_DIAGNOSTIC_RECORDING_INTERVAL("diagnosticRecordingIntervalMillis", ConfigEntryType.Integer, "setDiagnosticRecordingIntervalMillis"); + CONFIG_DIAGNOSTIC_RECORDING_INTERVAL("diagnosticRecordingIntervalMillis", ConfigEntryType.Integer, "setDiagnosticRecordingIntervalMillis"), + CONFIG_SECONDARY_MOBILE_KEYS("secondaryMobileKeys", ConfigEntryType.Map, "setSecondaryMobileKeys"); final String key; final ConfigEntryType type; @@ -112,17 +98,6 @@ void loadFromMap(ReadableMap map, LDConfig.Builder builder) { } } - /** - * An enum of all the supported configuration entries for LDUser configuration. - * - *

- * Each enum value has a lookup key, entry type, and internal setter associated with it. The - * lookup key is used to get the configuration value from a ReadableMap (JsonObject passed over - * the react native bridge). The entry type specifies the base type looked up from the - * ReadableMap as well as any additional conversion needed before setting the internal LDUser - * option, @see ConfigEntryType for more. The internal setter is a String name of the setter - * method used to pass the parsed configuration value into a LDUser builder. - */ enum UserConfigMapping { USER_ANONYMOUS("anonymous", ConfigEntryType.Boolean, "anonymous", null), USER_IP("ip", ConfigEntryType.String, "ip", "privateIp"), @@ -163,9 +138,6 @@ void loadFromMap(ReadableMap map, LDUser.Builder builder, Set privateAtt } } - // The LDClient instance - private LDClient ldClient; - // Current feature flag listeners private Map listeners = new HashMap<>(); private Map connectionModeListeners = new HashMap<>(); private Map allFlagsListeners = new HashMap<>(); @@ -188,17 +160,16 @@ public String getName() { return "LaunchdarklyReactNativeClient"; } - // Constants used in promise rejection private static final String ERROR_INIT = "E_INITIALIZE"; private static final String ERROR_IDENTIFY = "E_IDENTIFY"; private static final String ERROR_CLOSE = "E_CLOSE"; private static final String ERROR_UNKNOWN = "E_UNKNOWN"; - // Prefix for events sent over the React Native event bridge private static final String FLAG_PREFIX = "LaunchDarkly-Flag-"; private static final String ALL_FLAGS_PREFIX = "LaunchDarkly-All-Flags-"; private static final String CONNECTION_MODE_PREFIX = "LaunchDarkly-Connection-Mode-"; + private static final String DEFAULT_ENVIRONMENT = "default"; /** * Called automatically by the React Native bridging layer to associate constants with the @@ -212,41 +183,31 @@ public Map getConstants() { constants.put("FLAG_PREFIX", FLAG_PREFIX); constants.put("ALL_FLAGS_PREFIX", ALL_FLAGS_PREFIX); constants.put("CONNECTION_MODE_PREFIX", CONNECTION_MODE_PREFIX); + constants.put("DEFAULT_ENVIRONMENT", DEFAULT_ENVIRONMENT); return constants; } - /** - * React Method called from JavaScript to initialize the LDClient using the supplied - * configuration. - * - * @param config LDConfig configuration, @see configBuild - * @param user LDUser configuration, @see userBuild - * @param promise Either rejected if an error was encountered, otherwise resolved with null - * once client is initialized. - */ @ReactMethod public void configure(ReadableMap config, ReadableMap user, final Promise promise) { internalConfigure(config, user, null, promise); } - /** - * React Method called from JavaScript to initialize the LDClient using the supplied - * configuration with a timeout. - * - * @param config LDConfig configuration, @see configBuild - * @param user LDUser configuration, @see userBuild - * @param timeout Integer that blocks until the latest feature flags have been retrieved from LaunchDarkly - * @param promise Either rejected if an error was encountered, otherwise resolved with null - * once client is initialized. - */ @ReactMethod public void configureWithTimeout(ReadableMap config, ReadableMap user, Integer timeout, final Promise promise) { internalConfigure(config, user, timeout, promise); } private void internalConfigure(ReadableMap config, ReadableMap user, final Integer timeout, final Promise promise) { - if (ldClient != null) { - promise.reject(ERROR_INIT, "Client was already initialized"); + try { + if (LDClient.get() != null) { + promise.reject(ERROR_INIT, "Client was already initialized"); + return; + } + } catch (LaunchDarklyException e) { + //This exception indicates that the SDK has not been initialized yet + } catch (Exception e) { + Timber.w(e); + promise.reject(ERROR_INIT, e); return; } @@ -264,7 +225,7 @@ private void internalConfigure(ReadableMap config, ReadableMap user, final Integ } if (config.hasKey("allUserAttributesPrivate") - && config.getType("allUserAttributesPrivate").equals(ConfigEntryType.Boolean.getReadableType()) + && config.getType("allUserAttributesPrivate").equals(ConfigEntryType.Boolean.getReadableType()) && config.getBoolean("allUserAttributesPrivate")) { ldConfigBuilder.allAttributesPrivate(); } @@ -275,23 +236,12 @@ private void internalConfigure(ReadableMap config, ReadableMap user, final Integ Thread background = new Thread(new Runnable() { @Override public void run() { - try { - if (timeout != null) { - ldClient = LDClient.init(application, ldConfigBuilder.build(), userBuilder.build(), timeout).get(); - } else { - ldClient = LDClient.init(application, ldConfigBuilder.build(), userBuilder.build()).get(); - } - promise.resolve(null); - } catch (InterruptedException e) { - Timber.w(e); - promise.reject(ERROR_INIT, e); - } catch (ExecutionException e) { - Timber.w(e); - promise.reject(ERROR_INIT, e); - } catch (LaunchDarklyException e) { - Timber.w(e); - promise.reject(ERROR_INIT, e); + if (timeout != null) { + LDClient.init(application, ldConfigBuilder.build(), userBuilder.build(), timeout); + } else { + LDClient.init(application, ldConfigBuilder.build(), userBuilder.build()); } + promise.resolve(null); } }); @@ -302,15 +252,6 @@ public void run() { } } - /** - * Create a LDConfig.Builder using configuration values from the options ReadableMap. - * - *

- * This will look for all configuration values specified in {@link ConfigMapping}. - * - * @param options A ReadableMap of configuration options - * @return A LDConfig.Builder configured with options - */ private LDConfig.Builder configBuild(ReadableMap options) { LDConfig.Builder builder = new LDConfig.Builder(); @@ -321,15 +262,6 @@ private LDConfig.Builder configBuild(ReadableMap options) { return builder; } - /** - * Create a LDUser.Builder using configuration values from the options ReadableMap. - * - *

- * This will look for all configuration values specified in {@link UserConfigMapping}. - * - * @param options A ReadableMap of configuration options - * @return A LDUser.Builder configured with options - */ private LDUser.Builder userBuild(ReadableMap options) { if (!options.hasKey("key")) { return null; @@ -433,180 +365,101 @@ private LDUser.Builder userBuild(ReadableMap options) { } @ReactMethod - public void boolVariation(String flagKey, Promise promise) { - boolVariationDefaultValue(flagKey, null, promise); + public void boolVariation(String flagKey, String environment, Promise promise) { + boolVariationDefaultValue(flagKey, null, environment, promise); } - /** - * Looks up the current value for a flag, in the case of any issues, returns the given default - * value. - * - * @param flagKey The lookup key of the flag. - * @param defaultValue A default value to return if current value could not be acquired. - * @param promise Used to return the result to React Native - */ @ReactMethod - public void boolVariationDefaultValue(String flagKey, Boolean defaultValue, Promise promise) { + public void boolVariationDefaultValue(String flagKey, Boolean defaultValue, String environment, Promise promise) { try { - promise.resolve(ldClient.boolVariation(flagKey, defaultValue)); + promise.resolve(LDClient.getForMobileKey(environment).boolVariation(flagKey, defaultValue)); } catch (Exception e) { promise.resolve(defaultValue); } } @ReactMethod - public void intVariation(String flagKey, Promise promise) { - intVariationDefaultValue(flagKey, null, promise); + public void intVariation(String flagKey, String environment, Promise promise) { + intVariationDefaultValue(flagKey, null, environment, promise); } - /** - * Looks up the current value for a flag, in the case of any issues, returns the given default - * value. - * - * @param flagKey The lookup key of the flag. - * @param defaultValue A default value to return if current value could not be acquired. - * @param promise Used to return the result to React Native - */ @ReactMethod - public void intVariationDefaultValue(String flagKey, Integer defaultValue, Promise promise) { + public void intVariationDefaultValue(String flagKey, Integer defaultValue, String environment, Promise promise) { try { - promise.resolve(ldClient.intVariation(flagKey, defaultValue)); + promise.resolve(LDClient.getForMobileKey(environment).intVariation(flagKey, defaultValue)); } catch (Exception e) { promise.resolve(defaultValue); } } @ReactMethod - public void floatVariation(String flagKey, Promise promise) { - floatVariationDefaultValue(flagKey, null, promise); + public void floatVariation(String flagKey, String environment, Promise promise) { + floatVariationDefaultValue(flagKey, null, environment, promise); } - /** - * Looks up the current value for a flag, in the case of any issues, returns the given default - * value. - * - * @param flagKey The lookup key of the flag. - * @param defaultValue A default value to return if current value could not be acquired. - * @param promise Used to return the result to React Native - */ @ReactMethod - public void floatVariationDefaultValue(String flagKey, Double defaultValue, Promise promise) { + public void floatVariationDefaultValue(String flagKey, Float defaultValue, String environment, Promise promise) { try { - promise.resolve(ldClient.doubleVariation(flagKey, defaultValue)); + promise.resolve(LDClient.getForMobileKey(environment).doubleVariation(flagKey, defaultValue.doubleValue())); } catch (Exception e) { promise.resolve(defaultValue); } } @ReactMethod - public void stringVariation(String flagKey, Promise promise) { - stringVariationDefaultValue(flagKey, null, promise); + public void stringVariation(String flagKey, String environment, Promise promise) { + stringVariationDefaultValue(flagKey, null, environment, promise); } - /** - * Looks up the current value for a flag, in the case of any issues, returns the given default - * value. - * - * @param flagKey The lookup key of the flag. - * @param defaultValue A default value to return if current value could not be acquired. - * @param promise Used to return the result to React Native - */ @ReactMethod - public void stringVariationDefaultValue(String flagKey, String defaultValue, Promise promise) { + public void stringVariationDefaultValue(String flagKey, String defaultValue, String environment, Promise promise) { try { - promise.resolve(ldClient.stringVariation(flagKey, defaultValue)); + promise.resolve(LDClient.getForMobileKey(environment).stringVariation(flagKey, defaultValue)); } catch (Exception e) { promise.resolve(defaultValue); } } - /** - * Looks up the current value for a flag, in the case of any issues, returns null - * value. - * - * @param flagKey The lookup key of the flag. - * @param promise Used to return the result to React Native - */ @ReactMethod - public void jsonVariationNone(String flagKey, Promise promise) { - jsonVariationBase(flagKey, null, promise); + public void jsonVariationNone(String flagKey, String environment, Promise promise) { + jsonVariationBase(flagKey, null, environment, promise); } - /** - * Looks up the current value for a flag, in the case of any issues, returns the given default - * value. - * - * @param flagKey The lookup key of the flag. - * @param defaultValue A default value to return if current value could not be acquired. - * @param promise Used to return the result to React Native - */ @ReactMethod - public void jsonVariationNumber(String flagKey, Double defaultValue, Promise promise) { - jsonVariationBase(flagKey, new JsonPrimitive(defaultValue), promise); + public void jsonVariationNumber(String flagKey, Double defaultValue, String environment, Promise promise) { + jsonVariationBase(flagKey, new JsonPrimitive(defaultValue), environment, promise); } - /** - * Looks up the current value for a flag, in the case of any issues, returns the given default - * value. - * - * @param flagKey The lookup key of the flag. - * @param defaultValue A default value to return if current value could not be acquired. - * @param promise Used to return the result to React Native - */ @ReactMethod - public void jsonVariationBool(String flagKey, Boolean defaultValue, Promise promise) { - jsonVariationBase(flagKey, new JsonPrimitive(defaultValue), promise); + public void jsonVariationBool(String flagKey, Boolean defaultValue, String environment, Promise promise) { + jsonVariationBase(flagKey, new JsonPrimitive(defaultValue), environment, promise); } - /** - * Looks up the current value for a flag, in the case of any issues, returns the given default - * value. - * - * @param flagKey The lookup key of the flag. - * @param defaultValue A default value to return if current value could not be acquired. - * @param promise Used to return the result to React Native - */ @ReactMethod - public void jsonVariationString(String flagKey, String defaultValue, Promise promise) { - jsonVariationBase(flagKey, new JsonPrimitive(defaultValue), promise); + public void jsonVariationString(String flagKey, String defaultValue, String environment, Promise promise) { + jsonVariationBase(flagKey, new JsonPrimitive(defaultValue), environment, promise); } - /** - * Looks up the current value for a flag, in the case of any issues, returns the given default - * value. - * - * @param flagKey The lookup key of the flag. - * @param defaultValue A default value to return if current value could not be acquired. - * @param promise Used to return the result to React Native - */ @ReactMethod - public void jsonVariationArray(String flagKey, ReadableArray defaultValue, Promise promise) { - jsonVariationBase(flagKey, toJsonArray(defaultValue), promise); + public void jsonVariationArray(String flagKey, ReadableArray defaultValue, String environment, Promise promise) { + jsonVariationBase(flagKey, toJsonArray(defaultValue), environment, promise); } - /** - * Looks up the current value for a flag, in the case of any issues, returns the given default - * value. - * - * @param flagKey The lookup key of the flag. - * @param defaultValue A default value to return if current value could not be acquired. - * @param promise Used to return the result to React Native - */ @ReactMethod - public void jsonVariationObject(String flagKey, ReadableMap defaultValue, Promise promise) { - jsonVariationBase(flagKey, toJsonObject(defaultValue), promise); + public void jsonVariationObject(String flagKey, ReadableMap defaultValue, String environment, Promise promise) { + jsonVariationBase(flagKey, toJsonObject(defaultValue), environment, promise); } @ReactMethod - public void boolVariationDetail(String flagKey, Promise promise) { - boolVariationDetailDefaultValue(flagKey, null, promise); + public void boolVariationDetail(String flagKey, String environment, Promise promise) { + boolVariationDetailDefaultValue(flagKey, null, environment, promise); } @ReactMethod - public void boolVariationDetailDefaultValue(String flagKey, Boolean defaultValue, Promise promise) { + public void boolVariationDetailDefaultValue(String flagKey, Boolean defaultValue, String environment, Promise promise) { EvaluationDetail detailResult; try { - detailResult = ldClient.boolVariationDetail(flagKey, defaultValue); + detailResult = LDClient.getForMobileKey(environment).boolVariationDetail(flagKey, defaultValue); } catch (Exception e) { Timber.w(e); detailResult = new EvaluationDetail(EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION), null, defaultValue); @@ -617,15 +470,15 @@ public void boolVariationDetailDefaultValue(String flagKey, Boolean defaultValue } @ReactMethod - public void intVariationDetail(String flagKey, Promise promise) { - intVariationDetailDefaultValue(flagKey, null, promise); + public void intVariationDetail(String flagKey, String environment, Promise promise) { + intVariationDetailDefaultValue(flagKey, null, environment, promise); } @ReactMethod - public void intVariationDetailDefaultValue(String flagKey, Integer defaultValue, Promise promise) { + public void intVariationDetailDefaultValue(String flagKey, Integer defaultValue, String environment, Promise promise) { EvaluationDetail detailResult; try { - detailResult = ldClient.intVariationDetail(flagKey, defaultValue); + detailResult = LDClient.getForMobileKey(environment).intVariationDetail(flagKey, defaultValue); } catch (Exception e) { Timber.w(e); detailResult = new EvaluationDetail(EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION), null, defaultValue); @@ -636,18 +489,19 @@ public void intVariationDetailDefaultValue(String flagKey, Integer defaultValue, } @ReactMethod - public void floatVariationDetail(String flagKey, Promise promise) { - floatVariationDetailDefaultValue(flagKey, null, promise); + public void floatVariationDetail(String flagKey, String environment, Promise promise) { + floatVariationDetailDefaultValue(flagKey, null, environment, promise); } @ReactMethod - public void floatVariationDetailDefaultValue(String flagKey, Double defaultValue, Promise promise) { + public void floatVariationDetailDefaultValue(String flagKey, Float defaultValue, String environment, Promise promise) { EvaluationDetail detailResult; + Double doubleValue = defaultValue.doubleValue(); try { - detailResult = ldClient.doubleVariationDetail(flagKey, defaultValue); + detailResult = LDClient.getForMobileKey(environment).doubleVariationDetail(flagKey, doubleValue); } catch (Exception e) { Timber.w(e); - detailResult = new EvaluationDetail(EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION), null, defaultValue); + detailResult = new EvaluationDetail(EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION), null, doubleValue); } JsonObject jsonObject = gson.toJsonTree(detailResult).getAsJsonObject(); WritableMap detailMap = fromJsonObject(jsonObject); @@ -655,15 +509,15 @@ public void floatVariationDetailDefaultValue(String flagKey, Double defaultValue } @ReactMethod - public void stringVariationDetail(String flagKey, Promise promise) { - stringVariationDetailDefaultValue(flagKey, null, promise); + public void stringVariationDetail(String flagKey, String environment, Promise promise) { + stringVariationDetailDefaultValue(flagKey, null, environment, promise); } @ReactMethod - public void stringVariationDetailDefaultValue(String flagKey, String defaultValue, Promise promise) { + public void stringVariationDetailDefaultValue(String flagKey, String defaultValue, String environment, Promise promise) { EvaluationDetail detailResult; try { - detailResult = ldClient.stringVariationDetail(flagKey, defaultValue); + detailResult = LDClient.getForMobileKey(environment).stringVariationDetail(flagKey, defaultValue); } catch (Exception e) { Timber.w(e); detailResult = new EvaluationDetail(EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION), null, defaultValue); @@ -674,55 +528,49 @@ public void stringVariationDetailDefaultValue(String flagKey, String defaultValu } @ReactMethod - public void jsonVariationDetailNone(String flagKey, Promise promise) { - jsonVariationDetailBase(flagKey, null, promise); + public void jsonVariationDetailNone(String flagKey, String environment, Promise promise) { + jsonVariationDetailBase(flagKey, null, environment, promise); } @ReactMethod - public void jsonVariationDetailNumber(String flagKey, Double defaultValue, Promise promise) { - jsonVariationDetailBase(flagKey, new JsonPrimitive(defaultValue), promise); + public void jsonVariationDetailNumber(String flagKey, Double defaultValue, String environment, Promise promise) { + jsonVariationDetailBase(flagKey, new JsonPrimitive(defaultValue), environment, promise); } @ReactMethod - public void jsonVariationDetailBool(String flagKey, Boolean defaultValue, Promise promise) { - jsonVariationDetailBase(flagKey, new JsonPrimitive(defaultValue), promise); + public void jsonVariationDetailBool(String flagKey, Boolean defaultValue, String environment, Promise promise) { + jsonVariationDetailBase(flagKey, new JsonPrimitive(defaultValue), environment, promise); } @ReactMethod - public void jsonVariationDetailString(String flagKey, String defaultValue, Promise promise) { - jsonVariationDetailBase(flagKey, new JsonPrimitive(defaultValue), promise); + public void jsonVariationDetailString(String flagKey, String defaultValue, String environment, Promise promise) { + jsonVariationDetailBase(flagKey, new JsonPrimitive(defaultValue), environment, promise); } @ReactMethod - public void jsonVariationDetailArray(String flagKey, ReadableArray defaultValue, Promise promise) { - jsonVariationDetailBase(flagKey, toJsonArray(defaultValue), promise); + public void jsonVariationDetailArray(String flagKey, ReadableArray defaultValue, String environment, Promise promise) { + jsonVariationDetailBase(flagKey, toJsonArray(defaultValue), environment, promise); } @ReactMethod - public void jsonVariationDetailObject(String flagKey, ReadableMap defaultValue, Promise promise) { - jsonVariationDetailBase(flagKey, toJsonObject(defaultValue), promise); + public void jsonVariationDetailObject(String flagKey, ReadableMap defaultValue, String environment, Promise promise) { + jsonVariationDetailBase(flagKey, toJsonObject(defaultValue), environment, promise); } - /** - * Helper for jsonVariation methods. - * - * @param flagKey The lookup key of the flag. - * @param defaultValue A default value to return if the current value could not be acquired. - * @param promise Used to return the result to React Native. - */ - private void jsonVariationBase(String flagKey, JsonElement defaultValue, Promise promise) { - try { - JsonElement jsonElement = ldClient.jsonVariation(flagKey, defaultValue); + private void jsonVariationBase(String flagKey, JsonElement defaultValue, String environment, Promise promise) { + JsonElement jsonElement; + try { + jsonElement = LDClient.getForMobileKey(environment).jsonVariation(flagKey, defaultValue); resolveJsonElement(promise, jsonElement); } catch (Exception e) { resolveJsonElement(promise, defaultValue); } } - private void jsonVariationDetailBase(String flagKey, JsonElement defaultValue, Promise promise) { + private void jsonVariationDetailBase(String flagKey, JsonElement defaultValue, String environment, Promise promise) { EvaluationDetail jsonElementDetail; try { - jsonElementDetail = ldClient.jsonVariationDetail(flagKey, defaultValue); + jsonElementDetail = LDClient.getForMobileKey(environment).jsonVariationDetail(flagKey, defaultValue); } catch (Exception e) { Timber.w(e); jsonElementDetail = new EvaluationDetail(EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION), null, defaultValue); @@ -730,14 +578,6 @@ private void jsonVariationDetailBase(String flagKey, JsonElement defaultValue, P resolveJsonElementDetail(promise, jsonElementDetail); } - - /** - * Converts the jsonElement to a React Native bridge compatible type and resolves the promise - * with it's value. - * - * @param promise Promise to resolve - * @param jsonElement Value to convert and resolve promise with. - */ private void resolveJsonElement(Promise promise, JsonElement jsonElement) { if (jsonElement == null || jsonElement.isJsonNull()) { promise.resolve(null); @@ -765,319 +605,237 @@ private void resolveJsonElementDetail(Promise promise, EvaluationDetail flags = ldClient.allFlags(); - - // Convert map of all flags into WritableMap for React Native - WritableMap response = new WritableNativeMap(); - for (Map.Entry entry : flags.entrySet()) { - if (entry.getValue() == null) { - response.putNull(entry.getKey()); - } else if (entry.getValue() instanceof String) { - try { - JsonElement parsedJson = new JsonParser().parse((String) entry.getValue()); - if (parsedJson.isJsonObject()) { - response.putMap(entry.getKey(), fromJsonObject((JsonObject) parsedJson.getAsJsonObject())); - } else if (parsedJson.isJsonArray()) { - response.putArray(entry.getKey(), fromJsonArray((JsonArray) parsedJson.getAsJsonArray())); - } else { + try { + Map flags = LDClient.getForMobileKey(environment).allFlags(); + + WritableMap response = new WritableNativeMap(); + for (Map.Entry entry : flags.entrySet()) { + if (entry.getValue() == null) { + response.putNull(entry.getKey()); + } else if (entry.getValue() instanceof String) { + try { + JsonElement parsedJson = new JsonParser().parse((String) entry.getValue()); + if (parsedJson.isJsonObject()) { + response.putMap(entry.getKey(), fromJsonObject((JsonObject) parsedJson.getAsJsonObject())); + } else if (parsedJson.isJsonArray()) { + response.putArray(entry.getKey(), fromJsonArray((JsonArray) parsedJson.getAsJsonArray())); + } else { + response.putString(entry.getKey(),(String) entry.getValue()); + } + } catch (JsonParseException e) { response.putString(entry.getKey(),(String) entry.getValue()); } - } catch (JsonParseException e) { - response.putString(entry.getKey(),(String) entry.getValue()); - } - } else if (entry.getValue() instanceof Boolean) { - response.putBoolean(entry.getKey(), (Boolean) entry.getValue()); - } else if (entry.getValue() instanceof Double) { - response.putDouble(entry.getKey(), (Double) entry.getValue()); - } else if (entry.getValue() instanceof Float) { - response.putDouble(entry.getKey(), (Float) entry.getValue()); - } else if (entry.getValue() instanceof Integer) { - response.putInt(entry.getKey(), (Integer) entry.getValue()); - } else if (entry.getValue() instanceof JsonNull) { - response.putNull(entry.getKey()); - } else if (entry.getValue() instanceof JsonArray) { - response.putArray(entry.getKey(), fromJsonArray((JsonArray) entry.getValue())); - } else if (entry.getValue() instanceof JsonObject) { - response.putMap(entry.getKey(), fromJsonObject((JsonObject) entry.getValue())); - } else if (entry.getValue() instanceof JsonPrimitive) { - JsonPrimitive primitive = (JsonPrimitive) entry.getValue(); - if (primitive.isString()) { - response.putString(entry.getKey(), primitive.getAsString()); - } else if (primitive.isBoolean()) { - response.putBoolean(entry.getKey(), primitive.getAsBoolean()); - } else if (primitive.isNumber()) { - response.putDouble(entry.getKey(), primitive.getAsDouble()); + } else if (entry.getValue() instanceof Boolean) { + response.putBoolean(entry.getKey(), (Boolean) entry.getValue()); + } else if (entry.getValue() instanceof Double) { + response.putDouble(entry.getKey(), (Double) entry.getValue()); + } else if (entry.getValue() instanceof Float) { + response.putDouble(entry.getKey(), (Float) entry.getValue()); + } else if (entry.getValue() instanceof Integer) { + response.putInt(entry.getKey(), (Integer) entry.getValue()); + } else if (entry.getValue() instanceof JsonNull) { + response.putNull(entry.getKey()); + } else if (entry.getValue() instanceof JsonArray) { + response.putArray(entry.getKey(), fromJsonArray((JsonArray) entry.getValue())); + } else if (entry.getValue() instanceof JsonObject) { + response.putMap(entry.getKey(), fromJsonObject((JsonObject) entry.getValue())); + } else if (entry.getValue() instanceof JsonPrimitive) { + JsonPrimitive primitive = (JsonPrimitive) entry.getValue(); + if (primitive.isString()) { + response.putString(entry.getKey(), primitive.getAsString()); + } else if (primitive.isBoolean()) { + response.putBoolean(entry.getKey(), primitive.getAsBoolean()); + } else if (primitive.isNumber()) { + response.putDouble(entry.getKey(), primitive.getAsDouble()); + } } } + promise.resolve(response); + } catch (Exception e) { + Timber.w(e); } - promise.resolve(response); } - /** - * Runs the SDK's trackData method with a number as custom data - *

- * Separately typed methods are necessary at the React Native bridging layer requires that - * bridged method types disambiguate the value type. - * - * @param eventName Name of the event to track - * @param data The Double data to attach to the tracking event - */ @ReactMethod - public void trackNumber(String eventName, Double data) { + public void trackNumber(String eventName, Double data, String environment) { try { - ldClient.track(eventName, new JsonPrimitive(data)); + LDClient.getForMobileKey(environment).track(eventName, new JsonPrimitive(data)); } catch (Exception e) { Timber.w(e); } } - /** - * Runs the SDK's trackData method with a Boolean as custom data - *

- * Separately typed methods are necessary at the React Native bridging layer requires that - * bridged method types disambiguate the value type. - * - * @param eventName Name of the event to track - * @param data The Boolean data to attach to the tracking event - */ @ReactMethod - public void trackBool(String eventName, Boolean data) { + public void trackBool(String eventName, Boolean data, String environment) { try { - ldClient.track(eventName, new JsonPrimitive(data)); + LDClient.getForMobileKey(environment).track(eventName, new JsonPrimitive(data)); } catch (Exception e) { Timber.w(e); } } - /** - * Runs the SDK's trackData method with a String as custom data - *

- * Separately typed methods are necessary at the React Native bridging layer requires that - * bridged method types disambiguate the value type. - * - * @param eventName Name of the event to track - * @param data The String data to attach to the tracking event - */ @ReactMethod - public void trackString(String eventName, String data) { + public void trackString(String eventName, String data, String environment) { try { - ldClient.track(eventName, new JsonPrimitive(data)); + LDClient.getForMobileKey(environment).track(eventName, new JsonPrimitive(data)); } catch (Exception e) { Timber.w(e); } } - /** - * Runs the SDK's trackData method with an Array as custom data - *

- * Separately typed methods are necessary at the React Native bridging layer requires that - * bridged method types disambiguate the value type. - * - * @param eventName Name of the event to track - * @param data The Array data to attach to the tracking event - */ @ReactMethod - public void trackArray(String eventName, ReadableArray data) { + public void trackArray(String eventName, ReadableArray data, String environment) { try { - ldClient.track(eventName, toJsonArray(data)); + LDClient.getForMobileKey(environment).track(eventName, toJsonArray(data)); } catch (Exception e) { Timber.w(e); } } - /** - * Runs the SDK's trackData method with an object as custom data - *

- * Separately typed methods are necessary at the React Native bridging layer requires that - * bridged method types disambiguate the value type. - * - * @param eventName Name of the event to track - * @param data The Map(Object) data to attach to the tracking event - */ @ReactMethod - public void trackObject(String eventName, ReadableMap data) { + public void trackObject(String eventName, ReadableMap data, String environment) { try { - ldClient.track(eventName, toJsonObject(data)); + LDClient.getForMobileKey(environment).track(eventName, toJsonObject(data)); } catch (Exception e) { Timber.w(e); } } - /** - * Track an event with a custom name. - * - * @param eventName Name of the event - */ @ReactMethod - public void track(String eventName) { + public void track(String eventName, String environment) { try { - ldClient.track(eventName); + LDClient.getForMobileKey(environment).track(eventName); } catch (Exception e) { Timber.w(e); } } @ReactMethod - public void trackNumberMetricValue(String eventName, Double data, Double metricValue) { + public void trackNumberMetricValue(String eventName, Double data, Double metricValue, String environment) { try { - ldClient.track(eventName, new JsonPrimitive(data), metricValue); + LDClient.getForMobileKey(environment).track(eventName, new JsonPrimitive(data), metricValue); } catch (Exception e) { Timber.w(e); } } @ReactMethod - public void trackBoolMetricValue(String eventName, Boolean data, Double metricValue) { + public void trackBoolMetricValue(String eventName, Boolean data, Double metricValue, String environment) { try { - ldClient.track(eventName, new JsonPrimitive(data), metricValue); + LDClient.getForMobileKey(environment).track(eventName, new JsonPrimitive(data), metricValue); } catch (Exception e) { Timber.w(e); } } @ReactMethod - public void trackStringMetricValue(String eventName, String data, Double metricValue) { + public void trackStringMetricValue(String eventName, String data, Double metricValue, String environment) { try { - ldClient.track(eventName, new JsonPrimitive(data), metricValue); + LDClient.getForMobileKey(environment).track(eventName, new JsonPrimitive(data), metricValue); } catch (Exception e) { Timber.w(e); } } @ReactMethod - public void trackArrayMetricValue(String eventName, ReadableArray data, Double metricValue) { + public void trackArrayMetricValue(String eventName, ReadableArray data, Double metricValue, String environment) { try { - ldClient.track(eventName, toJsonArray(data), metricValue); + LDClient.getForMobileKey(environment).track(eventName, toJsonArray(data), metricValue); } catch (Exception e) { Timber.w(e); } } @ReactMethod - public void trackObjectMetricValue(String eventName, ReadableMap data, Double metricValue) { + public void trackObjectMetricValue(String eventName, ReadableMap data, Double metricValue, String environment) { try { - ldClient.track(eventName, toJsonObject(data), metricValue); + LDClient.getForMobileKey(environment).track(eventName, toJsonObject(data), metricValue); } catch (Exception e) { Timber.w(e); } } @ReactMethod - public void trackMetricValue(String eventName, Double metricValue) { + public void trackMetricValue(String eventName, Double metricValue, String environment) { try { - ldClient.track(eventName, new JsonPrimitive(""), metricValue); + LDClient.getForMobileKey(environment).track(eventName, new JsonPrimitive(""), metricValue); } catch (Exception e) { Timber.w(e); } } - /** - * Shuts down any network connections maintained by the client and puts the client in offline - * mode. - */ @ReactMethod public void setOffline(Promise promise) { try { - ldClient.setOffline(); + LDClient.get().setOffline(); promise.resolve(true); } catch (Exception e) { promise.reject(ERROR_UNKNOWN, e); } } - /** - * Checks if the client is offline - * - * @param promise resolved with boolean value of whether client is offline, or rejected on error - */ @ReactMethod public void isOffline(Promise promise) { try { - boolean result = ldClient.isOffline(); + boolean result = LDClient.get().isOffline(); promise.resolve(result); } catch (Exception e) { promise.reject(ERROR_UNKNOWN, e); } } - /** - * Restores network connectivity for the client, if the client was previously in offline mode. - */ @ReactMethod public void setOnline(Promise promise) { try { - ldClient.setOnline(); + LDClient.get().setOnline(); promise.resolve(true); } catch (Exception e) { promise.reject(ERROR_UNKNOWN, e); } } - /** - * Checks if the client is initialized - * - * @param promise resolved with boolean value of whether client is initialized, or rejected on - * error - */ @ReactMethod - public void isInitialized(Promise promise) { - if (ldClient == null) { - promise.resolve(false); - return; - } - + public void isInitialized(String environment, Promise promise) { try { - boolean result = ldClient.isInitialized(); + boolean result = LDClient.getForMobileKey(environment).isInitialized(); promise.resolve(result); } catch (Exception e) { promise.reject(ERROR_UNKNOWN, e); } } - /** - * Triggers a background flush of pending events waiting to be sent to LaunchDarkly. - */ @ReactMethod public void flush() { try { - ldClient.flush(); + LDClient.get().flush(); } catch (Exception e) { Timber.w(e); } } - /** - * Triggers a background flush and then closes all connections to LaunchDarkly. - */ @ReactMethod public void close(Promise promise) { try { - ldClient.close(); + LDClient.get().close(); promise.resolve(true); } catch (Exception e) { promise.reject(ERROR_CLOSE, e); } } - /** - * Calls LaunchDarkly's identify call that selects the user flags are pulled for, and tracking - * events refer to. - * - * @param options User configuration ReadableMap (JS Object) - * @param promise Resolved with null when identify complete or rejected with error - */ @ReactMethod public void identify(ReadableMap options, final Promise promise) { final LDUser.Builder userBuilder = userBuild(options); @@ -1089,7 +847,7 @@ public void identify(ReadableMap options, final Promise promise) { @Override public void run() { try { - ldClient.identify(userBuilder.build()).get(); + LDClient.get().identify(userBuilder.build()).get(); promise.resolve(null); } catch (InterruptedException e) { Timber.w(e); @@ -1107,48 +865,52 @@ public void run() { } @ReactMethod - public void getConnectionMode(Promise promise) { + public void getConnectionMode(String environment,Promise promise) { try { - promise.resolve(ldClient.getConnectionInformation().getConnectionMode().name()); + promise.resolve(LDClient.getForMobileKey(environment).getConnectionInformation().getConnectionMode().name()); } catch (Exception e) { promise.reject(ERROR_UNKNOWN, e); } } @ReactMethod - public void getLastSuccessfulConnection(Promise promise) { + public void getLastSuccessfulConnection(String environment,Promise promise) { try { - promise.resolve(ldClient.getConnectionInformation().getLastSuccessfulConnection().intValue()); + promise.resolve(LDClient.getForMobileKey(environment).getConnectionInformation().getLastSuccessfulConnection().intValue()); } catch (Exception e) { promise.reject(ERROR_UNKNOWN, e); } } @ReactMethod - public void getLastFailedConnection(Promise promise) { + public void getLastFailedConnection(String environment,Promise promise) { try { - promise.resolve(ldClient.getConnectionInformation().getLastFailedConnection().intValue()); + promise.resolve(LDClient.getForMobileKey(environment).getConnectionInformation().getLastFailedConnection().intValue()); } catch (Exception e) { promise.reject(ERROR_UNKNOWN, e); } } @ReactMethod - public void getLastFailure(Promise promise) { + public void getLastFailure(String environment,Promise promise) { try { - promise.resolve(ldClient.getConnectionInformation().getLastFailure().getFailureType().name()); + promise.resolve(LDClient.getForMobileKey(environment).getConnectionInformation().getLastFailure().getFailureType().name()); } catch (Exception e) { promise.reject(ERROR_UNKNOWN, e); } } + private String envConcat(String environment, String identifier) { + return environment.concat(";").concat(identifier); + } + @ReactMethod - public void registerFeatureFlagListener(String flagKey) { + public void registerFeatureFlagListener(final String flagKey, final String environment) { FeatureFlagChangeListener listener = new FeatureFlagChangeListener() { @Override public void onFeatureFlagChange(String flagKey) { WritableMap result = Arguments.createMap(); - result.putString("flagKey", flagKey); + result.putString("flagKey", envConcat(environment, flagKey)); getReactApplicationContext() .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) @@ -1157,7 +919,7 @@ public void onFeatureFlagChange(String flagKey) { }; try { - ldClient.registerFeatureFlagListener(flagKey, listener); + LDClient.getForMobileKey(environment).registerFeatureFlagListener(flagKey, listener); listeners.put(flagKey, listener); } catch (Exception e) { Timber.w(e); @@ -1165,10 +927,10 @@ public void onFeatureFlagChange(String flagKey) { } @ReactMethod - public void unregisterFeatureFlagListener(String flagKey) { + public void unregisterFeatureFlagListener(String flagKey, String environment) { try { if (listeners.containsKey(flagKey)) { - ldClient.unregisterFeatureFlagListener(flagKey, listeners.get(flagKey)); + LDClient.getForMobileKey(environment).unregisterFeatureFlagListener(flagKey, listeners.get(flagKey)); listeners.remove(flagKey); } } catch (Exception e) { @@ -1177,12 +939,13 @@ public void unregisterFeatureFlagListener(String flagKey) { } @ReactMethod - public void registerCurrentConnectionModeListener(String listenerId) { + public void registerCurrentConnectionModeListener(final String listenerId, final String environment) { LDStatusListener listener = new LDStatusListener() { @Override public void onConnectionModeChanged(ConnectionInformation connectionInfo) { WritableMap result = Arguments.createMap(); result.putString("connectionMode", gson.toJson(connectionInfo)); + result.putString("listenerId", envConcat(environment, listenerId)); getReactApplicationContext() .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) @@ -1194,7 +957,7 @@ public void onInternalFailure(LDFailure ldFailure) {} }; try { - ldClient.registerStatusListener(listener); + LDClient.getForMobileKey(environment).registerStatusListener(listener); connectionModeListeners.put(listenerId, listener); } catch (Exception e) { Timber.w(e); @@ -1202,10 +965,10 @@ public void onInternalFailure(LDFailure ldFailure) {} } @ReactMethod - public void unregisterCurrentConnectionModeListener(String listenerId) { + public void unregisterCurrentConnectionModeListener(String listenerId, String environment) { try { if (connectionModeListeners.containsKey(listenerId)) { - ldClient.unregisterStatusListener(connectionModeListeners.get(listenerId)); + LDClient.getForMobileKey(environment).unregisterStatusListener(connectionModeListeners.get(listenerId)); connectionModeListeners.remove(listenerId); } } catch (Exception e) { @@ -1214,12 +977,13 @@ public void unregisterCurrentConnectionModeListener(String listenerId) { } @ReactMethod - public void registerAllFlagsListener(String listenerId) { + public void registerAllFlagsListener(final String listenerId, final String environment) { LDAllFlagsListener listener = new LDAllFlagsListener() { @Override public void onChange(List flagKeys) { WritableMap result = Arguments.createMap(); result.putString("flagKeys", gson.toJson(flagKeys)); + result.putString("listenerId", envConcat(environment, listenerId)); getReactApplicationContext() .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) @@ -1228,7 +992,7 @@ public void onChange(List flagKeys) { }; try { - ldClient.registerAllFlagsListener(listener); + LDClient.getForMobileKey(environment).registerAllFlagsListener(listener); allFlagsListeners.put(listenerId, listener); } catch (Exception e) { Timber.w(e); @@ -1236,10 +1000,10 @@ public void onChange(List flagKeys) { } @ReactMethod - public void unregisterAllFlagsListener(String listenerId) { + public void unregisterAllFlagsListener(String listenerId, String environment) { try { if (allFlagsListeners.containsKey(listenerId)) { - ldClient.unregisterAllFlagsListener(allFlagsListeners.get(listenerId)); + LDClient.getForMobileKey(environment).unregisterAllFlagsListener(allFlagsListeners.get(listenerId)); allFlagsListeners.remove(listenerId); } } catch (Exception e) { @@ -1247,15 +1011,6 @@ public void unregisterAllFlagsListener(String listenerId) { } } - /** - * Convert a ReadableMap into a JsonObject - *

- * This will recursively convert internal ReadableMaps and ReadableArrays into JsonObjects and - * JsonArrays. - * - * @param readableMap A ReadableMap to be converted to a JsonObject - * @return A JsonObject containing the converted elements from the ReadableMap. - */ private static JsonObject toJsonObject(ReadableMap readableMap) { if (readableMap == null) return null; @@ -1293,15 +1048,6 @@ private static JsonObject toJsonObject(ReadableMap readableMap) { return jsonObject; } - /** - * Convert a ReadableArray into a JsonArray - *

- * This will recursively convert internal ReadableMaps and ReadableArrays into JsonObjects and - * JsonArrays. - * - * @param readableArray A ReadableArray to be converted to a JsonArray - * @return A JsonArray containing the converted elements from the ReadableArray - */ private static JsonArray toJsonArray(ReadableArray readableArray) { if (readableArray == null) return null; @@ -1336,15 +1082,6 @@ private static JsonArray toJsonArray(ReadableArray readableArray) { return jsonArray; } - /** - * Convert a JsonArray into a WritableArray - *

- * This will recursively convert internal JsonObjects and JsonArrays into WritableMaps and - * WritableArrays. - * - * @param jsonArray A JsonArray to be converted into a WritableArray - * @return A WritableArray containing converted elements from the JsonArray - */ private static WritableArray fromJsonArray(JsonArray jsonArray) { if (jsonArray == null) return null; @@ -1371,16 +1108,6 @@ private static WritableArray fromJsonArray(JsonArray jsonArray) { return result; } - - /** - * Convert a JsonObject into a WritableMap - *

- * This will recursively convert internal JsonObjects and JsonArrays into WritableMaps and - * WritableArrays. - * - * @param jsonObject A JsonObject to be converted into a WritableMap - * @return A WritableMap containing converted elements from the jsonObject - */ private static WritableMap fromJsonObject(JsonObject jsonObject) { if (jsonObject == null) return null; @@ -1407,32 +1134,10 @@ private static WritableMap fromJsonObject(JsonObject jsonObject) { return result; } - /** - * A support interface for defining how a ConfigEntryType is read and converted from a - * ReadableMap of configuration entries. - * - * @param The returned type of a value read from the config entry. - */ interface ConvertFromReadable { - /** - * Reads a config entry from map by key and converts to the appropriate return type of the - * ConfigEntryType. - * - * @param map A ReadableMap to get the raw config entry - * @param key The key to look up the config entry from the map - * @return An appropriate return value for the ConfigEntryType - */ T getFromMap(ReadableMap map, String key); } - /** - * An enum for the supported types of config entries. - * - *

- * Each type of config entry has a base ReadableType for checking that a ReadableMap contains an - * entry of the correct type, as well as an implementation of ConvertFromReadable for retrieving - * and converting a ReadableMap entry into a non base type for configuration processing. - */ enum ConfigEntryType implements ConvertFromReadable { String(ReadableType.String) { public String getFromMap(ReadableMap map, String key) { @@ -1464,6 +1169,11 @@ public Boolean getFromMap(ReadableMap map, String key) { return map.getBoolean(key); } }, + Map(ReadableType.Map) { + public Map getFromMap(ReadableMap map, String key) { + return map.getMap(key).toHashMap(); + } + }, StringSet(ReadableType.Array) { public Set getFromMap(ReadableMap map, String key) { ReadableArray array = map.getArray(key); @@ -1488,13 +1198,6 @@ ReadableType getReadableType() { } } - /** - * A helper for looking up a method from a Java class (reflection). - * - * @param cls The class to look up the method from - * @param methodName The name of the method to look up - * @return The looked up method (or null) - */ private static Method findSetter(Class cls, String methodName) { for (Method method : cls.getMethods()) { if (method.getName().equals(methodName) && method.getParameterTypes().length == 1) diff --git a/index.d.ts b/index.d.ts index 1bd1d8e..8bc2cbd 100644 --- a/index.d.ts +++ b/index.d.ts @@ -161,6 +161,11 @@ declare module 'launchdarkly-react-native-client-sdk' { */ diagnosticRecordingIntervalMillis?: number; + /** + * The mapping of environment names as keys to mobile keys for each environment as values. + */ + secondaryMobileKeys?: Record; + /** * Whether to treat all user attributes as private for event reporting for all users. * The SDK will not include private attribute values in analytics events, but private attribute names will be sent. @@ -434,10 +439,12 @@ declare module 'launchdarkly-react-native-client-sdk' { * The unique key of the feature flag. * @param defaultValue * The default value of the flag, to be used if the value is not available from LaunchDarkly. + * @param environment + * Optional environment name to obtain the result from the corresponding secondary environment * @returns * A promise containing the flag's value. */ - boolVariation(flagKey: string, defaultValue: boolean): Promise; + boolVariation(flagKey: string, defaultValue: boolean, environment?: string): Promise; /** * Determines the variation of an integer feature flag for the current user. @@ -446,10 +453,12 @@ declare module 'launchdarkly-react-native-client-sdk' { * The unique key of the feature flag. * @param defaultValue * The default value of the flag, to be used if the value is not available from LaunchDarkly. + * @param environment + * Optional environment name to obtain the result from the corresponding secondary environment * @returns * A promise containing the flag's value. */ - intVariation(flagKey: string, defaultValue: number): Promise; + intVariation(flagKey: string, defaultValue: number, environment?: string): Promise; /** * Determines the variation of a floating-point feature flag for the current user. @@ -458,10 +467,12 @@ declare module 'launchdarkly-react-native-client-sdk' { * The unique key of the feature flag. * @param defaultValue * The default value of the flag, to be used if the value is not available from LaunchDarkly. + * @param environment + * Optional environment name to obtain the result from the corresponding secondary environment * @returns * A promise containing the flag's value. */ - floatVariation(flagKey: string, defaultValue: number): Promise; + floatVariation(flagKey: string, defaultValue: number, environment?: string): Promise; /** * Determines the variation of a string feature flag for the current user. @@ -470,10 +481,12 @@ declare module 'launchdarkly-react-native-client-sdk' { * The unique key of the feature flag. * @param defaultValue * The default value of the flag, to be used if the value is not available from LaunchDarkly. + * @param environment + * Optional environment name to obtain the result from the corresponding secondary environment * @returns * A promise containing the flag's value. */ - stringVariation(flagKey: string, defaultValue: string): Promise; + stringVariation(flagKey: string, defaultValue: string, environment?: string): Promise; /** * Determines the variation of a JSON feature flag for the current user. @@ -482,12 +495,15 @@ declare module 'launchdarkly-react-native-client-sdk' { * The unique key of the feature flag. * @param defaultValue * The default value of the flag, to be used if the value is not available from LaunchDarkly. + * @param environment + * Optional environment name to obtain the result from the corresponding secondary environment * @returns * A promise containing the flag's value. */ jsonVariation( flagKey: string, defaultValue: Record, + environment?: string ): Promise>; /** @@ -503,12 +519,15 @@ declare module 'launchdarkly-react-native-client-sdk' { * The unique key of the feature flag. * @param defaultValue * The default value of the flag, to be used if the value is not available from LaunchDarkly. + * @param environment + * Optional environment name to obtain the result from the corresponding secondary environment * @returns * A promise containing an [[LDEvaluationDetail]] object containing the value and explanation. */ boolVariationDetail( flagKey: string, defaultValue: boolean, + environment?: string ): Promise>; /** @@ -524,12 +543,15 @@ declare module 'launchdarkly-react-native-client-sdk' { * The unique key of the feature flag. * @param defaultValue * The default value of the flag, to be used if the value is not available from LaunchDarkly. + * @param environment + * Optional environment name to obtain the result from the corresponding secondary environment * @returns * A promise containing an [[LDEvaluationDetail]] object containing the value and explanation. */ intVariationDetail( flagKey: string, defaultValue: number, + environment?: string ): Promise>; /** @@ -545,12 +567,15 @@ declare module 'launchdarkly-react-native-client-sdk' { * The unique key of the feature flag. * @param defaultValue * The default value of the flag, to be used if the value is not available from LaunchDarkly. + * @param environment + * Optional environment name to obtain the result from the corresponding secondary environment * @returns * A promise containing an [[LDEvaluationDetail]] object containing the value and explanation. */ floatVariationDetail( flagKey: string, defaultValue: number, + environment?: string ): Promise>; /** @@ -566,12 +591,15 @@ declare module 'launchdarkly-react-native-client-sdk' { * The unique key of the feature flag. * @param defaultValue * The default value of the flag, to be used if the value is not available from LaunchDarkly. + * @param environment + * Optional environment name to obtain the result from the corresponding secondary environment * @returns * A promise containing an [[LDEvaluationDetail]] object containing the value and explanation. */ stringVariationDetail( flagKey: string, defaultValue: string, + environment?: string ): Promise>; /** @@ -587,24 +615,29 @@ declare module 'launchdarkly-react-native-client-sdk' { * The unique key of the feature flag. * @param defaultValue * The default value of the flag, to be used if the value is not available from LaunchDarkly. + * @param environment + * Optional environment name to obtain the result from the corresponding secondary environment * @returns * A promise containing an [[LDEvaluationDetail]] object containing the value and explanation. */ jsonVariationDetail( flagKey: string, defaultValue: Record, + environment?: string ): Promise>>; /** * Returns a map of all available flags to the current user's values. * + * @param environment + * Optional environment name to obtain the result from the corresponding secondary environment * @returns * A promise containing an object in which each key is a feature flag key and each value is the flag value. * The promise will be rejected if the SDK has not yet completed initialization. * Note that there is no way to specify a default value for each flag as there is with the * `*Variation` methods, so any flag that cannot be evaluated will have a null value. */ - allFlags(): Promise; + allFlags(environment?: string): Promise; /** * Track events to use in goals or A/B tests. @@ -615,8 +648,10 @@ declare module 'launchdarkly-react-native-client-sdk' { * Optional additional information to associate with the event. * @param metricValue * Optional numeric value to attach to the tracked event + * @param environment + * Optional string to execute the function in a different environment than the default. */ - track(eventName: string, data?: any, metricValue?: number): void; + track(eventName: string, data?: any, metricValue?: number, environment?: string): void; /** * Checks whether the client has been put into offline mode. This is true only if [[setOffline]] @@ -659,10 +694,12 @@ declare module 'launchdarkly-react-native-client-sdk' { * * This function only works when running in Android. On iOS, this function will return a rejected promise. * + * @param environment + * Optional environment name to obtain the result from the corresponding secondary environment * @returns * A promise contianing true if the client is initialized or offline */ - isInitialized(): Promise; + isInitialized(environment?: string): Promise; /** * Flushes all pending analytics events. @@ -697,10 +734,13 @@ declare module 'launchdarkly-react-native-client-sdk' { * The flag key to attach the callback to * @param callback * The callback to attach to the flag key + * @param environment + * Optional string to execute the function in a different environment than the default. */ registerFeatureFlagListener( flagKey: string, callback: (flagKey: string) => void, + environment?: string ): void; /** @@ -710,10 +750,13 @@ declare module 'launchdarkly-react-native-client-sdk' { * The flag key to remove the callback from * @param callback * The callback to remove from the flag key + * @param environment + * Optional string to execute the function in a different environment than the default. */ unregisterFeatureFlagListener( flagKey: string, callback: (flagKey: string) => void, + environment?: string ): void; /** @@ -730,30 +773,36 @@ declare module 'launchdarkly-react-native-client-sdk' { * - `'SHUTDOWN'`: (Android specific enum value) The shutdown state indicates the SDK has been permanently shutdown as a result of a call to close(). * - `'ESTABLISHING_STREAMING_CONNECTION'`: (iOS specific enum value) The SDK is attempting to connect to LaunchDarkly by streaming. * + * @param environment + * Optional string to execute the function in a different environment than the default. * @returns * A promise containing a LDConnectionMode enum value representing the status of the connection to LaunchDarkly. */ - getConnectionMode(): Promise; + getConnectionMode(environment?: string): Promise; /** * Returns the most recent successful flag cache update in millis from the epoch * or null if flags have never been retrieved. * + * @param environment + * Optional string to execute the function in a different environment than the default. * @returns * A promise containing a number representing the status of the connection to LaunchDarkly, * or null if a successful connection has yet to be established. */ - getLastSuccessfulConnection(): Promise; + getLastSuccessfulConnection(environment?: string): Promise; /** * Most recent unsuccessful flag cache update attempt in millis from the epoch * or null if flag update has never been attempted. * + * @param environment + * Optional string to execute the function in a different environment than the default. * @returns * A promise containing a number representing the status of the connection to LaunchDarkly, * or null if a failed connection has yet to occur. */ - getLastFailedConnection(): Promise; + getLastFailedConnection(environment?: string): Promise; /** * Returns the most recent connection failure reason or null. @@ -769,11 +818,13 @@ declare module 'launchdarkly-react-native-client-sdk' { * - `'NETWORK_FAILURE'`: (Android specific enum value) A network request for polling, or the EventSource stream reported a failure. * - `'INVALID_RESPONSE_BODY'`: (Android specific enum value) A response body received either through polling or streaming was unable to be parsed. * + * @param environment + * Optional string to execute the function in a different environment than the default. * @returns * A promise containing a LDFailureReason enum value representing the reason for the most recently failed * connection to LaunchDarkly, or null if a failed connection has yet to occur. */ - getLastFailure(): Promise; + getLastFailure(environment?: string): Promise; /** * Registers a callback to be called on connection status updates. @@ -782,19 +833,24 @@ declare module 'launchdarkly-react-native-client-sdk' { * The listener to be called on a connection status update * @param callback * The callback to attach to the connection status update + * @param environment + * Optional string to execute the function in a different environment than the default. */ registerCurrentConnectionModeListener( listenerId: string, callback: (connectionMode: string) => void, + environment?: string ): void; /** * Unregisters a callback so that it will no longer be called on connection status updates. * * @param listenerId - * The listener to remoce the callback from + * The listener to remove the callback from + * @param environment + * Optional string to execute the function in a different environment than the default. */ - unregisterCurrentConnectionModeListener(listenerId: string): void; + unregisterCurrentConnectionModeListener(listenerId: string, environment?: string): void; /** * Registers a callback to be called when a flag update is processed by the SDK. @@ -803,10 +859,13 @@ declare module 'launchdarkly-react-native-client-sdk' { * The listener to be called when a flag update is processed * @param callback * The callback to attach to the flag update + * @param environment + * Optional string to execute the function in a different environment than the default. */ registerAllFlagsListener( listenerId: string, callback: (updatedFlags: string[]) => void, + environment?: string ): void; /** @@ -814,8 +873,10 @@ declare module 'launchdarkly-react-native-client-sdk' { * * @param listenerId * The listener to be removed + * @param environment + * Optional string to execute the function in a different environment than the default. */ - unregisterAllFlagsListener(listenerId: string): void; + unregisterAllFlagsListener(listenerId: string, environment?: string): void; } } diff --git a/index.js b/index.js index b443f29..43d6065 100644 --- a/index.js +++ b/index.js @@ -18,6 +18,10 @@ export default class LDClient { return String(version); } + _getEnvironment(environment) { + return environment !== undefined ? environment : LaunchdarklyReactNativeClient.DEFAULT_ENVIRONMENT; + } + configure(config, user, timeout) { if (this.isInitialized() == true) { Promise.reject('LaunchDarkly SDK already initialized'); @@ -37,138 +41,150 @@ export default class LDClient { } } - boolVariation(flagKey, defaultValue) { + boolVariation(flagKey, defaultValue, environment) { + const env = this._getEnvironment(environment); if (defaultValue == undefined) { - return LaunchdarklyReactNativeClient.boolVariation(flagKey); + return LaunchdarklyReactNativeClient.boolVariation(flagKey, env); } else { - return LaunchdarklyReactNativeClient.boolVariationDefaultValue(flagKey, defaultValue); + return LaunchdarklyReactNativeClient.boolVariationDefaultValue(flagKey, defaultValue, env); } } - intVariation(flagKey, defaultValue) { + intVariation(flagKey, defaultValue, environment) { + const env = this._getEnvironment(environment); if (defaultValue == undefined) { - return LaunchdarklyReactNativeClient.intVariation(flagKey); + return LaunchdarklyReactNativeClient.intVariation(flagKey, env); } else { - return LaunchdarklyReactNativeClient.intVariationDefaultValue(flagKey, defaultValue); + return LaunchdarklyReactNativeClient.intVariationDefaultValue(flagKey, defaultValue, env); } } - floatVariation(flagKey, defaultValue) { + floatVariation(flagKey, defaultValue, environment) { + const env = this._getEnvironment(environment); if (defaultValue == undefined) { - return LaunchdarklyReactNativeClient.floatVariation(flagKey); + return LaunchdarklyReactNativeClient.floatVariation(flagKey, env); } else { - return LaunchdarklyReactNativeClient.floatVariationDefaultValue(flagKey, defaultValue); + return LaunchdarklyReactNativeClient.floatVariationDefaultValue(flagKey, defaultValue, env); } } - stringVariation(flagKey, defaultValue) { + stringVariation(flagKey, defaultValue, environment) { + const env = this._getEnvironment(environment); if (defaultValue == undefined) { - return LaunchdarklyReactNativeClient.stringVariation(flagKey); + return LaunchdarklyReactNativeClient.stringVariation(flagKey, env); } else { - return LaunchdarklyReactNativeClient.stringVariationDefaultValue(flagKey, defaultValue); + return LaunchdarklyReactNativeClient.stringVariationDefaultValue(flagKey, defaultValue, env); } } - jsonVariation(flagKey, defaultValue) { + jsonVariation(flagKey, defaultValue, environment) { + const env = this._getEnvironment(environment); if (defaultValue == undefined) { - return LaunchdarklyReactNativeClient.jsonVariationNone(flagKey); + return LaunchdarklyReactNativeClient.jsonVariationNone(flagKey, env); } else if (typeof defaultValue === 'number') { - return LaunchdarklyReactNativeClient.jsonVariationNumber(flagKey, defaultValue); + return LaunchdarklyReactNativeClient.jsonVariationNumber(flagKey, defaultValue, env); } else if (typeof defaultValue === 'boolean') { - return LaunchdarklyReactNativeClient.jsonVariationBool(flagKey, defaultValue); + return LaunchdarklyReactNativeClient.jsonVariationBool(flagKey, defaultValue, env); } else if (typeof defaultValue === 'string') { - return LaunchdarklyReactNativeClient.jsonVariationString(flagKey, defaultValue); + return LaunchdarklyReactNativeClient.jsonVariationString(flagKey, defaultValue, env); } else if (Array.isArray(defaultValue)) { - return LaunchdarklyReactNativeClient.jsonVariationArray(flagKey, defaultValue); + return LaunchdarklyReactNativeClient.jsonVariationArray(flagKey, defaultValue, env); } else { // Should be an object - return LaunchdarklyReactNativeClient.jsonVariationObject(flagKey, defaultValue); + return LaunchdarklyReactNativeClient.jsonVariationObject(flagKey, defaultValue, env); } } - boolVariationDetail(flagKey, defaultValue) { + boolVariationDetail(flagKey, defaultValue, environment) { + const env = this._getEnvironment(environment); if (defaultValue == undefined) { - return LaunchdarklyReactNativeClient.boolVariationDetail(flagKey); + return LaunchdarklyReactNativeClient.boolVariationDetail(flagKey, env); } else { - return LaunchdarklyReactNativeClient.boolVariationDetailDefaultValue(flagKey, defaultValue); + return LaunchdarklyReactNativeClient.boolVariationDetailDefaultValue(flagKey, defaultValue, env); } } - intVariationDetail(flagKey, defaultValue) { + intVariationDetail(flagKey, defaultValue, environment) { + const env = this._getEnvironment(environment); if (defaultValue == undefined) { - return LaunchdarklyReactNativeClient.intVariationDetail(flagKey); + return LaunchdarklyReactNativeClient.intVariationDetail(flagKey, env); } else { - return LaunchdarklyReactNativeClient.intVariationDetailDefaultValue(flagKey, defaultValue); + return LaunchdarklyReactNativeClient.intVariationDetailDefaultValue(flagKey, defaultValue, env); } } - floatVariationDetail(flagKey, defaultValue) { + floatVariationDetail(flagKey, defaultValue, environment) { + const env = this._getEnvironment(environment); if (defaultValue == undefined) { - return LaunchdarklyReactNativeClient.floatVariationDetail(flagKey); + return LaunchdarklyReactNativeClient.floatVariationDetail(flagKey, env); } else { - return LaunchdarklyReactNativeClient.floatVariationDetailDefaultValue(flagKey, defaultValue); + return LaunchdarklyReactNativeClient.floatVariationDetailDefaultValue(flagKey, defaultValue, env); } } - stringVariationDetail(flagKey, defaultValue) { + stringVariationDetail(flagKey, defaultValue, environment) { + const env = this._getEnvironment(environment); if (defaultValue == undefined) { - return LaunchdarklyReactNativeClient.stringVariationDetail(flagKey); + return LaunchdarklyReactNativeClient.stringVariationDetail(flagKey, env); } else { - return LaunchdarklyReactNativeClient.stringVariationDetailDefaultValue(flagKey, defaultValue); + return LaunchdarklyReactNativeClient.stringVariationDetailDefaultValue(flagKey, defaultValue, env); } } - jsonVariationDetail(flagKey, defaultValue) { + jsonVariationDetail(flagKey, defaultValue, environment) { + const env = this._getEnvironment(environment); if (defaultValue == undefined) { - return LaunchdarklyReactNativeClient.jsonVariationDetailNone(flagKey); + return LaunchdarklyReactNativeClient.jsonVariationDetailNone(flagKey, env); } else if (typeof defaultValue === 'number') { - return LaunchdarklyReactNativeClient.jsonVariationDetailNumber(flagKey, defaultValue); + return LaunchdarklyReactNativeClient.jsonVariationDetailNumber(flagKey, defaultValue, env); } else if (typeof defaultValue === 'boolean') { - return LaunchdarklyReactNativeClient.jsonVariationDetailBool(flagKey, defaultValue); + return LaunchdarklyReactNativeClient.jsonVariationDetailBool(flagKey, defaultValue, env); } else if (typeof defaultValue === 'string') { - return LaunchdarklyReactNativeClient.jsonVariationDetailString(flagKey, defaultValue); + return LaunchdarklyReactNativeClient.jsonVariationDetailString(flagKey, defaultValue, env); } else if (Array.isArray(defaultValue)) { - return LaunchdarklyReactNativeClient.jsonVariationDetailArray(flagKey, defaultValue); + return LaunchdarklyReactNativeClient.jsonVariationDetailArray(flagKey, defaultValue, env); } else { // Should be an object - return LaunchdarklyReactNativeClient.jsonVariationDetailObject(flagKey, defaultValue); + return LaunchdarklyReactNativeClient.jsonVariationDetailObject(flagKey, defaultValue, env); } } - allFlags() { - return LaunchdarklyReactNativeClient.allFlags(); + allFlags(environment) { + const env = this._getEnvironment(environment); + return LaunchdarklyReactNativeClient.allFlags(env); } - track(eventName, data, metricValue) { + track(eventName, data, metricValue, environment) { + const env = this._getEnvironment(environment); if (metricValue) { if (data === null || typeof data === 'undefined') { - LaunchdarklyReactNativeClient.trackMetricValue(eventName, metricValue); + LaunchdarklyReactNativeClient.trackMetricValue(eventName, metricValue, env); } else if (typeof data === 'number') { - LaunchdarklyReactNativeClient.trackNumberMetricValue(eventName, data, metricValue); + LaunchdarklyReactNativeClient.trackNumberMetricValue(eventName, data, metricValue, env); } else if (typeof data === 'boolean') { - LaunchdarklyReactNativeClient.trackBoolMetricValue(eventName, data, metricValue); + LaunchdarklyReactNativeClient.trackBoolMetricValue(eventName, data, metricValue, env); } else if (typeof data === 'string') { - LaunchdarklyReactNativeClient.trackStringMetricValue(eventName, data, metricValue); + LaunchdarklyReactNativeClient.trackStringMetricValue(eventName, data, metricValue, env); } else if (Array.isArray(data)) { - LaunchdarklyReactNativeClient.trackArrayMetricValue(eventName, data, metricValue); + LaunchdarklyReactNativeClient.trackArrayMetricValue(eventName, data, metricValue, env); } else { // should be an object - LaunchdarklyReactNativeClient.trackObjectMetricValue(eventName, data, metricValue); + LaunchdarklyReactNativeClient.trackObjectMetricValue(eventName, data, metricValue, env); } } else { if (data === null || typeof data === 'undefined') { - LaunchdarklyReactNativeClient.track(eventName); + LaunchdarklyReactNativeClient.track(eventName, env); } else if (typeof data === 'number') { - LaunchdarklyReactNativeClient.trackNumber(eventName, data); + LaunchdarklyReactNativeClient.trackNumber(eventName, data, env); } else if (typeof data === 'boolean') { - LaunchdarklyReactNativeClient.trackBool(eventName, data); + LaunchdarklyReactNativeClient.trackBool(eventName, data, env); } else if (typeof data === 'string') { - LaunchdarklyReactNativeClient.trackString(eventName, data); + LaunchdarklyReactNativeClient.trackString(eventName, data, env); } else if (Array.isArray(data)) { - LaunchdarklyReactNativeClient.trackArray(eventName, data); + LaunchdarklyReactNativeClient.trackArray(eventName, data, env); } else { // should be an object - LaunchdarklyReactNativeClient.trackObject(eventName, data); + LaunchdarklyReactNativeClient.trackObject(eventName, data, env); } } } @@ -185,8 +201,9 @@ export default class LDClient { return LaunchdarklyReactNativeClient.setOnline(); } - isInitialized() { - return LaunchdarklyReactNativeClient.isInitialized(); + isInitialized(environment) { + const env = this._getEnvironment(environment); + return LaunchdarklyReactNativeClient.isInitialized(env); } flush() { @@ -219,94 +236,121 @@ export default class LDClient { _allFlagsUpdateListener(changedFlags) { const flagKeys = changedFlags.flagKeys; - let listeners = Object.values(this.allFlagsListeners); - for (const listener of listeners) { - listener(flagKeys); + const listenerId = changedFlags.listenerId; + for (const [key, value] of Object.entries(this.allFlagsListeners)) { + if (key == listenerId) { + key(flagKeys); + } } } _connectionModeUpdateListener(connectionStatus) { const connectionMode = connectionStatus.connectionMode; - let listeners = Object.values(this.connectionModeListeners); - for (const listener of listeners) { - listener(connectionMode); + const listenerId = connectionStatus.listenerId; + for (const [key, value] of Object.entries(this.connectionModeListeners)) { + if (key == listenerId) { + key(connectionMode); + } } } - registerFeatureFlagListener(flagKey, callback) { + _envConcat(env, flagKey) { + return env.concat(";", flagKey) + } + + registerFeatureFlagListener(flagKey, callback, environment) { if (typeof callback !== "function") { return; } + const env = this._getEnvironment(environment); + const multiFlagKey = this._envConcat(env, flagKey); - if (this.flagListeners.hasOwnProperty(flagKey)) { - this.flagListeners[flagKey].push(callback); + if (this.flagListeners.hasOwnProperty(multiFlagKey)) { + this.flagListeners[multiFlagKey].push(callback); } else { - this.flagListeners[flagKey] = [callback]; + this.flagListeners[multiFlagKey] = [callback]; - LaunchdarklyReactNativeClient.registerFeatureFlagListener(flagKey); + LaunchdarklyReactNativeClient.registerFeatureFlagListener(flagKey, env); } } - unregisterFeatureFlagListener(flagKey, callback) { - if (!this.flagListeners.hasOwnProperty(flagKey)) + unregisterFeatureFlagListener(flagKey, callback, environment) { + const env = this._getEnvironment(environment); + const multiFlagKey = this._envConcat(env, flagKey); + if (!this.flagListeners.hasOwnProperty(multiFlagKey)) { return; + } - this.flagListeners[flagKey] = - this.flagListeners[flagKey].filter(listener => listener != callback); + this.flagListeners[multiFlagKey] = + this.flagListeners[multiFlagKey].filter(listener => listener != callback); - if (this.flagListeners[flagKey].length == 0) { - LaunchdarklyReactNativeClient.unregisterFeatureFlagListener(flagKey); - delete this.flagListeners[flagKey]; + if (this.flagListeners[multiFlagKey].length == 0) { + LaunchdarklyReactNativeClient.unregisterFeatureFlagListener(flagKey, env); + delete this.flagListeners[multiFlagKey]; } } - registerCurrentConnectionModeListener(listenerId, callback) { + registerCurrentConnectionModeListener(listenerId, callback, environment) { if (typeof callback !== "function") { return; } + const env = this._getEnvironment(environment); + const multiListenerId = this._envConcat(env, flagKey); - this.connectionModeListeners[listenerId] = callback; - LaunchdarklyReactNativeClient.registerCurrentConnectionModeListener(listenerId); + this.connectionModeListeners[multiListenerId] = callback; + LaunchdarklyReactNativeClient.registerCurrentConnectionModeListener(listenerId, env); } - unregisterCurrentConnectionModeListener(listenerId) { - if (!this.connectionModeListeners.hasOwnProperty(listenerId)) + unregisterCurrentConnectionModeListener(listenerId, environment) { + const env = this._getEnvironment(environment); + const multiListenerId = this._envConcat(env, flagKey); + if (!this.connectionModeListeners.hasOwnProperty(multiListenerId)) { return; + } - LaunchdarklyReactNativeClient.unregisterCurrentConnectionModeListener(listenerId); - delete this.connectionModeListeners[listenerId]; + LaunchdarklyReactNativeClient.unregisterCurrentConnectionModeListener(listenerId, env); + delete this.connectionModeListeners[multiListenerId]; } - registerAllFlagsListener(listenerId, callback) { + registerAllFlagsListener(listenerId, callback, environment) { if (typeof callback !== "function") { return; } + const env = this._getEnvironment(environment); + const multiListenerId = this._envConcat(env, flagKey); - this.allFlagsListeners[listenerId] = callback; - LaunchdarklyReactNativeClient.registerAllFlagsListener(listenerId); + this.allFlagsListeners[multiListenerId] = callback; + LaunchdarklyReactNativeClient.registerAllFlagsListener(listenerId, env); } - unregisterAllFlagsListener(listenerId) { - if (!this.allFlagsListeners.hasOwnProperty(listenerId)) + unregisterAllFlagsListener(listenerId, environment) { + const env = this._getEnvironment(environment); + const multiListenerId = this._envConcat(env, flagKey); + if (!this.allFlagsListeners.hasOwnProperty(multiListenerId)) { return; + } - LaunchdarklyReactNativeClient.unregisterAllFlagsListener(listenerId); - delete this.allFlagsListeners[listenerId]; + LaunchdarklyReactNativeClient.unregisterAllFlagsListener(listenerId, env); + delete this.allFlagsListeners[multiListenerId]; } - getConnectionMode() { - return LaunchdarklyReactNativeClient.getConnectionMode(); + getConnectionMode(environment) { + const env = this._getEnvironment(environment); + return LaunchdarklyReactNativeClient.getConnectionMode(env); } - getLastSuccessfulConnection() { - return LaunchdarklyReactNativeClient.getLastSuccessfulConnection(); + getLastSuccessfulConnection(environment) { + const env = this._getEnvironment(environment); + return LaunchdarklyReactNativeClient.getLastSuccessfulConnection(env); } - getLastFailedConnection() { - return LaunchdarklyReactNativeClient.getLastFailedConnection(); + getLastFailedConnection(environment) { + const env = this._getEnvironment(environment); + return LaunchdarklyReactNativeClient.getLastFailedConnection(env); } - getLastFailure() { - return LaunchdarklyReactNativeClient.getLastFailure(); + getLastFailure(environment) { + const env = this._getEnvironment(environment); + return LaunchdarklyReactNativeClient.getLastFailure(env); } } diff --git a/ios/LaunchdarklyReactNativeClient.swift b/ios/LaunchdarklyReactNativeClient.swift index f3966b8..dc72386 100644 --- a/ios/LaunchdarklyReactNativeClient.swift +++ b/ios/LaunchdarklyReactNativeClient.swift @@ -11,13 +11,14 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { private let CONNECTION_MODE_PREFIX = "LaunchDarkly-Connection-Mode-" private let ERROR_INIT = "E_INITIALIZE" private let ERROR_IDENTIFY = "E_IDENTIFY" + private let DEFAULT_ENVIRONMENT = "default" override func supportedEvents() -> [String]! { return [FLAG_PREFIX, ALL_FLAGS_PREFIX, CONNECTION_MODE_PREFIX] } override func constantsToExport() -> [AnyHashable: Any] { - return ["FLAG_PREFIX": FLAG_PREFIX, "ALL_FLAGS_PREFIX": ALL_FLAGS_PREFIX, "CONNECTION_MODE_PREFIX": CONNECTION_MODE_PREFIX] + return ["FLAG_PREFIX": FLAG_PREFIX, "ALL_FLAGS_PREFIX": ALL_FLAGS_PREFIX, "CONNECTION_MODE_PREFIX": CONNECTION_MODE_PREFIX, "DEFAULT_ENVIRONMENT": DEFAULT_ENVIRONMENT] } override static func requiresMainQueueSetup() -> Bool { @@ -48,7 +49,8 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { } } else { LDClient.start(config: config!, user: user, completion: {() -> Void in - resolve(nil)}) + resolve(nil) + }) } } } @@ -134,6 +136,10 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { ldConfig.diagnosticRecordingInterval = TimeInterval(config["diagnosticRecordingIntervalMillis"] as! Float / 1000) } + if config["secondaryMobileKeys"] != nil { + try! ldConfig.setSecondaryMobileKeys(config["secondaryMobileKeys"] as! [String: String]) + } + if config["allUserAttributesPrivate"] != nil { ldConfig.allUserAttributesPrivate = config["allUserAttributesPrivate"] as! Bool } @@ -190,253 +196,253 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { return user } - @objc func boolVariationDefaultValue(_ flagKey: String, defaultValue: ObjCBool, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.get()!.variation(forKey: flagKey, defaultValue: defaultValue.boolValue) as Bool) + @objc func boolVariationDefaultValue(_ flagKey: String, defaultValue: ObjCBool, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + resolve(LDClient.get(environment: environment)!.variation(forKey: flagKey, defaultValue: defaultValue.boolValue) as Bool) } - @objc func intVariationDefaultValue(_ flagKey: String, defaultValue: Int, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.get()!.variation(forKey: flagKey, defaultValue: defaultValue) as Int) + @objc func intVariationDefaultValue(_ flagKey: String, defaultValue: Int, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + resolve(LDClient.get(environment: environment)!.variation(forKey: flagKey, defaultValue: defaultValue) as Int) } - @objc func floatVariationDefaultValue(_ flagKey: String, defaultValue: CGFloat, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.get()!.variation(forKey: flagKey, defaultValue: Double(defaultValue)) as Double) + @objc func floatVariationDefaultValue(_ flagKey: String, defaultValue: CGFloat, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + resolve(LDClient.get(environment: environment)!.variation(forKey: flagKey, defaultValue: Double(defaultValue)) as Double) } - @objc func stringVariationDefaultValue(_ flagKey: String, defaultValue: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.get()!.variation(forKey: flagKey, defaultValue: defaultValue) as String) + @objc func stringVariationDefaultValue(_ flagKey: String, defaultValue: String, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + resolve(LDClient.get(environment: environment)!.variation(forKey: flagKey, defaultValue: defaultValue) as String) } - @objc func boolVariation(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let boolFlagValue: Bool? = LDClient.get()!.variation(forKey: flagKey) + @objc func boolVariation(_ flagKey: String, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let boolFlagValue: Bool? = LDClient.get(environment: environment)!.variation(forKey: flagKey) resolve(boolFlagValue) } - @objc func intVariation(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let intFlagValue: Int? = LDClient.get()!.variation(forKey: flagKey) + @objc func intVariation(_ flagKey: String, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let intFlagValue: Int? = LDClient.get(environment: environment)!.variation(forKey: flagKey) resolve(intFlagValue) } - @objc func floatVariation(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let floatFlagValue: Double? = LDClient.get()!.variation(forKey: flagKey) + @objc func floatVariation(_ flagKey: String, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let floatFlagValue: Double? = LDClient.get(environment: environment)!.variation(forKey: flagKey) resolve(floatFlagValue) } - @objc func stringVariation(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let stringFlagValue: String? = LDClient.get()!.variation(forKey: flagKey) + @objc func stringVariation(_ flagKey: String, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let stringFlagValue: String? = LDClient.get(environment: environment)!.variation(forKey: flagKey) resolve(stringFlagValue) } - @objc func jsonVariationNone(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let jsonFlagValue: Dictionary? = LDClient.get()!.variation(forKey: flagKey) + @objc func jsonVariationNone(_ flagKey: String, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let jsonFlagValue: Dictionary? = LDClient.get(environment: environment)!.variation(forKey: flagKey) resolve(jsonFlagValue) } - @objc func jsonVariationNumber(_ flagKey: String, defaultValue: Double, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.get()!.variation(forKey: flagKey, defaultValue: defaultValue) as Double) + @objc func jsonVariationNumber(_ flagKey: String, defaultValue: Double, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + resolve(LDClient.get(environment: environment)!.variation(forKey: flagKey, defaultValue: defaultValue) as Double) } - @objc func jsonVariationBool(_ flagKey: String, defaultValue: Bool, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.get()!.variation(forKey: flagKey, defaultValue: defaultValue) as Bool) + @objc func jsonVariationBool(_ flagKey: String, defaultValue: Bool, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + resolve(LDClient.get(environment: environment)!.variation(forKey: flagKey, defaultValue: defaultValue) as Bool) } - @objc func jsonVariationString(_ flagKey: String, defaultValue: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.get()!.variation(forKey: flagKey, defaultValue: defaultValue) as String) + @objc func jsonVariationString(_ flagKey: String, defaultValue: String, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + resolve(LDClient.get(environment: environment)!.variation(forKey: flagKey, defaultValue: defaultValue) as String) } - @objc func jsonVariationArray(_ flagKey: String, defaultValue: Array, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.get()!.variation(forKey: flagKey, defaultValue: defaultValue) as Array) + @objc func jsonVariationArray(_ flagKey: String, defaultValue: Array, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + resolve(LDClient.get(environment: environment)!.variation(forKey: flagKey, defaultValue: defaultValue) as Array) } - @objc func jsonVariationObject(_ flagKey: String, defaultValue: NSDictionary, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.get()!.variation(forKey: flagKey, defaultValue: defaultValue.swiftDictionary) as NSDictionary) + @objc func jsonVariationObject(_ flagKey: String, defaultValue: NSDictionary, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + resolve(LDClient.get(environment: environment)!.variation(forKey: flagKey, defaultValue: defaultValue.swiftDictionary) as NSDictionary) } - @objc func boolVariationDetailDefaultValue(_ flagKey: String, defaultValue: ObjCBool, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail = LDClient.get()!.variationDetail(forKey: flagKey, defaultValue: defaultValue.boolValue) + @objc func boolVariationDetailDefaultValue(_ flagKey: String, defaultValue: ObjCBool, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let detail = LDClient.get(environment: environment)!.variationDetail(forKey: flagKey, defaultValue: defaultValue.boolValue) let jsonObject: NSDictionary = [ - "value": (detail.value as Any?) ?? NSNull(), - "variationIndex": (detail.variationIndex as Any?) ?? NSNull(), - "reason": (detail.reason as Any?) ?? NSNull() + "value": (detail.value as Any), + "variationIndex": (detail.variationIndex as Any), + "reason": (detail.reason as Any) ] resolve(jsonObject) } - @objc func intVariationDetailDefaultValue(_ flagKey: String, defaultValue: Int, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail = LDClient.get()!.variationDetail(forKey: flagKey, defaultValue: defaultValue) + @objc func intVariationDetailDefaultValue(_ flagKey: String, defaultValue: Int, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let detail = LDClient.get(environment: environment)!.variationDetail(forKey: flagKey, defaultValue: defaultValue) let jsonObject: NSDictionary = [ - "value": (detail.value as Any?) ?? NSNull(), - "variationIndex": (detail.variationIndex as Any?) ?? NSNull(), - "reason": (detail.reason as Any?) ?? NSNull() + "value": (detail.value as Any), + "variationIndex": (detail.variationIndex as Any), + "reason": (detail.reason as Any) ] resolve(jsonObject) } - @objc func floatVariationDetailDefaultValue(_ flagKey: String, defaultValue: CGFloat, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail = LDClient.get()!.variationDetail(forKey: flagKey, defaultValue: Double(defaultValue)) + @objc func floatVariationDetailDefaultValue(_ flagKey: String, defaultValue: CGFloat, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let detail = LDClient.get(environment: environment)!.variationDetail(forKey: flagKey, defaultValue: Double(defaultValue)) let jsonObject: NSDictionary = [ - "value": (detail.value as Any?) ?? NSNull(), - "variationIndex": (detail.variationIndex as Any?) ?? NSNull(), - "reason": (detail.reason as Any?) ?? NSNull() + "value": (detail.value as Any), + "variationIndex": (detail.variationIndex as Any), + "reason": (detail.reason as Any) ] resolve(jsonObject) } - @objc func stringVariationDetailDefaultValue(_ flagKey: String, defaultValue: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail = LDClient.get()!.variationDetail(forKey: flagKey, defaultValue: defaultValue) + @objc func stringVariationDetailDefaultValue(_ flagKey: String, defaultValue: String, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let detail = LDClient.get(environment: environment)!.variationDetail(forKey: flagKey, defaultValue: defaultValue) let jsonObject: NSDictionary = [ - "value": (detail.value as Any?) ?? NSNull(), - "variationIndex": (detail.variationIndex as Any?) ?? NSNull(), - "reason": (detail.reason as Any?) ?? NSNull() + "value": (detail.value as Any), + "variationIndex": (detail.variationIndex as Any), + "reason": (detail.reason as Any) ] resolve(jsonObject) } - @objc func boolVariationDetail(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail: LDEvaluationDetail = LDClient.get()!.variationDetail(forKey: flagKey) + @objc func boolVariationDetail(_ flagKey: String, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let detail: LDEvaluationDetail = LDClient.get(environment: environment)!.variationDetail(forKey: flagKey) let jsonObject: NSDictionary = [ - "value": (detail.value as Any?) ?? NSNull(), - "variationIndex": (detail.variationIndex as Any?) ?? NSNull(), - "reason": (detail.reason as Any?) ?? NSNull() + "value": (detail.value as Any), + "variationIndex": (detail.variationIndex as Any), + "reason": (detail.reason as Any) ] resolve(jsonObject) } - @objc func intVariationDetail(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail: LDEvaluationDetail = LDClient.get()!.variationDetail(forKey: flagKey) + @objc func intVariationDetail(_ flagKey: String, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let detail: LDEvaluationDetail = LDClient.get(environment: environment)!.variationDetail(forKey: flagKey) let jsonObject: NSDictionary = [ - "value": (detail.value as Any?) ?? NSNull(), - "variationIndex": (detail.variationIndex as Any?) ?? NSNull(), - "reason": (detail.reason as Any?) ?? NSNull() + "value": (detail.value as Any), + "variationIndex": (detail.variationIndex as Any), + "reason": (detail.reason as Any) ] resolve(jsonObject) } - @objc func floatVariationDetail(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail: LDEvaluationDetail = LDClient.get()!.variationDetail(forKey: flagKey) + @objc func floatVariationDetail(_ flagKey: String, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let detail: LDEvaluationDetail = LDClient.get(environment: environment)!.variationDetail(forKey: flagKey) let jsonObject: NSDictionary = [ - "value": (detail.value as Any?) ?? NSNull(), - "variationIndex": (detail.variationIndex as Any?) ?? NSNull(), - "reason": (detail.reason as Any?) ?? NSNull() + "value": (detail.value as Any), + "variationIndex": (detail.variationIndex as Any), + "reason": (detail.reason as Any) ] resolve(jsonObject) } - @objc func stringVariationDetail(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail: LDEvaluationDetail = LDClient.get()!.variationDetail(forKey: flagKey) + @objc func stringVariationDetail(_ flagKey: String, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let detail: LDEvaluationDetail = LDClient.get(environment: environment)!.variationDetail(forKey: flagKey) let jsonObject: NSDictionary = [ - "value": (detail.value as Any?) ?? NSNull(), - "variationIndex": (detail.variationIndex as Any?) ?? NSNull(), - "reason": (detail.reason as Any?) ?? NSNull() + "value": (detail.value as Any), + "variationIndex": (detail.variationIndex as Any), + "reason": (detail.reason as Any) ] resolve(jsonObject) } - @objc func jsonVariationDetailNone(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail: LDEvaluationDetail?> = LDClient.get()!.variationDetail(forKey: flagKey) + @objc func jsonVariationDetailNone(_ flagKey: String, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let detail: LDEvaluationDetail?> = LDClient.get(environment: environment)!.variationDetail(forKey: flagKey) let jsonObject: NSDictionary = [ - "value": (detail.value as Any?) ?? NSNull(), - "variationIndex": (detail.variationIndex as Any?) ?? NSNull(), - "reason": (detail.reason as Any?) ?? NSNull() + "value": (detail.value as Any), + "variationIndex": (detail.variationIndex as Any), + "reason": (detail.reason as Any) ] resolve(jsonObject) } - @objc func jsonVariationDetailNumber(_ flagKey: String, defaultValue: Double, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail = LDClient.get()!.variationDetail(forKey: flagKey, defaultValue: defaultValue) + @objc func jsonVariationDetailNumber(_ flagKey: String, defaultValue: Double, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let detail = LDClient.get(environment: environment)!.variationDetail(forKey: flagKey, defaultValue: defaultValue) let jsonObject: NSDictionary = [ - "value": (detail.value as Any?) ?? NSNull(), - "variationIndex": (detail.variationIndex as Any?) ?? NSNull(), - "reason": (detail.reason as Any?) ?? NSNull() + "value": (detail.value as Any), + "variationIndex": (detail.variationIndex as Any), + "reason": (detail.reason as Any) ] resolve(jsonObject) } - @objc func jsonVariationDetailBool(_ flagKey: String, defaultValue: Bool, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail = LDClient.get()!.variationDetail(forKey: flagKey, defaultValue: defaultValue) + @objc func jsonVariationDetailBool(_ flagKey: String, defaultValue: Bool, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let detail = LDClient.get(environment: environment)!.variationDetail(forKey: flagKey, defaultValue: defaultValue) let jsonObject: NSDictionary = [ - "value": (detail.value as Any?) ?? NSNull(), - "variationIndex": (detail.variationIndex as Any?) ?? NSNull(), - "reason": (detail.reason as Any?) ?? NSNull() + "value": (detail.value as Any), + "variationIndex": (detail.variationIndex as Any), + "reason": (detail.reason as Any) ] resolve(jsonObject) } - @objc func jsonVariationDetailString(_ flagKey: String, defaultValue: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail = LDClient.get()!.variationDetail(forKey: flagKey, defaultValue: defaultValue) + @objc func jsonVariationDetailString(_ flagKey: String, defaultValue: String, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let detail = LDClient.get(environment: environment)!.variationDetail(forKey: flagKey, defaultValue: defaultValue) let jsonObject: NSDictionary = [ - "value": (detail.value as Any?) ?? NSNull(), - "variationIndex": (detail.variationIndex as Any?) ?? NSNull(), - "reason": (detail.reason as Any?) ?? NSNull() + "value": (detail.value as Any), + "variationIndex": (detail.variationIndex as Any), + "reason": (detail.reason as Any) ] resolve(jsonObject) } - @objc func jsonVariationDetailArray(_ flagKey: String, defaultValue: Array, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail = LDClient.get()!.variationDetail(forKey: flagKey, defaultValue: defaultValue) + @objc func jsonVariationDetailArray(_ flagKey: String, defaultValue: Array, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let detail = LDClient.get(environment: environment)!.variationDetail(forKey: flagKey, defaultValue: defaultValue) let jsonObject: NSDictionary = [ - "value": (detail.value as Any?) ?? NSNull(), - "variationIndex": (detail.variationIndex as Any?) ?? NSNull(), - "reason": (detail.reason as Any?) ?? NSNull() + "value": (detail.value as Any), + "variationIndex": (detail.variationIndex as Any), + "reason": (detail.reason as Any) ] resolve(jsonObject) } - @objc func jsonVariationDetailObject(_ flagKey: String, defaultValue: NSDictionary, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail = LDClient.get()!.variationDetail(forKey: flagKey, defaultValue: defaultValue.swiftDictionary) + @objc func jsonVariationDetailObject(_ flagKey: String, defaultValue: NSDictionary, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let detail = LDClient.get(environment: environment)!.variationDetail(forKey: flagKey, defaultValue: defaultValue.swiftDictionary) let jsonObject: NSDictionary = [ - "value": (detail.value as Any?) ?? NSNull(), - "variationIndex": (detail.variationIndex as Any?) ?? NSNull(), - "reason": (detail.reason as Any?) ?? NSNull() + "value": (detail.value as Any), + "variationIndex": (detail.variationIndex as Any), + "reason": (detail.reason as Any) ] resolve(jsonObject) } - @objc func trackNumber(_ eventName: String, data: NSNumber) -> Void { - try? LDClient.get()!.track(key: eventName, data: data) + @objc func trackNumber(_ eventName: String, data: NSNumber, environment: String) -> Void { + try? LDClient.get(environment: environment)!.track(key: eventName, data: data) } - @objc func trackBool(_ eventName: String, data: ObjCBool) -> Void { - try? LDClient.get()!.track(key: eventName, data: data.boolValue) + @objc func trackBool(_ eventName: String, data: ObjCBool, environment: String) -> Void { + try? LDClient.get(environment: environment)!.track(key: eventName, data: data.boolValue) } - @objc func trackString(_ eventName: String, data: String) -> Void { - try? LDClient.get()!.track(key: eventName, data: data) + @objc func trackString(_ eventName: String, data: String, environment: String) -> Void { + try? LDClient.get(environment: environment)!.track(key: eventName, data: data) } - @objc func trackArray(_ eventName: String, data: NSArray) -> Void { - try? LDClient.get()!.track(key: eventName, data: data) + @objc func trackArray(_ eventName: String, data: NSArray, environment: String) -> Void { + try? LDClient.get(environment: environment)!.track(key: eventName, data: data) } - @objc func trackObject(_ eventName: String, data: NSDictionary) -> Void { - try? LDClient.get()!.track(key: eventName, data: data.swiftDictionary) + @objc func trackObject(_ eventName: String, data: NSDictionary, environment: String) -> Void { + try? LDClient.get(environment: environment)!.track(key: eventName, data: data.swiftDictionary) } - @objc func track(_ eventName: String) -> Void { - try? LDClient.get()!.track(key: eventName) + @objc func track(_ eventName: String, environment: String) -> Void { + try? LDClient.get(environment: environment)!.track(key: eventName) } - @objc func trackNumberMetricValue(_ eventName: String, data: NSNumber, metricValue: NSNumber) -> Void { - try? LDClient.get()!.track(key: eventName, data: data, metricValue: Double(truncating: metricValue)) + @objc func trackNumberMetricValue(_ eventName: String, data: NSNumber, metricValue: NSNumber, environment: String) -> Void { + try? LDClient.get(environment: environment)!.track(key: eventName, data: data, metricValue: Double(truncating: metricValue)) } - @objc func trackBoolMetricValue(_ eventName: String, data: ObjCBool, metricValue: NSNumber) -> Void { - try? LDClient.get()!.track(key: eventName, data: data.boolValue, metricValue: Double(truncating: metricValue)) + @objc func trackBoolMetricValue(_ eventName: String, data: ObjCBool, metricValue: NSNumber, environment: String) -> Void { + try? LDClient.get(environment: environment)!.track(key: eventName, data: data.boolValue, metricValue: Double(truncating: metricValue)) } - @objc func trackStringMetricValue(_ eventName: String, data: String, metricValue: NSNumber) -> Void { - try? LDClient.get()!.track(key: eventName, data: data, metricValue: Double(truncating: metricValue)) + @objc func trackStringMetricValue(_ eventName: String, data: String, metricValue: NSNumber, environment: String) -> Void { + try? LDClient.get(environment: environment)!.track(key: eventName, data: data, metricValue: Double(truncating: metricValue)) } - @objc func trackArrayMetricValue(_ eventName: String, data: NSArray, metricValue: NSNumber) -> Void { - try? LDClient.get()!.track(key: eventName, data: data, metricValue: Double(truncating: metricValue)) + @objc func trackArrayMetricValue(_ eventName: String, data: NSArray, metricValue: NSNumber, environment: String) -> Void { + try? LDClient.get(environment: environment)!.track(key: eventName, data: data, metricValue: Double(truncating: metricValue)) } - @objc func trackObjectMetricValue(_ eventName: String, data: NSDictionary, metricValue: NSNumber) -> Void { - try? LDClient.get()!.track(key: eventName, data: data.swiftDictionary, metricValue: Double(truncating: metricValue)) + @objc func trackObjectMetricValue(_ eventName: String, data: NSDictionary, metricValue: NSNumber, environment: String) -> Void { + try? LDClient.get(environment: environment)!.track(key: eventName, data: data.swiftDictionary, metricValue: Double(truncating: metricValue)) } - @objc func trackMetricValue(_ eventName: String, metricValue: NSNumber) -> Void { - try? LDClient.get()!.track(key: eventName, metricValue: Double(truncating: metricValue)) + @objc func trackMetricValue(_ eventName: String, metricValue: NSNumber, environment: String) -> Void { + try? LDClient.get(environment: environment)!.track(key: eventName, metricValue: Double(truncating: metricValue)) } @objc func setOffline(_ resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { @@ -446,7 +452,7 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { } @objc func isOffline(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(!LDClient.get()!.isOnline) + resolve(LDClient.get()!.isOnline) } @objc func setOnline(_ resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { @@ -475,9 +481,9 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { } } - @objc func allFlags(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + @objc func allFlags(_ environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { var allFlagsDict: [String: Any] = [:] - if let allFlags = LDClient.get()!.allFlags { + if let allFlags = LDClient.get(environment: environment)!.allFlags { for (key, value) in allFlags { allFlagsDict[key] = value } @@ -485,36 +491,8 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { resolve(allFlagsDict as NSDictionary) } - @objc func registerFeatureFlagListener(_ flagKey: String) -> Void { - let flagChangeOwner = flagKey as LDObserverOwner - if listenerKeys[flagKey] == nil { - listenerKeys[flagKey] = flagChangeOwner - } else { - return - } - LDClient.get()!.observe(keys: [flagKey], owner: flagChangeOwner, handler: { (changedFlags) in - if changedFlags[flagKey] != nil && self.bridge != nil { - self.sendEvent(withName: self.FLAG_PREFIX, body: ["flagKey": flagKey]) - } - }) - } - - private func unregisterListener(_ key: String) -> Void { - let owner = key as LDObserverOwner - if listenerKeys[key] != nil { - listenerKeys.removeValue(forKey: key) - } else { - return - } - LDClient.get()!.stopObserving(owner: owner) - } - - @objc func unregisterFeatureFlagListener(_ flagKey: String) -> Void { - unregisterListener(flagKey) - } - - @objc func getConnectionMode(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let connectionInformation = LDClient.get()!.getConnectionInformation() + @objc func getConnectionMode(_ environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let connectionInformation = LDClient.get(environment: environment)!.getConnectionInformation() var connectionMode: String switch connectionInformation.currentConnectionMode { case .streaming: @@ -530,16 +508,16 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { } // lastKnownFlagValidity is nil if either no connection has ever been successfully made or if the SDK has an active streaming connection. It will have a value if 1) in polling mode and at least one poll has completed successfully, or 2) if in streaming mode whenever the streaming connection closes. - @objc func getLastSuccessfulConnection(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.get()!.getConnectionInformation().lastKnownFlagValidity ?? 0) + @objc func getLastSuccessfulConnection(_ environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + resolve(LDClient.get(environment: environment)!.getConnectionInformation().lastKnownFlagValidity ?? 0) } - @objc func getLastFailedConnection(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.get()!.getConnectionInformation().lastFailedConnection ?? 0) + @objc func getLastFailedConnection(_ environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + resolve(LDClient.get(environment: environment)!.getConnectionInformation().lastFailedConnection ?? 0) } - @objc func getLastFailure(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let connectionInformation = LDClient.get()!.getConnectionInformation() + @objc func getLastFailure(_ environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let connectionInformation = LDClient.get(environment: environment)!.getConnectionInformation() var failureReason: String switch connectionInformation.lastConnectionFailureReason { case .unauthorized: @@ -553,48 +531,80 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { } resolve(failureReason) } + + private func envConcat(environment: String, identifier: String) -> String { + return environment + ";" + identifier + } + + @objc func registerFeatureFlagListener(_ flagKey: String, environment: String) -> Void { + let flagChangeOwner = flagKey as LDObserverOwner + if listenerKeys[flagKey] == nil { + listenerKeys[flagKey] = flagChangeOwner + } else { + return + } + LDClient.get(environment: environment)!.observe(keys: [flagKey], owner: flagChangeOwner, handler: { (changedFlags) in + if changedFlags[flagKey] != nil && self.bridge != nil { + self.sendEvent(withName: self.FLAG_PREFIX, body: ["flagKey": self.envConcat(environment: environment, identifier: flagKey)]) + } + }) + } + + private func unregisterListener(_ key: String, _ environment: String) -> Void { + let owner = key as LDObserverOwner + if listenerKeys[key] != nil { + listenerKeys.removeValue(forKey: key) + } else { + return + } + LDClient.get(environment: environment)!.stopObserving(owner: owner) + } + + @objc func unregisterFeatureFlagListener(_ flagKey: String, environment: String) -> Void { + unregisterListener(flagKey, environment) + } - @objc func registerCurrentConnectionModeListener(_ listenerId: String) -> Void { + @objc func registerCurrentConnectionModeListener(_ listenerId: String, environment: String) -> Void { let currentConnectionModeOwner = listenerId as LDObserverOwner if listenerKeys[listenerId] == nil { listenerKeys.removeValue(forKey: listenerId) } else { return } - LDClient.get()!.observeCurrentConnectionMode(owner: currentConnectionModeOwner, handler: { (connectionMode) in + LDClient.get(environment: environment)!.observeCurrentConnectionMode(owner: currentConnectionModeOwner, handler: { (connectionMode) in if self.bridge != nil { - self.sendEvent(withName: self.CONNECTION_MODE_PREFIX, body: ["connectionMode": connectionMode]) + self.sendEvent(withName: self.CONNECTION_MODE_PREFIX, body: ["connectionMode": connectionMode, "listenerId": self.envConcat(environment: environment, identifier: listenerId)]) } }) } - @objc func unregisterCurrentConnectionModeListener(_ listenerId: String) -> Void { - unregisterListener(listenerId) + @objc func unregisterCurrentConnectionModeListener(_ listenerId: String, environment: String) -> Void { + unregisterListener(listenerId, environment) } - @objc func registerAllFlagsListener(_ listenerId: String) -> Void { + @objc func registerAllFlagsListener(_ listenerId: String, environment: String) -> Void { let flagChangeOwner = listenerId as LDObserverOwner if listenerKeys[listenerId] == nil { listenerKeys[listenerId] = flagChangeOwner } else { return } - LDClient.get()!.observeAll(owner: flagChangeOwner, handler: { (changedFlags) in + LDClient.get(environment: environment)!.observeAll(owner: flagChangeOwner, handler: { (changedFlags) in if self.bridge != nil { - self.sendEvent(withName: self.ALL_FLAGS_PREFIX, body: ["flagKeys": changedFlags.keys.description]) + self.sendEvent(withName: self.ALL_FLAGS_PREFIX, body: ["flagKeys": changedFlags.keys.description, "listenerId": self.envConcat(environment: environment, identifier: listenerId)]) } }) } - @objc func unregisterAllFlagsListener(_ listenerId: String) -> Void { - unregisterListener(listenerId) + @objc func unregisterAllFlagsListener(_ listenerId: String, environment: String) -> Void { + unregisterListener(listenerId, environment) } - @objc func isInitialized(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + @objc func isInitialized(_ environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { if LDClient.get() == nil { resolve(false) } else { - resolve(LDClient.get()!.isInitialized) + resolve(LDClient.get(environment: environment)!.isInitialized) } } } diff --git a/ios/LaunchdarklyReactNativeClientBridge.m b/ios/LaunchdarklyReactNativeClientBridge.m index af3931a..76bf0c3 100644 --- a/ios/LaunchdarklyReactNativeClientBridge.m +++ b/ios/LaunchdarklyReactNativeClientBridge.m @@ -7,85 +7,85 @@ @interface RCT_EXTERN_MODULE(LaunchdarklyReactNativeClient, RCTEventEmitter) RCT_EXTERN_METHOD(configureWithTimeout:(NSDictionary *)config userConfig:(NSDictionary *)userConfig timeout:(NSInteger *)timeout resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(boolVariationDefaultValue:(NSString *)flagKey defaultValue:(BOOL *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(boolVariationDefaultValue:(NSString *)flagKey defaultValue:(BOOL *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(intVariationDefaultValue:(NSString *)flagKey defaultValue:(NSInteger *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(intVariationDefaultValue:(NSString *)flagKey defaultValue:(NSInteger *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(floatVariationDefaultValue:(NSString *)flagKey defaultValue:(CGFloat *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(floatVariationDefaultValue:(NSString *)flagKey defaultValue:(CGFloat *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(stringVariationDefaultValue:(NSString *)flagKey defaultValue:(NSString *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(stringVariationDefaultValue:(NSString *)flagKey defaultValue:(NSString *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(boolVariation:(NSString *)flagKey resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(boolVariation:(NSString *)flagKey environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(intVariation:(NSString *)flagKey resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(intVariation:(NSString *)flagKey environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(floatVariation:(NSString *)flagKey resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(floatVariation:(NSString *)flagKey environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(stringVariation:(NSString *)flagKey resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(stringVariation:(NSString *)flagKey environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(jsonVariationNone:(NSString *)flagKey resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(jsonVariationNone:(NSString *)flagKey environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(jsonVariationNumber:(NSString *)flagKey defaultValue:(NSNumber *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(jsonVariationNumber:(NSString *)flagKey defaultValue:(NSNumber *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(jsonVariationBool:(NSString *)flagKey defaultValue:(BOOL *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(jsonVariationBool:(NSString *)flagKey defaultValue:(BOOL *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(jsonVariationString:(NSString *)flagKey defaultValue:(NSString *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(jsonVariationString:(NSString *)flagKey defaultValue:(NSString *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(jsonVariationArray:(NSString *)flagKey defaultValue:(NSArray *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(jsonVariationArray:(NSString *)flagKey defaultValue:(NSArray *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(jsonVariationObject:(NSString *)flagKey defaultValue:(NSDictionary *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(jsonVariationObject:(NSString *)flagKey defaultValue:(NSDictionary *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(boolVariationDetailDefaultValue:(NSString *)flagKey defaultValue:(BOOL *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(boolVariationDetailDefaultValue:(NSString *)flagKey defaultValue:(BOOL *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(intVariationDetailDefaultValue:(NSString *)flagKey defaultValue:(NSInteger *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(intVariationDetailDefaultValue:(NSString *)flagKey defaultValue:(NSInteger *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(floatVariationDetailDefaultValue:(NSString *)flagKey defaultValue:(CGFloat *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(floatVariationDetailDefaultValue:(NSString *)flagKey defaultValue:(CGFloat *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(stringVariationDetailDefaultValue:(NSString *)flagKey defaultValue:(NSString *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(stringVariationDetailDefaultValue:(NSString *)flagKey defaultValue:(NSString *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(boolVariationDetail:(NSString *)flagKey resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(boolVariationDetail:(NSString *)flagKey environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(intVariationDetail:(NSString *)flagKey resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(intVariationDetail:(NSString *)flagKey environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(floatVariationDetail:(NSString *)flagKey resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(floatVariationDetail:(NSString *)flagKey environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(stringVariationDetail:(NSString *)flagKey resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(stringVariationDetail:(NSString *)flagKey environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(jsonVariationDetailNone:(NSString *)flagKey resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(jsonVariationDetailNone:(NSString *)flagKey environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(jsonVariationDetailNumber:(NSString *)flagKey defaultValue:(NSNumber *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(jsonVariationDetailNumber:(NSString *)flagKey defaultValue:(NSNumber *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(jsonVariationDetailBool:(NSString *)flagKey defaultValue:(BOOL *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(jsonVariationDetailBool:(NSString *)flagKey defaultValue:(BOOL *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(jsonVariationDetailString:(NSString *)flagKey defaultValue:(NSString *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(jsonVariationDetailString:(NSString *)flagKey defaultValue:(NSString *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(jsonVariationDetailArray:(NSString *)flagKey defaultValue:(NSArray *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(jsonVariationDetailArray:(NSString *)flagKey defaultValue:(NSArray *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(jsonVariationDetailObject:(NSString *)flagKey defaultValue:(NSDictionary *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(jsonVariationDetailObject:(NSString *)flagKey defaultValue:(NSDictionary *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(trackBool:(NSString *)eventName data:(BOOL *)data) +RCT_EXTERN_METHOD(trackBool:(NSString *)eventName data:(BOOL *)data environment:(NSString *)environment) -RCT_EXTERN_METHOD(trackArray:(NSString *)eventName data:(NSArray *)data) +RCT_EXTERN_METHOD(trackArray:(NSString *)eventName data:(NSArray *)data environment:(NSString *)environment) -RCT_EXTERN_METHOD(trackNumber:(NSString *)eventName data:(NSNumber * _Nonnull)data) +RCT_EXTERN_METHOD(trackNumber:(NSString *)eventName data:(NSNumber * _Nonnull)data environment:(NSString *)environment) -RCT_EXTERN_METHOD(trackString:(NSString *)eventName data:(NSString *)data) +RCT_EXTERN_METHOD(trackString:(NSString *)eventName data:(NSString *)data environment:(NSString *)environment) -RCT_EXTERN_METHOD(trackObject:(NSString *)eventName data:(NSDictionary *)data) +RCT_EXTERN_METHOD(trackObject:(NSString *)eventName data:(NSDictionary *)data environment:(NSString *)environment) -RCT_EXTERN_METHOD(track:(NSString *)eventName) +RCT_EXTERN_METHOD(track:(NSString *)eventName environment:(NSString *)environment) -RCT_EXTERN_METHOD(trackBoolMetricValue:(NSString *)eventName data:(BOOL *)data metricValue:(NSNumber * _Nonnull)metricValue) +RCT_EXTERN_METHOD(trackBoolMetricValue:(NSString *)eventName data:(BOOL *)data metricValue:(NSNumber * _Nonnull)metricValue environment:(NSString *)environment) -RCT_EXTERN_METHOD(trackArrayMetricValue:(NSString *)eventName data:(NSArray *)data metricValue:(NSNumber * _Nonnull)metricValue) +RCT_EXTERN_METHOD(trackArrayMetricValue:(NSString *)eventName data:(NSArray *)data metricValue:(NSNumber * _Nonnull)metricValue environment:(NSString *)environment) -RCT_EXTERN_METHOD(trackNumberMetricValue:(NSString *)eventName data:(NSNumber * _Nonnull)data metricValue:(NSNumber * _Nonnull)metricValue) +RCT_EXTERN_METHOD(trackNumberMetricValue:(NSString *)eventName data:(NSNumber *)data metricValue:(NSNumber * _Nonnull)metricValue environment:(NSString *)environment) -RCT_EXTERN_METHOD(trackStringMetricValue:(NSString *)eventName data:(NSString *)data metricValue:(NSNumber * _Nonnull)metricValue) +RCT_EXTERN_METHOD(trackStringMetricValue:(NSString *)eventName data:(NSString *)data metricValue:(NSNumber * _Nonnull)metricValue environment:(NSString *)environment) -RCT_EXTERN_METHOD(trackObjectMetricValue:(NSString *)eventName data:(NSDictionary *)data metricValue:(NSNumber * _Nonnull)metricValue) +RCT_EXTERN_METHOD(trackObjectMetricValue:(NSString *)eventName data:(NSDictionary *)data metricValue:(NSNumber * _Nonnull)metricValue environment:(NSString *)environment) -RCT_EXTERN_METHOD(trackMetricValue:(NSString *)eventName metricValue:(NSNumber * _Nonnull)metricValue) +RCT_EXTERN_METHOD(trackMetricValue:(NSString *)eventName metricValue:(NSNumber * _Nonnull)metricValue environment:(NSString *)environment) RCT_EXTERN_METHOD(setOffline:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) @@ -99,28 +99,28 @@ @interface RCT_EXTERN_MODULE(LaunchdarklyReactNativeClient, RCTEventEmitter) RCT_EXTERN_METHOD(identify:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(allFlags:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(allFlags:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(registerFeatureFlagListener:(NSString *)flagKey) +RCT_EXTERN_METHOD(registerFeatureFlagListener:(NSString *)flagKey environment:(NSString *)environment) -RCT_EXTERN_METHOD(unregisterFeatureFlagListener:(NSString *)flagKey) +RCT_EXTERN_METHOD(unregisterFeatureFlagListener:(NSString *)flagKey environment:(NSString *)environment) -RCT_EXTERN_METHOD(registerCurrentConnectionModeListener:(NSString *)listenerId) +RCT_EXTERN_METHOD(registerCurrentConnectionModeListener:(NSString *)listenerId environment:(NSString *)environment) -RCT_EXTERN_METHOD(unregisterCurrentConnectionModeListener:(NSString *)listenerId) +RCT_EXTERN_METHOD(unregisterCurrentConnectionModeListener:(NSString *)listenerId environment:(NSString *)environment) -RCT_EXTERN_METHOD(registerAllFlagsListener:(NSString *)listenerId) +RCT_EXTERN_METHOD(registerAllFlagsListener:(NSString *)listenerId environment:(NSString *)environment) -RCT_EXTERN_METHOD(unregisterAllFlagsListener:(NSString *)listenerId) +RCT_EXTERN_METHOD(unregisterAllFlagsListener:(NSString *)listenerId environment:(NSString *)environment) -RCT_EXTERN_METHOD(isInitialized:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(isInitialized:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(getConnectionMode:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(getConnectionMode:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(getLastSuccessfulConnection:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(getLastSuccessfulConnection:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(getLastFailedConnection:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(getLastFailedConnection:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(getLastFailure:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(getLastFailure:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) @end diff --git a/test-types.ts b/test-types.ts index f311656..7071b19 100644 --- a/test-types.ts +++ b/test-types.ts @@ -41,6 +41,7 @@ async function tests() { offline: true, debugMode: true, evaluationReasons: true, + secondaryMobileKeys: {'test' : 'fake_key'}, maxCachedUsers: 6, diagnosticOptOut: true, diagnosticRecordingIntervalMillis: 100000, @@ -79,6 +80,12 @@ async function tests() { const stringDetail: LDEvaluationDetail = await client.stringVariationDetail('key', 'default'); const jsonDetail: LDEvaluationDetail> = await client.jsonVariationDetail('key', jsonObj); + const boolDetailMulti: LDEvaluationDetail = await client.boolVariationDetail('key', false, 'test'); + const intDetailMulti: LDEvaluationDetail = await client.intVariationDetail('key', 2, 'test'); + const floatDetailMulti: LDEvaluationDetail = await client.floatVariationDetail('key', 2.3, 'test'); + const stringDetailMulti: LDEvaluationDetail = await client.stringVariationDetail('key', 'default', 'test'); + const jsonDetailMulti: LDEvaluationDetail> = await client.jsonVariationDetail('key', jsonObj, 'test'); + const detailIndex: number | undefined = boolDetail.variationIndex; const detailReason: LDEvaluationReason = boolDetail.reason; const detailBoolValue: boolean = boolDetail.value; From 4ca1a1f5cfcb7697c5db21fd1da364e0b240c792 Mon Sep 17 00:00:00 2001 From: Joe Cieslik <5600929+torchhound@users.noreply.github.com> Date: Tue, 11 May 2021 14:23:57 -0700 Subject: [PATCH 10/32] Add secondary user attribute (#76) --- index.d.ts | 7 +++++++ ios/LaunchdarklyReactNativeClient.swift | 4 ++++ test-types.ts | 3 ++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index 8bc2cbd..860e678 100644 --- a/index.d.ts +++ b/index.d.ts @@ -184,6 +184,13 @@ declare module 'launchdarkly-react-native-client-sdk' { */ key: string; + /** + * The secondary key for the user. See the + * [documentation](https://docs.launchdarkly.com/home/managing-flags/targeting-users#percentage-rollout-logic) + * for more information on it's use for percentage rollout bucketing. + */ + secondary?: string; + /** * The user's name. * diff --git a/ios/LaunchdarklyReactNativeClient.swift b/ios/LaunchdarklyReactNativeClient.swift index dc72386..3b42dc2 100644 --- a/ios/LaunchdarklyReactNativeClient.swift +++ b/ios/LaunchdarklyReactNativeClient.swift @@ -152,6 +152,10 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { private func userBuild(userDict: NSDictionary) -> LDUser? { var user = LDUser() user.key = userDict["key"] as! String + + if userDict["secondary"] != nil { + user.secondary = userDict["secondary"] as? String + } if userDict["name"] != nil { user.name = userDict["name"] as? String diff --git a/test-types.ts b/test-types.ts index 7071b19..94b3b69 100644 --- a/test-types.ts +++ b/test-types.ts @@ -50,6 +50,7 @@ async function tests() { const userWithKeyOnly: LDUser = { key: 'user' }; const user: LDUser = { key: 'user', + secondary: 'user.secondary', name: 'name', firstName: 'first', lastName: 'last', @@ -65,7 +66,7 @@ async function tests() { const timeoutClient: LDClient = new LDClient(); const configure: null = await client.configure(configWithAllOptions, user); - const configureWithTimeout: null = await timeoutClient.configure(configWithAllOptions, user, 10); + const configureWithTimeout: null = await timeoutClient.configure(configWithAllOptions, userWithKeyOnly, 10); const identify: null = await client.identify(user); const boolFlagValue: boolean = await client.boolVariation('key', false); From 81ae48dc076fa22c0aa79889d12ac9f4ca8bc84c Mon Sep 17 00:00:00 2001 From: Joe Cieslik <5600929+torchhound@users.noreply.github.com> Date: Wed, 19 May 2021 12:55:58 -0500 Subject: [PATCH 11/32] Fix multi environment on restwrapper (#77) * remove platform specific default env name * Replace function with ternary * Remove _getEnvironment --- .../LaunchdarklyReactNativeClientModule.java | 3 -- index.js | 50 +++++++++---------- ios/LaunchdarklyReactNativeClient.swift | 3 +- 3 files changed, 24 insertions(+), 32 deletions(-) diff --git a/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java b/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java index 97961e4..9bf022c 100644 --- a/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java +++ b/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java @@ -169,8 +169,6 @@ public String getName() { private static final String ALL_FLAGS_PREFIX = "LaunchDarkly-All-Flags-"; private static final String CONNECTION_MODE_PREFIX = "LaunchDarkly-Connection-Mode-"; - private static final String DEFAULT_ENVIRONMENT = "default"; - /** * Called automatically by the React Native bridging layer to associate constants with the * object used to call into native modules. @@ -183,7 +181,6 @@ public Map getConstants() { constants.put("FLAG_PREFIX", FLAG_PREFIX); constants.put("ALL_FLAGS_PREFIX", ALL_FLAGS_PREFIX); constants.put("CONNECTION_MODE_PREFIX", CONNECTION_MODE_PREFIX); - constants.put("DEFAULT_ENVIRONMENT", DEFAULT_ENVIRONMENT); return constants; } diff --git a/index.js b/index.js index 43d6065..9dcea3d 100644 --- a/index.js +++ b/index.js @@ -18,10 +18,6 @@ export default class LDClient { return String(version); } - _getEnvironment(environment) { - return environment !== undefined ? environment : LaunchdarklyReactNativeClient.DEFAULT_ENVIRONMENT; - } - configure(config, user, timeout) { if (this.isInitialized() == true) { Promise.reject('LaunchDarkly SDK already initialized'); @@ -42,7 +38,7 @@ export default class LDClient { } boolVariation(flagKey, defaultValue, environment) { - const env = this._getEnvironment(environment); + const env = environment !== undefined ? environment : "default"; if (defaultValue == undefined) { return LaunchdarklyReactNativeClient.boolVariation(flagKey, env); } else { @@ -51,7 +47,7 @@ export default class LDClient { } intVariation(flagKey, defaultValue, environment) { - const env = this._getEnvironment(environment); + const env = environment !== undefined ? environment : "default"; if (defaultValue == undefined) { return LaunchdarklyReactNativeClient.intVariation(flagKey, env); } else { @@ -60,7 +56,7 @@ export default class LDClient { } floatVariation(flagKey, defaultValue, environment) { - const env = this._getEnvironment(environment); + const env = environment !== undefined ? environment : "default"; if (defaultValue == undefined) { return LaunchdarklyReactNativeClient.floatVariation(flagKey, env); } else { @@ -69,7 +65,7 @@ export default class LDClient { } stringVariation(flagKey, defaultValue, environment) { - const env = this._getEnvironment(environment); + const env = environment !== undefined ? environment : "default"; if (defaultValue == undefined) { return LaunchdarklyReactNativeClient.stringVariation(flagKey, env); } else { @@ -78,7 +74,7 @@ export default class LDClient { } jsonVariation(flagKey, defaultValue, environment) { - const env = this._getEnvironment(environment); + const env = environment !== undefined ? environment : "default"; if (defaultValue == undefined) { return LaunchdarklyReactNativeClient.jsonVariationNone(flagKey, env); } else if (typeof defaultValue === 'number') { @@ -96,7 +92,7 @@ export default class LDClient { } boolVariationDetail(flagKey, defaultValue, environment) { - const env = this._getEnvironment(environment); + const env = environment !== undefined ? environment : "default"; if (defaultValue == undefined) { return LaunchdarklyReactNativeClient.boolVariationDetail(flagKey, env); } else { @@ -105,7 +101,7 @@ export default class LDClient { } intVariationDetail(flagKey, defaultValue, environment) { - const env = this._getEnvironment(environment); + const env = environment !== undefined ? environment : "default"; if (defaultValue == undefined) { return LaunchdarklyReactNativeClient.intVariationDetail(flagKey, env); } else { @@ -114,7 +110,7 @@ export default class LDClient { } floatVariationDetail(flagKey, defaultValue, environment) { - const env = this._getEnvironment(environment); + const env = environment !== undefined ? environment : "default"; if (defaultValue == undefined) { return LaunchdarklyReactNativeClient.floatVariationDetail(flagKey, env); } else { @@ -123,7 +119,7 @@ export default class LDClient { } stringVariationDetail(flagKey, defaultValue, environment) { - const env = this._getEnvironment(environment); + const env = environment !== undefined ? environment : "default"; if (defaultValue == undefined) { return LaunchdarklyReactNativeClient.stringVariationDetail(flagKey, env); } else { @@ -132,7 +128,7 @@ export default class LDClient { } jsonVariationDetail(flagKey, defaultValue, environment) { - const env = this._getEnvironment(environment); + const env = environment !== undefined ? environment : "default"; if (defaultValue == undefined) { return LaunchdarklyReactNativeClient.jsonVariationDetailNone(flagKey, env); } else if (typeof defaultValue === 'number') { @@ -150,12 +146,12 @@ export default class LDClient { } allFlags(environment) { - const env = this._getEnvironment(environment); + const env = environment !== undefined ? environment : "default"; return LaunchdarklyReactNativeClient.allFlags(env); } track(eventName, data, metricValue, environment) { - const env = this._getEnvironment(environment); + const env = environment !== undefined ? environment : "default"; if (metricValue) { if (data === null || typeof data === 'undefined') { LaunchdarklyReactNativeClient.trackMetricValue(eventName, metricValue, env); @@ -202,7 +198,7 @@ export default class LDClient { } isInitialized(environment) { - const env = this._getEnvironment(environment); + const env = environment !== undefined ? environment : "default"; return LaunchdarklyReactNativeClient.isInitialized(env); } @@ -262,7 +258,7 @@ export default class LDClient { if (typeof callback !== "function") { return; } - const env = this._getEnvironment(environment); + const env = environment !== undefined ? environment : "default"; const multiFlagKey = this._envConcat(env, flagKey); if (this.flagListeners.hasOwnProperty(multiFlagKey)) { @@ -275,7 +271,7 @@ export default class LDClient { } unregisterFeatureFlagListener(flagKey, callback, environment) { - const env = this._getEnvironment(environment); + const env = environment !== undefined ? environment : "default"; const multiFlagKey = this._envConcat(env, flagKey); if (!this.flagListeners.hasOwnProperty(multiFlagKey)) { return; @@ -294,7 +290,7 @@ export default class LDClient { if (typeof callback !== "function") { return; } - const env = this._getEnvironment(environment); + const env = environment !== undefined ? environment : "default"; const multiListenerId = this._envConcat(env, flagKey); this.connectionModeListeners[multiListenerId] = callback; @@ -302,7 +298,7 @@ export default class LDClient { } unregisterCurrentConnectionModeListener(listenerId, environment) { - const env = this._getEnvironment(environment); + const env = environment !== undefined ? environment : "default"; const multiListenerId = this._envConcat(env, flagKey); if (!this.connectionModeListeners.hasOwnProperty(multiListenerId)) { return; @@ -316,7 +312,7 @@ export default class LDClient { if (typeof callback !== "function") { return; } - const env = this._getEnvironment(environment); + const env = environment !== undefined ? environment : "default"; const multiListenerId = this._envConcat(env, flagKey); this.allFlagsListeners[multiListenerId] = callback; @@ -324,7 +320,7 @@ export default class LDClient { } unregisterAllFlagsListener(listenerId, environment) { - const env = this._getEnvironment(environment); + const env = environment !== undefined ? environment : "default"; const multiListenerId = this._envConcat(env, flagKey); if (!this.allFlagsListeners.hasOwnProperty(multiListenerId)) { return; @@ -335,22 +331,22 @@ export default class LDClient { } getConnectionMode(environment) { - const env = this._getEnvironment(environment); + const env = environment !== undefined ? environment : "default"; return LaunchdarklyReactNativeClient.getConnectionMode(env); } getLastSuccessfulConnection(environment) { - const env = this._getEnvironment(environment); + const env = environment !== undefined ? environment : "default"; return LaunchdarklyReactNativeClient.getLastSuccessfulConnection(env); } getLastFailedConnection(environment) { - const env = this._getEnvironment(environment); + const env = environment !== undefined ? environment : "default"; return LaunchdarklyReactNativeClient.getLastFailedConnection(env); } getLastFailure(environment) { - const env = this._getEnvironment(environment); + const env = environment !== undefined ? environment : "default"; return LaunchdarklyReactNativeClient.getLastFailure(env); } } diff --git a/ios/LaunchdarklyReactNativeClient.swift b/ios/LaunchdarklyReactNativeClient.swift index 3b42dc2..0dd1488 100644 --- a/ios/LaunchdarklyReactNativeClient.swift +++ b/ios/LaunchdarklyReactNativeClient.swift @@ -11,14 +11,13 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { private let CONNECTION_MODE_PREFIX = "LaunchDarkly-Connection-Mode-" private let ERROR_INIT = "E_INITIALIZE" private let ERROR_IDENTIFY = "E_IDENTIFY" - private let DEFAULT_ENVIRONMENT = "default" override func supportedEvents() -> [String]! { return [FLAG_PREFIX, ALL_FLAGS_PREFIX, CONNECTION_MODE_PREFIX] } override func constantsToExport() -> [AnyHashable: Any] { - return ["FLAG_PREFIX": FLAG_PREFIX, "ALL_FLAGS_PREFIX": ALL_FLAGS_PREFIX, "CONNECTION_MODE_PREFIX": CONNECTION_MODE_PREFIX, "DEFAULT_ENVIRONMENT": DEFAULT_ENVIRONMENT] + return ["FLAG_PREFIX": FLAG_PREFIX, "ALL_FLAGS_PREFIX": ALL_FLAGS_PREFIX, "CONNECTION_MODE_PREFIX": CONNECTION_MODE_PREFIX] } override static func requiresMainQueueSetup() -> Bool { From 83640b72dc12829736852ccce088735c9628ffcb Mon Sep 17 00:00:00 2001 From: Gavin Whelan Date: Fri, 28 May 2021 23:33:59 +0000 Subject: [PATCH 12/32] [ch109800] Await Android client initialization. (#78) Handle promise from `isInitialized` during configuration. Update iOS `isInitialized` to reject when not configured to match Android implementation. Improve Android `allFlags` to reject promise when non-existent environment is used or other exception, and allow calling `allFlags` before client initialization completes. --- .../LaunchdarklyReactNativeClientModule.java | 33 ++++++++--------- index.js | 37 +++++++++++-------- ios/LaunchdarklyReactNativeClient.swift | 7 +++- 3 files changed, 42 insertions(+), 35 deletions(-) diff --git a/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java b/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java index 9bf022c..cabce69 100644 --- a/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java +++ b/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java @@ -196,16 +196,11 @@ public void configureWithTimeout(ReadableMap config, ReadableMap user, Integer t private void internalConfigure(ReadableMap config, ReadableMap user, final Integer timeout, final Promise promise) { try { - if (LDClient.get() != null) { - promise.reject(ERROR_INIT, "Client was already initialized"); - return; - } - } catch (LaunchDarklyException e) { - //This exception indicates that the SDK has not been initialized yet - } catch (Exception e) { - Timber.w(e); - promise.reject(ERROR_INIT, e); + LDClient.get(); + promise.reject(ERROR_INIT, "Client was already initialized"); return; + } catch (LaunchDarklyException e) { + // This exception indicates that the SDK has not been initialized yet } final LDConfig.Builder ldConfigBuilder = configBuild(config); @@ -236,7 +231,11 @@ public void run() { if (timeout != null) { LDClient.init(application, ldConfigBuilder.build(), userBuilder.build(), timeout); } else { - LDClient.init(application, ldConfigBuilder.build(), userBuilder.build()); + try { + LDClient.init(application, ldConfigBuilder.build(), userBuilder.build()).get(); + } catch (ExecutionException | InterruptedException e) { + Timber.e(e, "Exception during Client initialization"); + } } promise.resolve(null); } @@ -605,13 +604,9 @@ private void resolveJsonElementDetail(Promise promise, EvaluationDetail { + throw new Error('LaunchDarkly SDK already initialized'); + }, + () => { + const configWithOverriddenDefaults = Object.assign({ + backgroundPollingIntervalMillis: 3600000, // the iOS SDK defaults this to 900000 + disableBackgroundUpdating: false, // the iOS SDK defaults this to true + pollUri: 'https://clientsdk.launchdarkly.com', + wrapperName: 'react-native-client-sdk', + wrapperVersion: this.getVersion() + }, config); + + if (timeout == undefined) { + return LaunchdarklyReactNativeClient.configure(configWithOverriddenDefaults, this._addUserOverrides(user)); + } else { + return LaunchdarklyReactNativeClient.configureWithTimeout(configWithOverriddenDefaults, this._addUserOverrides(user), timeout); + } + } + ); } boolVariation(flagKey, defaultValue, environment) { diff --git a/ios/LaunchdarklyReactNativeClient.swift b/ios/LaunchdarklyReactNativeClient.swift index 0dd1488..d0308f2 100644 --- a/ios/LaunchdarklyReactNativeClient.swift +++ b/ios/LaunchdarklyReactNativeClient.swift @@ -11,6 +11,7 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { private let CONNECTION_MODE_PREFIX = "LaunchDarkly-Connection-Mode-" private let ERROR_INIT = "E_INITIALIZE" private let ERROR_IDENTIFY = "E_IDENTIFY" + private let ERROR_UNKNOWN = "E_UNKNOWN" override func supportedEvents() -> [String]! { return [FLAG_PREFIX, ALL_FLAGS_PREFIX, CONNECTION_MODE_PREFIX] @@ -605,9 +606,11 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { @objc func isInitialized(_ environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { if LDClient.get() == nil { - resolve(false) + reject(ERROR_UNKNOWN, "SDK has not been configured", nil) + } else if let client = LDClient.get(environment: environment) { + resolve(client.isInitialized) } else { - resolve(LDClient.get(environment: environment)!.isInitialized) + reject(ERROR_UNKNOWN, "SDK not configured with requested environment", nil) } } } From 2668c2ab7a384cbbf6a8396aa0835d3faa7e5b84 Mon Sep 17 00:00:00 2001 From: Ben Woskow <48036130+bwoskow-ld@users.noreply.github.com> Date: Tue, 1 Jun 2021 13:48:20 -0700 Subject: [PATCH 13/32] Update iOS method signature to match implementation (#79) --- ios/LaunchdarklyReactNativeClientBridge.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/LaunchdarklyReactNativeClientBridge.m b/ios/LaunchdarklyReactNativeClientBridge.m index 76bf0c3..835e4b2 100644 --- a/ios/LaunchdarklyReactNativeClientBridge.m +++ b/ios/LaunchdarklyReactNativeClientBridge.m @@ -5,7 +5,7 @@ @interface RCT_EXTERN_MODULE(LaunchdarklyReactNativeClient, RCTEventEmitter) RCT_EXTERN_METHOD(configure:(NSDictionary *)config user:(NSDictionary *)user resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(configureWithTimeout:(NSDictionary *)config userConfig:(NSDictionary *)userConfig timeout:(NSInteger *)timeout resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(configureWithTimeout:(NSDictionary *)config user:(NSDictionary *)user timeout:(NSInteger *)timeout resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) RCT_EXTERN_METHOD(boolVariationDefaultValue:(NSString *)flagKey defaultValue:(BOOL *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) From 8399a02b7d163e948f3518fa8264c59b6201886e Mon Sep 17 00:00:00 2001 From: Ben Woskow Date: Wed, 2 Jun 2021 13:07:18 -0700 Subject: [PATCH 14/32] Releasing version 4.0.4 --- CHANGELOG.md | 4 ++++ ios/LaunchdarklyReactNativeClient.podspec | 2 +- ios/LaunchdarklyReactNativeClientBridge.m | 2 +- package-lock.json | 2 +- package.json | 2 +- 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05ea3ec..9e8357e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to the LaunchDarkly React Native SDK will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org). +## [4.0.4] - 2021-06-02 +### Fixed: +- iOS: Fixed an issue where an exception was thrown when calling `LDClient.configure` with an optional `timeout` ([#80](https://github.com/launchdarkly/react-native-client-sdk/issues/80)). + ## [4.0.3] - 2021-04-28 ### Fixed: - The `LDEvaluationReasonErrorKind`, `LDEvaluationReasonKind`, `LDConnectionMode`, and `LDFailureReason` enum TypeScript types were undefined when evaluated at runtime due to being defined in an ambient context. diff --git a/ios/LaunchdarklyReactNativeClient.podspec b/ios/LaunchdarklyReactNativeClient.podspec index bbaf530..f5961cd 100644 --- a/ios/LaunchdarklyReactNativeClient.podspec +++ b/ios/LaunchdarklyReactNativeClient.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "LaunchdarklyReactNativeClient" - s.version = "4.0.3" + s.version = "4.0.4" s.summary = "LaunchdarklyReactNativeClient" s.description = <<-DESC LaunchdarklyReactNativeClient diff --git a/ios/LaunchdarklyReactNativeClientBridge.m b/ios/LaunchdarklyReactNativeClientBridge.m index af3931a..24bd9be 100644 --- a/ios/LaunchdarklyReactNativeClientBridge.m +++ b/ios/LaunchdarklyReactNativeClientBridge.m @@ -5,7 +5,7 @@ @interface RCT_EXTERN_MODULE(LaunchdarklyReactNativeClient, RCTEventEmitter) RCT_EXTERN_METHOD(configure:(NSDictionary *)config user:(NSDictionary *)user resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(configureWithTimeout:(NSDictionary *)config userConfig:(NSDictionary *)userConfig timeout:(NSInteger *)timeout resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(configureWithTimeout:(NSDictionary *)config user:(NSDictionary *)user timeout:(NSInteger *)timeout resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) RCT_EXTERN_METHOD(boolVariationDefaultValue:(NSString *)flagKey defaultValue:(BOOL *)defaultValue resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) diff --git a/package-lock.json b/package-lock.json index 202cd92..16aaa27 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "launchdarkly-react-native-client-sdk", - "version": "4.0.3", + "version": "4.0.4", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index dc932ea..1c89471 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "launchdarkly-react-native-client-sdk", - "version": "4.0.3", + "version": "4.0.4", "description": "", "main": "index.js", "types": "index.d.ts", From 4d003c08c19a743f4897e4e52a97b95de0467e2b Mon Sep 17 00:00:00 2001 From: Gavin Whelan Date: Mon, 14 Jun 2021 17:17:54 -0700 Subject: [PATCH 15/32] [ch110474] Fixes for undeclared variables and other callback issues. (#81) --- .../LaunchdarklyReactNativeClientModule.java | 37 ++++++++------ index.js | 25 +++++----- ios/LaunchdarklyReactNativeClient.swift | 49 +++++++------------ 3 files changed, 51 insertions(+), 60 deletions(-) diff --git a/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java b/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java index cabce69..47910e6 100644 --- a/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java +++ b/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java @@ -902,11 +902,13 @@ private String envConcat(String environment, String identifier) { @ReactMethod public void registerFeatureFlagListener(final String flagKey, final String environment) { - FeatureFlagChangeListener listener = new FeatureFlagChangeListener() { + final String multiListenerId = envConcat(environment, flagKey); + final FeatureFlagChangeListener listener = new FeatureFlagChangeListener() { @Override public void onFeatureFlagChange(String flagKey) { WritableMap result = Arguments.createMap(); - result.putString("flagKey", envConcat(environment, flagKey)); + result.putString("flagKey", flagKey); + result.putString("listenerId", multiListenerId); getReactApplicationContext() .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) @@ -924,10 +926,11 @@ public void onFeatureFlagChange(String flagKey) { @ReactMethod public void unregisterFeatureFlagListener(String flagKey, String environment) { + String multiListenerId = envConcat(environment, flagKey); try { - if (listeners.containsKey(flagKey)) { - LDClient.getForMobileKey(environment).unregisterFeatureFlagListener(flagKey, listeners.get(flagKey)); - listeners.remove(flagKey); + if (listeners.containsKey(multiListenerId)) { + LDClient.getForMobileKey(environment).unregisterFeatureFlagListener(flagKey, listeners.get(multiListenerId)); + listeners.remove(multiListenerId); } } catch (Exception e) { Timber.w(e); @@ -936,12 +939,13 @@ public void unregisterFeatureFlagListener(String flagKey, String environment) { @ReactMethod public void registerCurrentConnectionModeListener(final String listenerId, final String environment) { + final String multiListenerId = envConcat(environment, listenerId); LDStatusListener listener = new LDStatusListener() { @Override public void onConnectionModeChanged(ConnectionInformation connectionInfo) { WritableMap result = Arguments.createMap(); result.putString("connectionMode", gson.toJson(connectionInfo)); - result.putString("listenerId", envConcat(environment, listenerId)); + result.putString("listenerId", multiListenerId); getReactApplicationContext() .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) @@ -954,7 +958,7 @@ public void onInternalFailure(LDFailure ldFailure) {} try { LDClient.getForMobileKey(environment).registerStatusListener(listener); - connectionModeListeners.put(listenerId, listener); + connectionModeListeners.put(multiListenerId, listener); } catch (Exception e) { Timber.w(e); } @@ -963,9 +967,10 @@ public void onInternalFailure(LDFailure ldFailure) {} @ReactMethod public void unregisterCurrentConnectionModeListener(String listenerId, String environment) { try { - if (connectionModeListeners.containsKey(listenerId)) { - LDClient.getForMobileKey(environment).unregisterStatusListener(connectionModeListeners.get(listenerId)); - connectionModeListeners.remove(listenerId); + String multiListenerId = envConcat(environment, listenerId); + if (connectionModeListeners.containsKey(multiListenerId)) { + LDClient.getForMobileKey(environment).unregisterStatusListener(connectionModeListeners.get(multiListenerId)); + connectionModeListeners.remove(multiListenerId); } } catch (Exception e) { Timber.w(e); @@ -974,12 +979,13 @@ public void unregisterCurrentConnectionModeListener(String listenerId, String en @ReactMethod public void registerAllFlagsListener(final String listenerId, final String environment) { + final String multiListenerId = envConcat(environment, listenerId); LDAllFlagsListener listener = new LDAllFlagsListener() { @Override public void onChange(List flagKeys) { WritableMap result = Arguments.createMap(); result.putString("flagKeys", gson.toJson(flagKeys)); - result.putString("listenerId", envConcat(environment, listenerId)); + result.putString("listenerId", multiListenerId); getReactApplicationContext() .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) @@ -989,7 +995,7 @@ public void onChange(List flagKeys) { try { LDClient.getForMobileKey(environment).registerAllFlagsListener(listener); - allFlagsListeners.put(listenerId, listener); + allFlagsListeners.put(multiListenerId, listener); } catch (Exception e) { Timber.w(e); } @@ -998,9 +1004,10 @@ public void onChange(List flagKeys) { @ReactMethod public void unregisterAllFlagsListener(String listenerId, String environment) { try { - if (allFlagsListeners.containsKey(listenerId)) { - LDClient.getForMobileKey(environment).unregisterAllFlagsListener(allFlagsListeners.get(listenerId)); - allFlagsListeners.remove(listenerId); + String multiListenerId = envConcat(environment, listenerId); + if (allFlagsListeners.containsKey(multiListenerId)) { + LDClient.getForMobileKey(environment).unregisterAllFlagsListener(allFlagsListeners.get(multiListenerId)); + allFlagsListeners.remove(multiListenerId); } } catch (Exception e) { Timber.w(e); diff --git a/index.js b/index.js index 1681161..54d7d6f 100644 --- a/index.js +++ b/index.js @@ -227,8 +227,9 @@ export default class LDClient { _flagUpdateListener(changedFlag) { const flagKey = changedFlag.flagKey; - if (this.flagListeners.hasOwnProperty(flagKey)) { - let listeners = this.flagListeners[flagKey]; + const listenerId = changedFlag.listenerId; + if (this.flagListeners.hasOwnProperty(listenerId)) { + let listeners = this.flagListeners[listenerId]; for (const listener of listeners) { listener(flagKey); } @@ -238,20 +239,16 @@ export default class LDClient { _allFlagsUpdateListener(changedFlags) { const flagKeys = changedFlags.flagKeys; const listenerId = changedFlags.listenerId; - for (const [key, value] of Object.entries(this.allFlagsListeners)) { - if (key == listenerId) { - key(flagKeys); - } + if (this.allFlagsListeners.hasOwnProperty(listenerId)) { + this.allFlagsListeners[listenerId](flagKeys); } } _connectionModeUpdateListener(connectionStatus) { const connectionMode = connectionStatus.connectionMode; const listenerId = connectionStatus.listenerId; - for (const [key, value] of Object.entries(this.connectionModeListeners)) { - if (key == listenerId) { - key(connectionMode); - } + if (this.connectionModeListeners.hasOwnProperty(listenerId)) { + this.connectionModeListeners[listenerId](connectionMode); } } @@ -296,7 +293,7 @@ export default class LDClient { return; } const env = environment !== undefined ? environment : "default"; - const multiListenerId = this._envConcat(env, flagKey); + const multiListenerId = this._envConcat(env, listenerId); this.connectionModeListeners[multiListenerId] = callback; LaunchdarklyReactNativeClient.registerCurrentConnectionModeListener(listenerId, env); @@ -304,7 +301,7 @@ export default class LDClient { unregisterCurrentConnectionModeListener(listenerId, environment) { const env = environment !== undefined ? environment : "default"; - const multiListenerId = this._envConcat(env, flagKey); + const multiListenerId = this._envConcat(env, listenerId); if (!this.connectionModeListeners.hasOwnProperty(multiListenerId)) { return; } @@ -318,7 +315,7 @@ export default class LDClient { return; } const env = environment !== undefined ? environment : "default"; - const multiListenerId = this._envConcat(env, flagKey); + const multiListenerId = this._envConcat(env, listenerId); this.allFlagsListeners[multiListenerId] = callback; LaunchdarklyReactNativeClient.registerAllFlagsListener(listenerId, env); @@ -326,7 +323,7 @@ export default class LDClient { unregisterAllFlagsListener(listenerId, environment) { const env = environment !== undefined ? environment : "default"; - const multiListenerId = this._envConcat(env, flagKey); + const multiListenerId = this._envConcat(env, listenerId); if (!this.allFlagsListeners.hasOwnProperty(multiListenerId)) { return; } diff --git a/ios/LaunchdarklyReactNativeClient.swift b/ios/LaunchdarklyReactNativeClient.swift index 5344588..da7d816 100644 --- a/ios/LaunchdarklyReactNativeClient.swift +++ b/ios/LaunchdarklyReactNativeClient.swift @@ -541,27 +541,22 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { } @objc func registerFeatureFlagListener(_ flagKey: String, environment: String) -> Void { - let flagChangeOwner = flagKey as LDObserverOwner - if listenerKeys[flagKey] == nil { - listenerKeys[flagKey] = flagChangeOwner - } else { - return - } - LDClient.get(environment: environment)!.observe(keys: [flagKey], owner: flagChangeOwner, handler: { (changedFlags) in - if changedFlags[flagKey] != nil && self.bridge != nil { - self.sendEvent(withName: self.FLAG_PREFIX, body: ["flagKey": self.envConcat(environment: environment, identifier: flagKey)]) + let multiListenerId = envConcat(environment: environment, identifier: flagKey) + let flagChangeOwner = multiListenerId as LDObserverOwner + listenerKeys[multiListenerId] = flagChangeOwner + LDClient.get(environment: environment)!.observe(key: flagKey, owner: flagChangeOwner, handler: { changedFlag in + if self.bridge != nil { + self.sendEvent(withName: self.FLAG_PREFIX, body: ["flagKey": changedFlag.key, "listenerId": multiListenerId]) } }) } private func unregisterListener(_ key: String, _ environment: String) -> Void { - let owner = key as LDObserverOwner - if listenerKeys[key] != nil { - listenerKeys.removeValue(forKey: key) - } else { - return + let multiListenerId = envConcat(environment: environment, identifier: key) + let owner = multiListenerId as LDObserverOwner + if listenerKeys.removeValue(forKey: multiListenerId) != nil { + LDClient.get(environment: environment)!.stopObserving(owner: owner) } - LDClient.get(environment: environment)!.stopObserving(owner: owner) } @objc func unregisterFeatureFlagListener(_ flagKey: String, environment: String) -> Void { @@ -569,15 +564,11 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { } @objc func registerCurrentConnectionModeListener(_ listenerId: String, environment: String) -> Void { - let currentConnectionModeOwner = listenerId as LDObserverOwner - if listenerKeys[listenerId] == nil { - listenerKeys.removeValue(forKey: listenerId) - } else { - return - } - LDClient.get(environment: environment)!.observeCurrentConnectionMode(owner: currentConnectionModeOwner, handler: { (connectionMode) in + let multiListenerId = envConcat(environment: environment, identifier: listenerId) + let currentConnectionModeOwner = multiListenerId as LDObserverOwner + LDClient.get(environment: environment)!.observeCurrentConnectionMode(owner: currentConnectionModeOwner, handler: { connectionMode in if self.bridge != nil { - self.sendEvent(withName: self.CONNECTION_MODE_PREFIX, body: ["connectionMode": connectionMode, "listenerId": self.envConcat(environment: environment, identifier: listenerId)]) + self.sendEvent(withName: self.CONNECTION_MODE_PREFIX, body: ["connectionMode": connectionMode, "listenerId": multiListenerId]) } }) } @@ -587,15 +578,11 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { } @objc func registerAllFlagsListener(_ listenerId: String, environment: String) -> Void { - let flagChangeOwner = listenerId as LDObserverOwner - if listenerKeys[listenerId] == nil { - listenerKeys[listenerId] = flagChangeOwner - } else { - return - } - LDClient.get(environment: environment)!.observeAll(owner: flagChangeOwner, handler: { (changedFlags) in + let multiListenerId = envConcat(environment: environment, identifier: listenerId) + let flagChangeOwner = multiListenerId as LDObserverOwner + LDClient.get(environment: environment)!.observeAll(owner: flagChangeOwner, handler: { changedFlags in if self.bridge != nil { - self.sendEvent(withName: self.ALL_FLAGS_PREFIX, body: ["flagKeys": changedFlags.keys.description, "listenerId": self.envConcat(environment: environment, identifier: listenerId)]) + self.sendEvent(withName: self.ALL_FLAGS_PREFIX, body: ["flagKeys": Array(changedFlags.keys), "listenerId": multiListenerId]) } }) } From 093d62db92eea0a15be00af90e7758fb2f5bf435 Mon Sep 17 00:00:00 2001 From: Gavin Whelan Date: Tue, 15 Jun 2021 11:29:47 -0700 Subject: [PATCH 16/32] Changes for Android 3. (#82) --- android/build.gradle | 14 +- android/gradle.properties | 1 + .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../LaunchdarklyReactNativeClientModule.java | 669 +++++++----------- 4 files changed, 267 insertions(+), 419 deletions(-) create mode 100644 android/gradle.properties diff --git a/android/build.gradle b/android/build.gradle index 8eeeced..097fc57 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -2,7 +2,7 @@ buildscript { ext { buildToolsVersion = "28.0.2" - minSdkVersion = 16 + minSdkVersion = 21 compileSdkVersion = 28 targetSdkVersion = 28 supportLibVersion = "28.0.0" @@ -13,7 +13,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.3.2' + classpath 'com.android.tools.build:gradle:4.1.3' } } @@ -24,7 +24,7 @@ android { buildToolsVersion "28.0.3" defaultConfig { - minSdkVersion 16 + minSdkVersion 21 targetSdkVersion 26 versionCode 1 versionName "1.0" @@ -48,9 +48,9 @@ allprojects { } dependencies { - implementation 'com.facebook.react:react-native:+' - implementation 'com.launchdarkly:launchdarkly-android-client-sdk:2.14.1' - implementation 'com.jakewharton.timber:timber:4.7.1' - implementation "com.google.code.gson:gson:2.8.5" + implementation("com.facebook.react:react-native:+") + implementation("com.launchdarkly:launchdarkly-android-client-sdk:3.0.0") + implementation("com.jakewharton.timber:timber:4.7.1") + implementation("com.google.code.gson:gson:2.8.6") } diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..5bac8ac --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1 @@ +android.useAndroidX=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 82cdf6a..77115ed 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip diff --git a/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java b/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java index 47910e6..5229988 100644 --- a/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java +++ b/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java @@ -18,34 +18,31 @@ import com.facebook.react.bridge.WritableNativeMap; import com.facebook.react.modules.core.DeviceEventManagerModule; import com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonNull; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; -import com.google.gson.JsonParser; -import com.google.gson.JsonParseException; -import com.launchdarkly.android.FeatureFlagChangeListener; -import com.launchdarkly.android.LDClient; -import com.launchdarkly.android.LDConfig; -import com.launchdarkly.android.LDCountryCode; -import com.launchdarkly.android.LDUser; -import com.launchdarkly.android.ConnectionInformation; -import com.launchdarkly.android.LDStatusListener; -import com.launchdarkly.android.LDAllFlagsListener; -import com.launchdarkly.android.EvaluationDetail; -import com.launchdarkly.android.EvaluationReason; -import com.launchdarkly.android.LDFailure; -import com.launchdarkly.android.LaunchDarklyException; +import com.launchdarkly.sdk.ArrayBuilder; +import com.launchdarkly.sdk.EvaluationDetail; +import com.launchdarkly.sdk.EvaluationReason; +import com.launchdarkly.sdk.LDUser; +import com.launchdarkly.sdk.LDValue; +import com.launchdarkly.sdk.ObjectBuilder; +import com.launchdarkly.sdk.UserAttribute; +import com.launchdarkly.sdk.android.ConnectionInformation; +import com.launchdarkly.sdk.android.FeatureFlagChangeListener; +import com.launchdarkly.sdk.android.LDAllFlagsListener; +import com.launchdarkly.sdk.android.LDClient; +import com.launchdarkly.sdk.android.LDConfig; +import com.launchdarkly.sdk.android.LDFailure; +import com.launchdarkly.sdk.android.LDStatusListener; +import com.launchdarkly.sdk.android.LaunchDarklyException; + +import org.jetbrains.annotations.NotNull; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; -import java.util.List; import java.util.concurrent.ExecutionException; import timber.log.Timber; @@ -53,32 +50,36 @@ public class LaunchdarklyReactNativeClientModule extends ReactContextBaseJavaModule { enum ConfigMapping { - CONFIG_MOBILE_KEY("mobileKey", ConfigEntryType.String, "setMobileKey"), - CONFIG_BASE_URI("pollUri", ConfigEntryType.Uri, "setPollUri"), - CONFIG_EVENTS_URI("eventsUri", ConfigEntryType.UriMobile, "setEventsUri"), - CONFIG_STREAM_URI("streamUri", ConfigEntryType.Uri, "setStreamUri"), - CONFIG_EVENTS_CAPACITY("eventsCapacity", ConfigEntryType.Integer, "setEventsCapacity"), - CONFIG_EVENTS_FLUSH_INTERVAL("eventsFlushIntervalMillis", ConfigEntryType.Integer, "setEventsFlushIntervalMillis"), - CONFIG_CONNECTION_TIMEOUT("connectionTimeoutMillis", ConfigEntryType.Integer, "setConnectionTimeoutMillis"), - CONFIG_POLLING_INTERVAL("pollingIntervalMillis", ConfigEntryType.Integer, "setPollingIntervalMillis"), - CONFIG_BACKGROUND_POLLING_INTERVAL("backgroundPollingIntervalMillis", ConfigEntryType.Integer, "setBackgroundPollingIntervalMillis"), - CONFIG_USE_REPORT("useReport", ConfigEntryType.Boolean, "setUseReport"), - CONFIG_STREAM("stream", ConfigEntryType.Boolean, "setStream"), - CONFIG_DISABLE_BACKGROUND_UPDATING("disableBackgroundUpdating", ConfigEntryType.Boolean, "setDisableBackgroundUpdating"), - CONFIG_OFFLINE("offline", ConfigEntryType.Boolean, "setOffline"), - CONFIG_PRIVATE_ATTRIBUTES("privateAttributeNames", ConfigEntryType.StringSet, "setPrivateAttributeNames"), - CONFIG_EVALUATION_REASONS("evaluationReasons", ConfigEntryType.Boolean, "setEvaluationReasons"), - CONFIG_WRAPPER_NAME("wrapperName", ConfigEntryType.String, "setWrapperName"), - CONFIG_WRAPPER_VERSION("wrapperVersion", ConfigEntryType.String, "setWrapperVersion"), - CONFIG_MAX_CACHED_USERS("maxCachedUsers", ConfigEntryType.Integer, "setMaxCachedUsers"), - CONFIG_DIAGNOSTIC_OPT_OUT("diagnosticOptOut", ConfigEntryType.Boolean, "setDiagnosticOptOut"), - CONFIG_DIAGNOSTIC_RECORDING_INTERVAL("diagnosticRecordingIntervalMillis", ConfigEntryType.Integer, "setDiagnosticRecordingIntervalMillis"), - CONFIG_SECONDARY_MOBILE_KEYS("secondaryMobileKeys", ConfigEntryType.Map, "setSecondaryMobileKeys"); + CONFIG_MOBILE_KEY("mobileKey", ConfigEntryType.String), + CONFIG_BASE_URI("pollUri", ConfigEntryType.Uri), + CONFIG_EVENTS_URI("eventsUri", ConfigEntryType.Uri), + CONFIG_STREAM_URI("streamUri", ConfigEntryType.Uri), + CONFIG_EVENTS_CAPACITY("eventsCapacity", ConfigEntryType.Integer), + CONFIG_EVENTS_FLUSH_INTERVAL("eventsFlushIntervalMillis", ConfigEntryType.Integer), + CONFIG_CONNECTION_TIMEOUT("connectionTimeoutMillis", ConfigEntryType.Integer), + CONFIG_POLLING_INTERVAL("pollingIntervalMillis", ConfigEntryType.Integer), + CONFIG_BACKGROUND_POLLING_INTERVAL("backgroundPollingIntervalMillis", ConfigEntryType.Integer), + CONFIG_USE_REPORT("useReport", ConfigEntryType.Boolean), + CONFIG_STREAM("stream", ConfigEntryType.Boolean), + CONFIG_DISABLE_BACKGROUND_UPDATING("disableBackgroundUpdating", ConfigEntryType.Boolean), + CONFIG_OFFLINE("offline", ConfigEntryType.Boolean), + CONFIG_PRIVATE_ATTRIBUTES("privateAttributeNames", ConfigEntryType.UserAttributes, "privateAttributes"), + CONFIG_EVALUATION_REASONS("evaluationReasons", ConfigEntryType.Boolean), + CONFIG_WRAPPER_NAME("wrapperName", ConfigEntryType.String), + CONFIG_WRAPPER_VERSION("wrapperVersion", ConfigEntryType.String), + CONFIG_MAX_CACHED_USERS("maxCachedUsers", ConfigEntryType.Integer), + CONFIG_DIAGNOSTIC_OPT_OUT("diagnosticOptOut", ConfigEntryType.Boolean), + CONFIG_DIAGNOSTIC_RECORDING_INTERVAL("diagnosticRecordingIntervalMillis", ConfigEntryType.Integer), + CONFIG_SECONDARY_MOBILE_KEYS("secondaryMobileKeys", ConfigEntryType.Map); final String key; final ConfigEntryType type; private final Method setter; + ConfigMapping(String key, ConfigEntryType type) { + this(key, type, key); + } + ConfigMapping(String key, ConfigEntryType type, String setterName) { this.key = key; this.type = type; @@ -89,9 +90,7 @@ void loadFromMap(ReadableMap map, LDConfig.Builder builder) { if (map.hasKey(key) && map.getType(key).equals(type.getReadableType())) { try { setter.invoke(builder, type.getFromMap(map, key)); - } catch (IllegalAccessException e) { - Timber.w(e); - } catch (InvocationTargetException e) { + } catch (IllegalAccessException | InvocationTargetException e) { Timber.w(e); } } @@ -107,7 +106,7 @@ enum UserConfigMapping { USER_NAME("name", ConfigEntryType.String, "name", "privateName"), USER_SECONDARY("secondary", ConfigEntryType.String, "secondary", "privateSecondary"), USER_AVATAR("avatar", ConfigEntryType.String, "avatar", "privateAvatar"), - USER_COUNTRY("country", ConfigEntryType.Country, "country", "privateCountry"); + USER_COUNTRY("country", ConfigEntryType.String, "country", "privateCountry"); final String key; final ConfigEntryType type; @@ -129,20 +128,19 @@ void loadFromMap(ReadableMap map, LDUser.Builder builder, Set privateAtt } else { setter.invoke(builder, type.getFromMap(map, key)); } - } catch (IllegalAccessException e) { - Timber.w(e); - } catch (InvocationTargetException e) { + } catch (IllegalAccessException | InvocationTargetException e) { Timber.w(e); } } } } - private Map listeners = new HashMap<>(); - private Map connectionModeListeners = new HashMap<>(); - private Map allFlagsListeners = new HashMap<>(); + private final Map listeners = new HashMap<>(); + private final Map connectionModeListeners = new HashMap<>(); + private final Map allFlagsListeners = new HashMap<>(); - private static Gson gson = new Gson(); + private static final Gson gson = new Gson(); + private static boolean debugLoggingStarted = false; public LaunchdarklyReactNativeClientModule(ReactApplicationContext reactContext) { super(reactContext); @@ -156,7 +154,7 @@ public LaunchdarklyReactNativeClientModule(ReactApplicationContext reactContext) */ @SuppressWarnings("SameReturnValue") @Override - public String getName() { + public @NotNull String getName() { return "LaunchdarklyReactNativeClient"; } @@ -195,6 +193,14 @@ public void configureWithTimeout(ReadableMap config, ReadableMap user, Integer t } private void internalConfigure(ReadableMap config, ReadableMap user, final Integer timeout, final Promise promise) { + if (!debugLoggingStarted + && config.hasKey("debugMode") + && config.getType("debugMode").equals(ReadableType.Boolean) + && config.getBoolean("debugMode")) { + Timber.plant(new Timber.DebugTree()); + LaunchdarklyReactNativeClientModule.debugLoggingStarted = true; + } + try { LDClient.get(); promise.reject(ERROR_INIT, "Client was already initialized"); @@ -283,76 +289,12 @@ private LDUser.Builder userBuild(ReadableMap options) { } if (options.hasKey("custom") && options.getType("custom") == ReadableType.Map) { - ReadableMap custom = options.getMap("custom"); - ReadableMapKeySetIterator iterator = custom.keySetIterator(); - while (iterator.hasNextKey()) { - String customKey = iterator.nextKey(); - switch (custom.getType(customKey)) { - case Boolean: - if (privateAttrs.contains(customKey)) { - userBuilder.privateCustom(customKey, custom.getBoolean(customKey)); - } else { - userBuilder.custom(customKey, custom.getBoolean(customKey)); - } - break; - case Number: - if (privateAttrs.contains(customKey)) { - userBuilder.privateCustom(customKey, custom.getDouble(customKey)); - } else { - userBuilder.custom(customKey, custom.getDouble(customKey)); - } - break; - case String: - if (privateAttrs.contains(customKey)) { - userBuilder.privateCustom(customKey, custom.getString(customKey)); - } else { - userBuilder.custom(customKey, custom.getString(customKey)); - } - break; - case Array: - ReadableArray array = custom.getArray(customKey); - ArrayList strArray = null; - ArrayList numArray = null; - for (int i = 0; i < array.size(); i++) { - if (strArray != null) { - if (array.getType(i) == ReadableType.String) { - strArray.add(array.getString(i)); - } - } else if (numArray != null) { - if (array.getType(i) == ReadableType.Number) { - numArray.add(array.getDouble(i)); - } - } else if (array.getType(i) == ReadableType.String) { - strArray = new ArrayList<>(); - strArray.add(array.getString(i)); - } else if (array.getType(i) == ReadableType.Number) { - numArray = new ArrayList<>(); - numArray.add(array.getDouble(i)); - } - } - if (strArray != null) { - if (privateAttrs.contains(customKey)) { - userBuilder.privateCustomString(customKey, strArray); - } else { - userBuilder.customString(customKey, strArray); - } - } else if (numArray != null) { - if (privateAttrs.contains(customKey)) { - userBuilder.privateCustomNumber(customKey, numArray); - } else { - userBuilder.customNumber(customKey, numArray); - } - } else { - if (privateAttrs.contains(customKey)) { - userBuilder.privateCustomString(customKey, new ArrayList()); - } else { - userBuilder.customString(customKey, new ArrayList()); - } - } - break; - case Null: - case Map: - break; + LDValue custom = toLDValue(options.getMap("custom")); + for (String customKey : custom.keys()) { + if (privateAttrs.contains(customKey)) { + userBuilder.privateCustom(customKey, custom.get(customKey)); + } else { + userBuilder.custom(customKey, custom.get(customKey)); } } } @@ -418,32 +360,32 @@ public void stringVariationDefaultValue(String flagKey, String defaultValue, Str @ReactMethod public void jsonVariationNone(String flagKey, String environment, Promise promise) { - jsonVariationBase(flagKey, null, environment, promise); + jsonVariationBase(flagKey, LDValue.ofNull(), environment, promise); } @ReactMethod - public void jsonVariationNumber(String flagKey, Double defaultValue, String environment, Promise promise) { - jsonVariationBase(flagKey, new JsonPrimitive(defaultValue), environment, promise); + public void jsonVariationNumber(String flagKey, double defaultValue, String environment, Promise promise) { + jsonVariationBase(flagKey, LDValue.of(defaultValue), environment, promise); } @ReactMethod - public void jsonVariationBool(String flagKey, Boolean defaultValue, String environment, Promise promise) { - jsonVariationBase(flagKey, new JsonPrimitive(defaultValue), environment, promise); + public void jsonVariationBool(String flagKey, boolean defaultValue, String environment, Promise promise) { + jsonVariationBase(flagKey, LDValue.of(defaultValue), environment, promise); } @ReactMethod public void jsonVariationString(String flagKey, String defaultValue, String environment, Promise promise) { - jsonVariationBase(flagKey, new JsonPrimitive(defaultValue), environment, promise); + jsonVariationBase(flagKey, LDValue.of(defaultValue), environment, promise); } @ReactMethod public void jsonVariationArray(String flagKey, ReadableArray defaultValue, String environment, Promise promise) { - jsonVariationBase(flagKey, toJsonArray(defaultValue), environment, promise); + jsonVariationBase(flagKey, toLDValue(defaultValue), environment, promise); } @ReactMethod public void jsonVariationObject(String flagKey, ReadableMap defaultValue, String environment, Promise promise) { - jsonVariationBase(flagKey, toJsonObject(defaultValue), environment, promise); + jsonVariationBase(flagKey, toLDValue(defaultValue), environment, promise); } @ReactMethod @@ -458,11 +400,15 @@ public void boolVariationDetailDefaultValue(String flagKey, Boolean defaultValue detailResult = LDClient.getForMobileKey(environment).boolVariationDetail(flagKey, defaultValue); } catch (Exception e) { Timber.w(e); - detailResult = new EvaluationDetail(EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION), null, defaultValue); + detailResult = EvaluationDetail.fromValue(defaultValue, EvaluationDetail.NO_VARIATION, EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION)); } - JsonObject jsonObject = gson.toJsonTree(detailResult).getAsJsonObject(); - WritableMap detailMap = fromJsonObject(jsonObject); - promise.resolve(detailMap); + WritableMap result = detailToMap(detailResult); + if (detailResult.getValue() == null) { + result.putNull("value"); + } else { + result.putBoolean("value", detailResult.getValue()); + } + promise.resolve(result); } @ReactMethod @@ -477,11 +423,15 @@ public void intVariationDetailDefaultValue(String flagKey, Integer defaultValue, detailResult = LDClient.getForMobileKey(environment).intVariationDetail(flagKey, defaultValue); } catch (Exception e) { Timber.w(e); - detailResult = new EvaluationDetail(EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION), null, defaultValue); + detailResult = EvaluationDetail.fromValue(defaultValue, EvaluationDetail.NO_VARIATION, EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION)); + } + WritableMap result = detailToMap(detailResult); + if (detailResult.getValue() == null) { + result.putNull("value"); + } else { + result.putInt("value", detailResult.getValue()); } - JsonObject jsonObject = gson.toJsonTree(detailResult).getAsJsonObject(); - WritableMap detailMap = fromJsonObject(jsonObject); - promise.resolve(detailMap); + promise.resolve(result); } @ReactMethod @@ -497,11 +447,15 @@ public void floatVariationDetailDefaultValue(String flagKey, Float defaultValue, detailResult = LDClient.getForMobileKey(environment).doubleVariationDetail(flagKey, doubleValue); } catch (Exception e) { Timber.w(e); - detailResult = new EvaluationDetail(EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION), null, doubleValue); + detailResult = EvaluationDetail.fromValue(doubleValue, EvaluationDetail.NO_VARIATION, EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION)); } - JsonObject jsonObject = gson.toJsonTree(detailResult).getAsJsonObject(); - WritableMap detailMap = fromJsonObject(jsonObject); - promise.resolve(detailMap); + WritableMap result = detailToMap(detailResult); + if (detailResult.getValue() == null) { + result.putNull("value"); + } else { + result.putDouble("value", detailResult.getValue()); + } + promise.resolve(result); } @ReactMethod @@ -516,89 +470,112 @@ public void stringVariationDetailDefaultValue(String flagKey, String defaultValu detailResult = LDClient.getForMobileKey(environment).stringVariationDetail(flagKey, defaultValue); } catch (Exception e) { Timber.w(e); - detailResult = new EvaluationDetail(EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION), null, defaultValue); + detailResult = EvaluationDetail.fromValue(defaultValue, EvaluationDetail.NO_VARIATION, EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION)); + } + WritableMap result = detailToMap(detailResult); + if (detailResult.getValue() == null) { + result.putNull("value"); + } else { + result.putString("value", detailResult.getValue()); } - JsonObject jsonObject = gson.toJsonTree(detailResult).getAsJsonObject(); - WritableMap detailMap = fromJsonObject(jsonObject); - promise.resolve(detailMap); + promise.resolve(result); } @ReactMethod public void jsonVariationDetailNone(String flagKey, String environment, Promise promise) { - jsonVariationDetailBase(flagKey, null, environment, promise); + jsonVariationDetailBase(flagKey, LDValue.ofNull(), environment, promise); } @ReactMethod - public void jsonVariationDetailNumber(String flagKey, Double defaultValue, String environment, Promise promise) { - jsonVariationDetailBase(flagKey, new JsonPrimitive(defaultValue), environment, promise); + public void jsonVariationDetailNumber(String flagKey, double defaultValue, String environment, Promise promise) { + jsonVariationDetailBase(flagKey, LDValue.of(defaultValue), environment, promise); } @ReactMethod - public void jsonVariationDetailBool(String flagKey, Boolean defaultValue, String environment, Promise promise) { - jsonVariationDetailBase(flagKey, new JsonPrimitive(defaultValue), environment, promise); + public void jsonVariationDetailBool(String flagKey, boolean defaultValue, String environment, Promise promise) { + jsonVariationDetailBase(flagKey, LDValue.of(defaultValue), environment, promise); } @ReactMethod public void jsonVariationDetailString(String flagKey, String defaultValue, String environment, Promise promise) { - jsonVariationDetailBase(flagKey, new JsonPrimitive(defaultValue), environment, promise); + jsonVariationDetailBase(flagKey, LDValue.of(defaultValue), environment, promise); } @ReactMethod public void jsonVariationDetailArray(String flagKey, ReadableArray defaultValue, String environment, Promise promise) { - jsonVariationDetailBase(flagKey, toJsonArray(defaultValue), environment, promise); + jsonVariationDetailBase(flagKey, toLDValue(defaultValue), environment, promise); } @ReactMethod public void jsonVariationDetailObject(String flagKey, ReadableMap defaultValue, String environment, Promise promise) { - jsonVariationDetailBase(flagKey, toJsonObject(defaultValue), environment, promise); + jsonVariationDetailBase(flagKey, toLDValue(defaultValue), environment, promise); } - private void jsonVariationBase(String flagKey, JsonElement defaultValue, String environment, Promise promise) { - JsonElement jsonElement; - try { - jsonElement = LDClient.getForMobileKey(environment).jsonVariation(flagKey, defaultValue); - resolveJsonElement(promise, jsonElement); + private void jsonVariationBase(String flagKey, LDValue defaultValue, String environment, Promise promise) { + try { + LDValue value = LDClient.getForMobileKey(environment).jsonValueVariation(flagKey, defaultValue); + resolveValue(promise, value); } catch (Exception e) { - resolveJsonElement(promise, defaultValue); + resolveValue(promise, defaultValue); } } - private void jsonVariationDetailBase(String flagKey, JsonElement defaultValue, String environment, Promise promise) { - EvaluationDetail jsonElementDetail; + private void jsonVariationDetailBase(String flagKey, LDValue defaultValue, String environment, Promise promise) { + EvaluationDetail detailResult; try { - jsonElementDetail = LDClient.getForMobileKey(environment).jsonVariationDetail(flagKey, defaultValue); + detailResult = LDClient.getForMobileKey(environment).jsonValueVariationDetail(flagKey, defaultValue); } catch (Exception e) { Timber.w(e); - jsonElementDetail = new EvaluationDetail(EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION), null, defaultValue); + detailResult = EvaluationDetail.fromValue(defaultValue, EvaluationDetail.NO_VARIATION, EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION)); } - resolveJsonElementDetail(promise, jsonElementDetail); + resolveEvaluationDetailValue(promise, detailResult); } - private void resolveJsonElement(Promise promise, JsonElement jsonElement) { - if (jsonElement == null || jsonElement.isJsonNull()) { - promise.resolve(null); - } else if (jsonElement.isJsonArray()) { - promise.resolve(fromJsonArray(jsonElement.getAsJsonArray())); - } else if (jsonElement.isJsonObject()) { - promise.resolve(fromJsonObject(jsonElement.getAsJsonObject())); - } else { - JsonPrimitive prim = jsonElement.getAsJsonPrimitive(); - if (prim.isBoolean()) { - promise.resolve(prim.getAsBoolean()); - } else if (prim.isString()) { - promise.resolve(prim.getAsString()); - } else { - promise.resolve(prim.getAsNumber().doubleValue()); - } + private void resolveValue(Promise promise, LDValue value) { + switch (value.getType()) { + case NULL: promise.resolve(null); break; + case BOOLEAN: promise.resolve(value.booleanValue()); break; + case NUMBER: promise.resolve(value.doubleValue()); break; + case STRING: promise.resolve(value.stringValue()); break; + case ARRAY: promise.resolve(ldValueToArray(value)); break; + case OBJECT: promise.resolve(ldValueToMap(value)); break; } } - private void resolveJsonElementDetail(Promise promise, EvaluationDetail jsonElementDetail) { - JsonObject jsonObject = new JsonObject(); - jsonObject.add("value", jsonElementDetail.getValue()); - jsonObject.addProperty("variationIndex", jsonElementDetail.getVariationIndex()); - jsonObject.add("reason", gson.toJsonTree(jsonElementDetail.getReason())); - resolveJsonElement(promise, jsonObject); + private void resolveEvaluationDetailValue(Promise promise, EvaluationDetail detailValue) { + WritableMap result = detailToMap(detailValue); + switch (detailValue.getValue().getType()) { + case NULL: result.putNull("value"); break; + case BOOLEAN: result.putBoolean("value", detailValue.getValue().booleanValue()); break; + case NUMBER: result.putDouble("value", detailValue.getValue().doubleValue()); break; + case STRING: result.putString("value", detailValue.getValue().stringValue()); break; + case ARRAY: result.putArray("value", ldValueToArray(detailValue.getValue())); break; + case OBJECT: result.putMap("value", ldValueToMap(detailValue.getValue())); break; + } + promise.resolve(result); + } + + private WritableMap detailToMap(EvaluationDetail detail) { + EvaluationReason reason = detail.getReason(); + WritableMap result = new WritableNativeMap(); + if (!detail.isDefaultValue()) { + result.putInt("variationIndex", detail.getVariationIndex()); + } + WritableMap reasonMap = new WritableNativeMap(); + reasonMap.putString("kind", detail.getReason().getKind().name()); + switch (reason.getKind()) { + case RULE_MATCH: + reasonMap.putInt("ruleIndex", reason.getRuleIndex()); + if (reason.getRuleId() != null) { + reasonMap.putString("ruleId", reason.getRuleId()); + } + break; + case PREREQUISITE_FAILED: reasonMap.putString("prerequisiteKey", reason.getPrerequisiteKey()); break; + case ERROR: reasonMap.putString("errorKind", reason.getErrorKind().name()); break; + default: break; + } + result.putMap("reason", reasonMap); + return result; } @ReactMethod @@ -611,48 +588,17 @@ public void allFlags(String environment, Promise promise) { } try { - Map flags = LDClient.getForMobileKey(environment).allFlags(); + Map flags = LDClient.getForMobileKey(environment).allFlags(); WritableMap response = new WritableNativeMap(); - for (Map.Entry entry : flags.entrySet()) { - if (entry.getValue() == null) { - response.putNull(entry.getKey()); - } else if (entry.getValue() instanceof String) { - try { - JsonElement parsedJson = new JsonParser().parse((String) entry.getValue()); - if (parsedJson.isJsonObject()) { - response.putMap(entry.getKey(), fromJsonObject((JsonObject) parsedJson.getAsJsonObject())); - } else if (parsedJson.isJsonArray()) { - response.putArray(entry.getKey(), fromJsonArray((JsonArray) parsedJson.getAsJsonArray())); - } else { - response.putString(entry.getKey(),(String) entry.getValue()); - } - } catch (JsonParseException e) { - response.putString(entry.getKey(),(String) entry.getValue()); - } - } else if (entry.getValue() instanceof Boolean) { - response.putBoolean(entry.getKey(), (Boolean) entry.getValue()); - } else if (entry.getValue() instanceof Double) { - response.putDouble(entry.getKey(), (Double) entry.getValue()); - } else if (entry.getValue() instanceof Float) { - response.putDouble(entry.getKey(), (Float) entry.getValue()); - } else if (entry.getValue() instanceof Integer) { - response.putInt(entry.getKey(), (Integer) entry.getValue()); - } else if (entry.getValue() instanceof JsonNull) { - response.putNull(entry.getKey()); - } else if (entry.getValue() instanceof JsonArray) { - response.putArray(entry.getKey(), fromJsonArray((JsonArray) entry.getValue())); - } else if (entry.getValue() instanceof JsonObject) { - response.putMap(entry.getKey(), fromJsonObject((JsonObject) entry.getValue())); - } else if (entry.getValue() instanceof JsonPrimitive) { - JsonPrimitive primitive = (JsonPrimitive) entry.getValue(); - if (primitive.isString()) { - response.putString(entry.getKey(), primitive.getAsString()); - } else if (primitive.isBoolean()) { - response.putBoolean(entry.getKey(), primitive.getAsBoolean()); - } else if (primitive.isNumber()) { - response.putDouble(entry.getKey(), primitive.getAsDouble()); - } + for (Map.Entry entry : flags.entrySet()) { + switch (entry.getValue().getType()) { + case NULL: response.putNull(entry.getKey()); break; + case BOOLEAN: response.putBoolean(entry.getKey(), entry.getValue().booleanValue()); break; + case NUMBER: response.putDouble(entry.getKey(), entry.getValue().doubleValue()); break; + case STRING: response.putString(entry.getKey(), entry.getValue().stringValue()); break; + case ARRAY: response.putArray(entry.getKey(), ldValueToArray(entry.getValue())); break; + case OBJECT: response.putMap(entry.getKey(), ldValueToMap(entry.getValue())); break; } } promise.resolve(response); @@ -666,108 +612,73 @@ public void allFlags(String environment, Promise promise) { } @ReactMethod - public void trackNumber(String eventName, Double data, String environment) { - try { - LDClient.getForMobileKey(environment).track(eventName, new JsonPrimitive(data)); - } catch (Exception e) { - Timber.w(e); - } + public void trackNumber(String eventName, double data, String environment) { + trackSafe(environment, eventName, LDValue.of(data), null); } @ReactMethod - public void trackBool(String eventName, Boolean data, String environment) { - try { - LDClient.getForMobileKey(environment).track(eventName, new JsonPrimitive(data)); - } catch (Exception e) { - Timber.w(e); - } + public void trackBool(String eventName, boolean data, String environment) { + trackSafe(environment, eventName, LDValue.of(data), null); } @ReactMethod public void trackString(String eventName, String data, String environment) { - try { - LDClient.getForMobileKey(environment).track(eventName, new JsonPrimitive(data)); - } catch (Exception e) { - Timber.w(e); - } + trackSafe(environment, eventName, LDValue.of(data), null); } @ReactMethod public void trackArray(String eventName, ReadableArray data, String environment) { - try { - LDClient.getForMobileKey(environment).track(eventName, toJsonArray(data)); - } catch (Exception e) { - Timber.w(e); - } + trackSafe(environment, eventName, toLDValue(data), null); } @ReactMethod public void trackObject(String eventName, ReadableMap data, String environment) { - try { - LDClient.getForMobileKey(environment).track(eventName, toJsonObject(data)); - } catch (Exception e) { - Timber.w(e); - } + trackSafe(environment, eventName, toLDValue(data), null); } @ReactMethod public void track(String eventName, String environment) { - try { - LDClient.getForMobileKey(environment).track(eventName); - } catch (Exception e) { - Timber.w(e); - } + trackSafe(environment, eventName, LDValue.ofNull(), null); } @ReactMethod - public void trackNumberMetricValue(String eventName, Double data, Double metricValue, String environment) { - try { - LDClient.getForMobileKey(environment).track(eventName, new JsonPrimitive(data), metricValue); - } catch (Exception e) { - Timber.w(e); - } + public void trackNumberMetricValue(String eventName, double data, double metricValue, String environment) { + trackSafe(environment, eventName, LDValue.of(data), metricValue); } @ReactMethod - public void trackBoolMetricValue(String eventName, Boolean data, Double metricValue, String environment) { - try { - LDClient.getForMobileKey(environment).track(eventName, new JsonPrimitive(data), metricValue); - } catch (Exception e) { - Timber.w(e); - } + public void trackBoolMetricValue(String eventName, boolean data, double metricValue, String environment) { + trackSafe(environment, eventName, LDValue.of(data), metricValue); } @ReactMethod - public void trackStringMetricValue(String eventName, String data, Double metricValue, String environment) { - try { - LDClient.getForMobileKey(environment).track(eventName, new JsonPrimitive(data), metricValue); - } catch (Exception e) { - Timber.w(e); - } + public void trackStringMetricValue(String eventName, String data, double metricValue, String environment) { + trackSafe(environment, eventName, LDValue.of(data), metricValue); } @ReactMethod - public void trackArrayMetricValue(String eventName, ReadableArray data, Double metricValue, String environment) { - try { - LDClient.getForMobileKey(environment).track(eventName, toJsonArray(data), metricValue); - } catch (Exception e) { - Timber.w(e); - } + public void trackArrayMetricValue(String eventName, ReadableArray data, double metricValue, String environment) { + trackSafe(environment, eventName, toLDValue(data), metricValue); } @ReactMethod - public void trackObjectMetricValue(String eventName, ReadableMap data, Double metricValue, String environment) { - try { - LDClient.getForMobileKey(environment).track(eventName, toJsonObject(data), metricValue); - } catch (Exception e) { - Timber.w(e); - } + public void trackObjectMetricValue(String eventName, ReadableMap data, double metricValue, String environment) { + trackSafe(environment, eventName, toLDValue(data), metricValue); } @ReactMethod - public void trackMetricValue(String eventName, Double metricValue, String environment) { + public void trackMetricValue(String eventName, double metricValue, String environment) { + trackSafe(environment, eventName, LDValue.ofNull(), metricValue); + } + + private void trackSafe(String environment, String eventName, LDValue value, Double metricValue) { try { - LDClient.getForMobileKey(environment).track(eventName, new JsonPrimitive(""), metricValue); + LDClient instance = LDClient.getForMobileKey(environment); + if (metricValue != null) { + instance.trackMetric(eventName, value, metricValue); + } else { + instance.trackData(eventName, value); + } } catch (Exception e) { Timber.w(e); } @@ -1014,124 +925,70 @@ public void unregisterAllFlagsListener(String listenerId, String environment) { } } - private static JsonObject toJsonObject(ReadableMap readableMap) { - if (readableMap == null) - return null; - - JsonObject jsonObject = new JsonObject(); - - ReadableMapKeySetIterator keySet = readableMap.keySetIterator(); - - while (keySet.hasNextKey()) { - String key = keySet.nextKey(); - ReadableType type = readableMap.getType(key); - - switch (type) { - case Null: - jsonObject.add(key, null); - break; - case Boolean: - jsonObject.addProperty(key, readableMap.getBoolean(key)); - break; - case Number: - jsonObject.addProperty(key, readableMap.getDouble(key)); - break; - case String: - jsonObject.addProperty(key, readableMap.getString(key)); - break; - case Map: - jsonObject.add(key, toJsonObject(readableMap.getMap(key))); - break; - case Array: - jsonObject.add(key, toJsonArray(readableMap.getArray(key))); - break; + private static LDValue toLDValue(ReadableArray readableArray) { + if (readableArray == null) { + return LDValue.ofNull(); + } + ArrayBuilder array = LDValue.buildArray(); + for (int i = 0; i < readableArray.size(); i++) { + switch (readableArray.getType(i)) { + case Null: array.add(LDValue.ofNull()); break; + case Boolean: array.add(readableArray.getBoolean(i)); break; + case Number: array.add(readableArray.getDouble(i)); break; + case String: array.add(readableArray.getString(i)); break; + case Array: array.add(toLDValue(readableArray.getArray(i))); break; + case Map: array.add(toLDValue(readableArray.getMap(i))); break; } } - - return jsonObject; + return array.build(); } - private static JsonArray toJsonArray(ReadableArray readableArray) { - if (readableArray == null) - return null; - - JsonArray jsonArray = new JsonArray(); - - for (int i = 0; i < readableArray.size(); i++) { - ReadableType type = readableArray.getType(i); - - switch (type) { - case Null: - jsonArray.add((Boolean) null); - break; - case Boolean: - jsonArray.add(readableArray.getBoolean(i)); - break; - case Number: - jsonArray.add(readableArray.getDouble(i)); - break; - case String: - jsonArray.add(readableArray.getString(i)); - break; - case Map: - jsonArray.add(toJsonObject(readableArray.getMap(i))); - break; - case Array: - jsonArray.add(toJsonArray(readableArray.getArray(i))); - break; + private static LDValue toLDValue(ReadableMap readableMap) { + if (readableMap == null) { + return LDValue.ofNull(); + } + ObjectBuilder object = LDValue.buildObject(); + ReadableMapKeySetIterator iter = readableMap.keySetIterator(); + while (iter.hasNextKey()) { + String key = iter.nextKey(); + switch (readableMap.getType(key)) { + case Null: object.put(key, LDValue.ofNull()); break; + case Boolean: object.put(key, readableMap.getBoolean(key)); break; + case Number: object.put(key, readableMap.getDouble(key)); break; + case String: object.put(key, readableMap.getString(key)); break; + case Array: object.put(key, toLDValue(readableMap.getArray(key))); break; + case Map: object.put(key, toLDValue(readableMap.getMap(key))); break; } } - - return jsonArray; + return object.build(); } - private static WritableArray fromJsonArray(JsonArray jsonArray) { - if (jsonArray == null) - return null; - + private static WritableArray ldValueToArray(LDValue value) { WritableArray result = new WritableNativeArray(); - for (JsonElement element : jsonArray) { - if (element == null || element.isJsonNull()) { - result.pushNull(); - } else if (element.isJsonObject()) { - result.pushMap(fromJsonObject(element.getAsJsonObject())); - } else if (element.isJsonArray()) { - result.pushArray(fromJsonArray(element.getAsJsonArray())); - } else if (element.isJsonPrimitive()) { - JsonPrimitive primitive = element.getAsJsonPrimitive(); - if (primitive.isBoolean()) { - result.pushBoolean(primitive.getAsBoolean()); - } else if (primitive.isString()) { - result.pushString(primitive.getAsString()); - } else if (primitive.isNumber()) { - result.pushDouble(primitive.getAsDouble()); - } + for (LDValue val : value.values()) { + switch (val.getType()) { + case NULL: result.pushNull(); break; + case BOOLEAN: result.pushBoolean(val.booleanValue()); break; + case NUMBER: result.pushDouble(val.doubleValue()); break; + case STRING: result.pushString(val.stringValue()); break; + case ARRAY: result.pushArray(ldValueToArray(val)); break; + case OBJECT: result.pushMap(ldValueToMap(val)); break; } } return result; } - private static WritableMap fromJsonObject(JsonObject jsonObject) { - if (jsonObject == null) - return null; - + private static WritableMap ldValueToMap(LDValue value) { WritableMap result = new WritableNativeMap(); - for (Map.Entry entry : jsonObject.entrySet()) { - if (entry.getValue() == null || entry.getValue().isJsonNull()) { - result.putNull(entry.getKey()); - } else if (entry.getValue().isJsonObject()) { - result.putMap(entry.getKey(), fromJsonObject(entry.getValue().getAsJsonObject())); - } else if (entry.getValue().isJsonArray()) { - result.putArray(entry.getKey(), fromJsonArray(entry.getValue().getAsJsonArray())); - } else if (entry.getValue().isJsonPrimitive()) { - JsonPrimitive primitive = entry.getValue().getAsJsonPrimitive(); - if (primitive.isBoolean()) { - result.putBoolean(entry.getKey(), primitive.getAsBoolean()); - } else if (primitive.isString()) { - result.putString(entry.getKey(), primitive.getAsString()); - } else if (primitive.isNumber()) { - result.putDouble(entry.getKey(), primitive.getAsDouble()); - } + for (String key : value.keys()) { + LDValue val = value.get(key); + switch (val.getType()) { + case NULL: result.putNull(key); break; + case BOOLEAN: result.putBoolean(key, val.booleanValue()); break; + case NUMBER: result.putDouble(key, val.doubleValue()); break; + case STRING: result.putString(key, val.stringValue()); break; + case ARRAY: result.putArray(key, ldValueToArray(val)); break; + case OBJECT: result.putMap(key, ldValueToMap(val)); break; } } return result; @@ -1152,16 +1009,6 @@ public Uri getFromMap(ReadableMap map, String key) { return android.net.Uri.parse(map.getString(key)); } }, - UriMobile(ReadableType.String) { - public Uri getFromMap(ReadableMap map, String key) { - return android.net.Uri.parse(map.getString(key) + "/mobile"); - } - }, - Country(ReadableType.String) { - public LDCountryCode getFromMap(ReadableMap map, String key) { - return LDCountryCode.valueOf(map.getString(key)); - } - }, Integer(ReadableType.Number) { public Integer getFromMap(ReadableMap map, String key) { return map.getInt(key); @@ -1177,16 +1024,16 @@ public Map getFromMap(ReadableMap map, String key) { return map.getMap(key).toHashMap(); } }, - StringSet(ReadableType.Array) { - public Set getFromMap(ReadableMap map, String key) { + UserAttributes(ReadableType.Array) { + public UserAttribute[] getFromMap(ReadableMap map, String key) { ReadableArray array = map.getArray(key); - Set returnSet = new HashSet<>(); + Set userAttributes = new HashSet<>(); for (int i = 0; i < array.size(); i++) { if (array.getType(i).equals(ReadableType.String)) { - returnSet.add(array.getString(i)); + userAttributes.add(UserAttribute.forName(array.getString(i))); } } - return returnSet; + return userAttributes.toArray(new UserAttribute[0]); } }; From 894de0f9e08f719ef152d0b67a28bee85e054bbf Mon Sep 17 00:00:00 2001 From: Gavin Whelan Date: Wed, 16 Jun 2021 10:40:47 -0700 Subject: [PATCH 17/32] Add unit tests for JS native bridge wrapper. (#83) --- .babelrc | 3 - .circleci/config.yml | 19 +- __mocks__/native.js | 87 +++++++++ index.js | 2 +- index.test.js | 423 +++++++++++++++++++++++++++++++++++++++++++ package.json | 21 ++- 6 files changed, 546 insertions(+), 9 deletions(-) delete mode 100644 .babelrc create mode 100644 __mocks__/native.js create mode 100644 index.test.js diff --git a/.babelrc b/.babelrc deleted file mode 100644 index d4b74b5..0000000 --- a/.babelrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "presets": ["module:metro-react-native-babel-preset"] -} diff --git a/.circleci/config.yml b/.circleci/config.yml index 53fff3e..f401b45 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -50,17 +50,30 @@ jobs: common: docker: - - image: circleci/node:11.10.1 + - image: cimg/node:current steps: - checkout - run: npm install + - run: mkdir -p reports/jest + - run: + command: npm run test:junit + environment: + JEST_JUNIT_OUTPUT_DIR: "./reports/jest" + - run: npm run check-typescript + - store_test_results: + path: reports + workflows: version: 2 android-ios: jobs: - - android - - ios - common + - android: + requires: + - common + - ios: + requires: + - common diff --git a/__mocks__/native.js b/__mocks__/native.js new file mode 100644 index 0000000..4be8f95 --- /dev/null +++ b/__mocks__/native.js @@ -0,0 +1,87 @@ +const mockNativeModule = { + FLAG_PREFIX: "test-flag-prefix", + ALL_FLAGS_PREFIX: "test-all-flags-prefix", + CONNECTION_MODE_PREFIX: "test-connection-mode-prefix", + + configure: jest.fn(), + configureWithTimeout: jest.fn(), + + boolVariation: jest.fn(), + boolVariationDefaultValue: jest.fn(), + intVariation: jest.fn(), + intVariationDefaultValue: jest.fn(), + floatVariation: jest.fn(), + floatVariationDefaultValue: jest.fn(), + stringVariation: jest.fn(), + stringVariationDefaultValue: jest.fn(), + jsonVariationNone: jest.fn(), + jsonVariationNumber: jest.fn(), + jsonVariationBool: jest.fn(), + jsonVariationString: jest.fn(), + jsonVariationArray: jest.fn(), + jsonVariationObject: jest.fn(), + + boolVariationDetail: jest.fn(), + boolVariationDetailDefaultValue: jest.fn(), + intVariationDetail: jest.fn(), + intVariationDetailDefaultValue: jest.fn(), + floatVariationDetail: jest.fn(), + floatVariationDetailDefaultValue: jest.fn(), + stringVariationDetail: jest.fn(), + stringVariationDetailDefaultValue: jest.fn(), + jsonVariationDetailNone: jest.fn(), + jsonVariationDetailNumber: jest.fn(), + jsonVariationDetailBool: jest.fn(), + jsonVariationDetailString: jest.fn(), + jsonVariationDetailArray: jest.fn(), + jsonVariationDetailObject: jest.fn(), + + allFlags: jest.fn(), + + trackNumber: jest.fn(), + trackBool: jest.fn(), + trackString: jest.fn(), + trackArray: jest.fn(), + trackObject: jest.fn(), + track: jest.fn(), + + trackNumberMetricValue: jest.fn(), + trackBoolMetricValue: jest.fn(), + trackStringMetricValue: jest.fn(), + trackArrayMetricValue: jest.fn(), + trackObjectMetricValue: jest.fn(), + trackMetricValue: jest.fn(), + + setOffline: jest.fn(), + isOffline: jest.fn(), + setOnline: jest.fn(), + isInitialized: jest.fn(), + flush: jest.fn(), + close: jest.fn(), + identify: jest.fn(), + getConnectionMode: jest.fn(), + getLastSuccessfulConnection: jest.fn(), + getLastFailedConnection: jest.fn(), + getLastFailure: jest.fn(), + + registerFeatureFlagListener: jest.fn(), + unregisterFeatureFlagListener: jest.fn(), + registerCurrentConnectionModeListener: jest.fn(), + unregisterCurrentConnectionModeListener: jest.fn(), + registerAllFlagsListener: jest.fn(), + unregisterAllFlagsListener: jest.fn() +}; + +jest.mock('react-native', + () => { + return { + NativeModules: { + LaunchdarklyReactNativeClient: mockNativeModule + }, + NativeEventEmitter: jest.fn().mockImplementation(() => { + return { addListener: jest.fn() }; + }) + } + }, + { virtual: true } +); diff --git a/index.js b/index.js index 54d7d6f..3198084 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ -import { NativeModules, NativeEventEmitter, Platform } from 'react-native'; +import { NativeModules, NativeEventEmitter } from 'react-native'; import { version } from './package.json'; let LaunchdarklyReactNativeClient = NativeModules.LaunchdarklyReactNativeClient; diff --git a/index.test.js b/index.test.js new file mode 100644 index 0000000..c5c50a5 --- /dev/null +++ b/index.test.js @@ -0,0 +1,423 @@ +import { NativeModules, NativeEventEmitter } from 'react-native'; +import LDClient from './index.js'; + +var client; +var addListenerMock; +let nativeMock = NativeModules.LaunchdarklyReactNativeClient; + +function getClientFlagListener() { + return addListenerMock.calls[0][1]; +} + +function getClientFlagsListener() { + return addListenerMock.calls[1][1]; +} + +function getClientConnectionListener() { + return addListenerMock.calls[2][1]; +} + +beforeEach(() => { + Object.values(nativeMock).forEach(v => { + if (typeof v === 'function') { + v.mockClear(); + } + }); + NativeEventEmitter.mockClear(); + + client = new LDClient(); + expect(NativeEventEmitter).toHaveBeenCalledTimes(1); + expect(NativeEventEmitter.mock.calls[0].length).toBe(1); + expect(Object.is(NativeEventEmitter.mock.calls[0][0], NativeModules.LaunchdarklyReactNativeClient)).toBe(true); + + addListenerMock = NativeEventEmitter.mock.results[0].value.addListener.mock; +}); + +test('constructor', () => { + expect(addListenerMock.calls.length).toBe(3); + expect(addListenerMock.calls[0].length).toBe(2); + expect(addListenerMock.calls[1].length).toBe(2); + expect(addListenerMock.calls[2].length).toBe(2); + expect(addListenerMock.calls[0][0]).toBe(nativeMock.FLAG_PREFIX); + expect(addListenerMock.calls[1][0]).toBe(nativeMock.ALL_FLAGS_PREFIX); + expect(addListenerMock.calls[2][0]).toBe(nativeMock.CONNECTION_MODE_PREFIX); +}); + +test('boolVariation', () => { + client.boolVariation('key1'); + client.boolVariation('key2', undefined, 'env1'); + client.boolVariation('key3', true); + client.boolVariation('key4', false, 'env2'); + + expect(nativeMock.boolVariation).toHaveBeenCalledTimes(2); + expect(nativeMock.boolVariation).toHaveBeenNthCalledWith(1, 'key1', 'default'); + expect(nativeMock.boolVariation).toHaveBeenNthCalledWith(2, 'key2', 'env1'); + + expect(nativeMock.boolVariationDefaultValue).toHaveBeenCalledTimes(2); + expect(nativeMock.boolVariationDefaultValue).toHaveBeenNthCalledWith(1, 'key3', true, 'default'); + expect(nativeMock.boolVariationDefaultValue).toHaveBeenNthCalledWith(2, 'key4', false, 'env2'); +}); + +test('intVariation', () => { + client.intVariation('key1'); + client.intVariation('key2', undefined, 'env1'); + client.intVariation('key3', 0); + client.intVariation('key4', 5, 'env2'); + + expect(nativeMock.intVariation).toHaveBeenCalledTimes(2); + expect(nativeMock.intVariation).toHaveBeenNthCalledWith(1, 'key1', 'default'); + expect(nativeMock.intVariation).toHaveBeenNthCalledWith(2, 'key2', 'env1'); + + expect(nativeMock.intVariationDefaultValue).toHaveBeenCalledTimes(2); + expect(nativeMock.intVariationDefaultValue).toHaveBeenNthCalledWith(1, 'key3', 0, 'default'); + expect(nativeMock.intVariationDefaultValue).toHaveBeenNthCalledWith(2, 'key4', 5, 'env2'); +}); + +test('floatVariation', () => { + client.floatVariation('key1'); + client.floatVariation('key2', undefined, 'env1'); + client.floatVariation('key3', 1.5); + client.floatVariation('key4', 5.5, 'env2'); + + expect(nativeMock.floatVariation).toHaveBeenCalledTimes(2); + expect(nativeMock.floatVariation).toHaveBeenNthCalledWith(1, 'key1', 'default'); + expect(nativeMock.floatVariation).toHaveBeenNthCalledWith(2, 'key2', 'env1'); + + expect(nativeMock.floatVariationDefaultValue).toHaveBeenCalledTimes(2); + expect(nativeMock.floatVariationDefaultValue).toHaveBeenNthCalledWith(1, 'key3', 1.5, 'default'); + expect(nativeMock.floatVariationDefaultValue).toHaveBeenNthCalledWith(2, 'key4', 5.5, 'env2'); +}); + +test('stringVariation', () => { + client.stringVariation('key1'); + client.stringVariation('key2', undefined, 'env1'); + client.stringVariation('key3', ''); + client.stringVariation('key4', 'abc', 'env2'); + + expect(nativeMock.stringVariation).toHaveBeenCalledTimes(2); + expect(nativeMock.stringVariation).toHaveBeenNthCalledWith(1, 'key1', 'default'); + expect(nativeMock.stringVariation).toHaveBeenNthCalledWith(2, 'key2', 'env1'); + + expect(nativeMock.stringVariationDefaultValue).toHaveBeenCalledTimes(2); + expect(nativeMock.stringVariationDefaultValue).toHaveBeenNthCalledWith(1, 'key3', '', 'default'); + expect(nativeMock.stringVariationDefaultValue).toHaveBeenNthCalledWith(2, 'key4', 'abc', 'env2'); +}); + +test('boolVariationDetail', () => { + client.boolVariationDetail('key1'); + client.boolVariationDetail('key2', undefined, 'env1'); + client.boolVariationDetail('key3', true); + client.boolVariationDetail('key4', false, 'env2'); + + expect(nativeMock.boolVariationDetail).toHaveBeenCalledTimes(2); + expect(nativeMock.boolVariationDetail).toHaveBeenNthCalledWith(1, 'key1', 'default'); + expect(nativeMock.boolVariationDetail).toHaveBeenNthCalledWith(2, 'key2', 'env1'); + + expect(nativeMock.boolVariationDetailDefaultValue).toHaveBeenCalledTimes(2); + expect(nativeMock.boolVariationDetailDefaultValue).toHaveBeenNthCalledWith(1, 'key3', true, 'default'); + expect(nativeMock.boolVariationDetailDefaultValue).toHaveBeenNthCalledWith(2, 'key4', false, 'env2'); +}); + +test('intVariationDetail', () => { + client.intVariationDetail('key1'); + client.intVariationDetail('key2', undefined, 'env1'); + client.intVariationDetail('key3', 0); + client.intVariationDetail('key4', 5, 'env2'); + + expect(nativeMock.intVariationDetail).toHaveBeenCalledTimes(2); + expect(nativeMock.intVariationDetail).toHaveBeenNthCalledWith(1, 'key1', 'default'); + expect(nativeMock.intVariationDetail).toHaveBeenNthCalledWith(2, 'key2', 'env1'); + + expect(nativeMock.intVariationDetailDefaultValue).toHaveBeenCalledTimes(2); + expect(nativeMock.intVariationDetailDefaultValue).toHaveBeenNthCalledWith(1, 'key3', 0, 'default'); + expect(nativeMock.intVariationDetailDefaultValue).toHaveBeenNthCalledWith(2, 'key4', 5, 'env2'); +}); + +test('floatVariationDetail', () => { + client.floatVariationDetail('key1'); + client.floatVariationDetail('key2', undefined, 'env1'); + client.floatVariationDetail('key3', 1.5); + client.floatVariationDetail('key4', 5.5, 'env2'); + + expect(nativeMock.floatVariationDetail).toHaveBeenCalledTimes(2); + expect(nativeMock.floatVariationDetail).toHaveBeenNthCalledWith(1, 'key1', 'default'); + expect(nativeMock.floatVariationDetail).toHaveBeenNthCalledWith(2, 'key2', 'env1'); + + expect(nativeMock.floatVariationDetailDefaultValue).toHaveBeenCalledTimes(2); + expect(nativeMock.floatVariationDetailDefaultValue).toHaveBeenNthCalledWith(1, 'key3', 1.5, 'default'); + expect(nativeMock.floatVariationDetailDefaultValue).toHaveBeenNthCalledWith(2, 'key4', 5.5, 'env2'); +}); + +test('stringVariationDetail', () => { + client.stringVariationDetail('key1'); + client.stringVariationDetail('key2', undefined, 'env1'); + client.stringVariationDetail('key3', ''); + client.stringVariationDetail('key4', 'abc', 'env2'); + + expect(nativeMock.stringVariationDetail).toHaveBeenCalledTimes(2); + expect(nativeMock.stringVariationDetail).toHaveBeenNthCalledWith(1, 'key1', 'default'); + expect(nativeMock.stringVariationDetail).toHaveBeenNthCalledWith(2, 'key2', 'env1'); + + expect(nativeMock.stringVariationDetailDefaultValue).toHaveBeenCalledTimes(2); + expect(nativeMock.stringVariationDetailDefaultValue).toHaveBeenNthCalledWith(1, 'key3', '', 'default'); + expect(nativeMock.stringVariationDetailDefaultValue).toHaveBeenNthCalledWith(2, 'key4', 'abc', 'env2'); +}); + +test('allFlags', () => { + nativeMock.allFlags.mockReturnValueOnce('pass1'); + expect(client.allFlags()).toBe('pass1'); + expect(nativeMock.allFlags).toHaveBeenCalledTimes(1); + expect(nativeMock.allFlags).toHaveBeenNthCalledWith(1, 'default'); + + nativeMock.allFlags.mockReturnValueOnce('pass2'); + expect(client.allFlags('alt')).toBe('pass2'); + expect(nativeMock.allFlags).toHaveBeenCalledTimes(2); + expect(nativeMock.allFlags).toHaveBeenNthCalledWith(2, 'alt'); +}); + +test('setOffline', () => { + nativeMock.setOffline.mockReturnValue('passthrough'); + expect(client.setOffline()).toBe('passthrough'); + expect(nativeMock.setOffline).toHaveBeenCalledTimes(1); +}); + +test('isOffline', () => { + nativeMock.isOffline.mockReturnValue(true); + expect(client.isOffline()).toBe(true); + expect(nativeMock.isOffline).toHaveBeenCalledTimes(1); +}); + +test('setOnline', () => { + nativeMock.setOnline.mockReturnValue('passthrough'); + expect(client.setOnline()).toBe('passthrough'); + expect(nativeMock.setOnline).toHaveBeenCalledTimes(1); +}); + +test('isInitialized', () => { + nativeMock.isInitialized.mockReturnValueOnce(false); + expect(client.isInitialized()).toBe(false); + nativeMock.isInitialized.mockReturnValueOnce(true); + expect(client.isInitialized('alt')).toBe(true); + + expect(nativeMock.isInitialized).toHaveBeenCalledTimes(2); + expect(nativeMock.isInitialized).toHaveBeenNthCalledWith(1, 'default'); + expect(nativeMock.isInitialized).toHaveBeenNthCalledWith(2, 'alt'); +}); + +test('flush', () => { + client.flush(); + expect(nativeMock.flush).toHaveBeenCalledTimes(1); + expect(nativeMock.flush).toHaveBeenNthCalledWith(1); +}); + +test('close', () => { + client.close(); + expect(nativeMock.close).toHaveBeenCalledTimes(1); + expect(nativeMock.close).toHaveBeenNthCalledWith(1); +}); + +test('identify', () => { + nativeMock.identify.mockReturnValueOnce('pass1'); + expect(client.identify({ name: 'John Smith' })).toBe('pass1'); + expect(nativeMock.identify).toHaveBeenCalledTimes(1); + expect(nativeMock.identify).toHaveBeenNthCalledWith(1, { name: 'John Smith', anonymous: false }); + + nativeMock.identify.mockReturnValueOnce('pass2'); + expect(client.identify({ name: 'Joe Smith', anonymous: true })).toBe('pass2'); + expect(nativeMock.identify).toHaveBeenCalledTimes(2); + expect(nativeMock.identify).toHaveBeenNthCalledWith(2, { name: 'Joe Smith', anonymous: true }); +}); + +test('featureFlagListener', () => { + let clientListener = getClientFlagListener(); + let listener1 = jest.fn(); + let listener2 = jest.fn(); + let listener3 = jest.fn(); + client.registerFeatureFlagListener('a', listener1); + client.registerFeatureFlagListener('a', listener2, 'alt'); + + expect(listener1).toHaveBeenCalledTimes(0); + expect(listener2).toHaveBeenCalledTimes(0); + + expect(nativeMock.registerFeatureFlagListener).toHaveBeenCalledTimes(2); + expect(nativeMock.registerFeatureFlagListener).toHaveBeenNthCalledWith(1, 'a', 'default'); + expect(nativeMock.registerFeatureFlagListener).toHaveBeenNthCalledWith(2, 'a', 'alt'); + + client.registerFeatureFlagListener('a', listener3, 'default'); + // JS wrapper coalesces listeners for the same key and environment + expect(nativeMock.registerFeatureFlagListener).toHaveBeenCalledTimes(2); + expect(listener3).toHaveBeenCalledTimes(0); + + // Wrapper doesn't call listeners for differing key + clientListener({ flagKey: 'b', listenerId: 'default;b' }); + expect(listener1).toHaveBeenCalledTimes(0); + expect(listener2).toHaveBeenCalledTimes(0); + expect(listener3).toHaveBeenCalledTimes(0); + + // Wrapper calls single listener + clientListener({ flagKey: 'a', listenerId: 'alt;a' }); + expect(listener1).toHaveBeenCalledTimes(0); + expect(listener2).toHaveBeenCalledTimes(1); + expect(listener3).toHaveBeenCalledTimes(0); + expect(listener2).toHaveBeenNthCalledWith(1, 'a'); + + // Wrapper informs both coalesced listeners + clientListener({ flagKey: 'a', listenerId: 'default;a' }); + expect(listener1).toHaveBeenCalledTimes(1); + expect(listener2).toHaveBeenCalledTimes(1); + expect(listener3).toHaveBeenCalledTimes(1); + expect(listener1).toHaveBeenNthCalledWith(1, 'a'); + expect(listener3).toHaveBeenNthCalledWith(1, 'a'); + + // Remove single listener from coalesced, should not unregister from native + client.unregisterFeatureFlagListener('a', listener3); + expect(nativeMock.unregisterFeatureFlagListener).toHaveBeenCalledTimes(0); + + clientListener({ flagKey: 'a', listenerId: 'default;a' }); + expect(listener1).toHaveBeenCalledTimes(2); + expect(listener2).toHaveBeenCalledTimes(1); + expect(listener3).toHaveBeenCalledTimes(1); + expect(listener1).toHaveBeenNthCalledWith(2, 'a'); + + // Removing remaining listener should unregister on native + client.unregisterFeatureFlagListener('a', listener1, 'default'); + client.unregisterFeatureFlagListener('a', listener2, 'alt'); + expect(nativeMock.unregisterFeatureFlagListener).toHaveBeenCalledTimes(2); + expect(nativeMock.unregisterFeatureFlagListener).toHaveBeenNthCalledWith(1, 'a', 'default'); + expect(nativeMock.unregisterFeatureFlagListener).toHaveBeenNthCalledWith(2, 'a', 'alt'); + + // No longer calls listeners + clientListener({ flagKey: 'a', listenerId: 'default;a' }); + clientListener({ flagKey: 'a', listenerId: 'alt;a' }); + expect(listener1).toHaveBeenCalledTimes(2); + expect(listener2).toHaveBeenCalledTimes(1); + expect(listener3).toHaveBeenCalledTimes(1); +}); + +test('connectionModeListener', () => { + let clientListener = getClientConnectionListener(); + let listener1 = jest.fn(); + let listener2 = jest.fn(); + client.registerCurrentConnectionModeListener('a', listener1); + client.registerCurrentConnectionModeListener('b', listener2, 'alt'); + + expect(listener1).toHaveBeenCalledTimes(0); + expect(listener2).toHaveBeenCalledTimes(0); + + expect(nativeMock.registerCurrentConnectionModeListener).toHaveBeenCalledTimes(2); + expect(nativeMock.registerCurrentConnectionModeListener).toHaveBeenNthCalledWith(1, 'a', 'default'); + expect(nativeMock.registerCurrentConnectionModeListener).toHaveBeenNthCalledWith(2, 'b', 'alt'); + + clientListener({ connectionMode: ['abc'], listenerId: 'default;a' }); + expect(listener1).toHaveBeenCalledTimes(1); + expect(listener1).toHaveBeenNthCalledWith(1, ['abc']); + expect(listener2).toHaveBeenCalledTimes(0); + + clientListener({ connectionMode: ['def'], listenerId: 'alt;b' }); + expect(listener1).toHaveBeenCalledTimes(1); + expect(listener2).toHaveBeenCalledTimes(1); + expect(listener2).toHaveBeenNthCalledWith(1, ['def']); + + client.unregisterCurrentConnectionModeListener('b', 'alt'); + + expect(nativeMock.unregisterCurrentConnectionModeListener).toHaveBeenCalledTimes(1); + expect(nativeMock.unregisterCurrentConnectionModeListener).toHaveBeenNthCalledWith(1, 'b', 'alt'); + + clientListener({ connectionMode: [], listenerId: 'alt;b' }); + clientListener({ connectionMode: [], listenerId: 'alt;a' }); + + expect(listener1).toHaveBeenCalledTimes(1); + expect(listener2).toHaveBeenCalledTimes(1); + + client.unregisterCurrentConnectionModeListener('a', 'default'); + client.unregisterCurrentConnectionModeListener('b', 'alt'); + + expect(nativeMock.unregisterCurrentConnectionModeListener).toHaveBeenCalledTimes(2); + expect(nativeMock.unregisterCurrentConnectionModeListener).toHaveBeenNthCalledWith(2, 'a', 'default'); +}); + +test('allFlagsListener', () => { + let clientListener = getClientFlagsListener(); + let listener1 = jest.fn(); + let listener2 = jest.fn(); + client.registerAllFlagsListener('a', listener1); + client.registerAllFlagsListener('b', listener2, 'alt'); + + expect(listener1).toHaveBeenCalledTimes(0); + expect(listener2).toHaveBeenCalledTimes(0); + + expect(nativeMock.registerAllFlagsListener).toHaveBeenCalledTimes(2); + expect(nativeMock.registerAllFlagsListener).toHaveBeenNthCalledWith(1, 'a', 'default'); + expect(nativeMock.registerAllFlagsListener).toHaveBeenNthCalledWith(2, 'b', 'alt'); + + clientListener({ flagKeys: ['abc'], listenerId: 'default;a' }); + expect(listener1).toHaveBeenCalledTimes(1); + expect(listener1).toHaveBeenNthCalledWith(1, ['abc']); + expect(listener2).toHaveBeenCalledTimes(0); + + clientListener({ flagKeys: ['def'], listenerId: 'alt;b' }); + expect(listener1).toHaveBeenCalledTimes(1); + expect(listener2).toHaveBeenCalledTimes(1); + expect(listener2).toHaveBeenNthCalledWith(1, ['def']); + + client.unregisterAllFlagsListener('b', 'alt'); + + expect(nativeMock.unregisterAllFlagsListener).toHaveBeenCalledTimes(1); + expect(nativeMock.unregisterAllFlagsListener).toHaveBeenNthCalledWith(1, 'b', 'alt'); + + clientListener({ flagKeys: [], listenerId: 'alt;b' }); + clientListener({ flagKeys: [], listenerId: 'alt;a' }); + + expect(listener1).toHaveBeenCalledTimes(1); + expect(listener2).toHaveBeenCalledTimes(1); + + client.unregisterAllFlagsListener('a', 'default'); + client.unregisterAllFlagsListener('b', 'alt'); + + expect(nativeMock.unregisterAllFlagsListener).toHaveBeenCalledTimes(2); + expect(nativeMock.unregisterAllFlagsListener).toHaveBeenNthCalledWith(2, 'a', 'default'); +}); + +test('getConnectionMode', () => { + nativeMock.getConnectionMode.mockReturnValue('passthrough'); + + expect(client.getConnectionMode()).toBe('passthrough'); + expect(client.getConnectionMode('alt')).toBe('passthrough'); + + expect(nativeMock.getConnectionMode).toHaveBeenCalledTimes(2); + expect(nativeMock.getConnectionMode).toHaveBeenNthCalledWith(1, 'default'); + expect(nativeMock.getConnectionMode).toHaveBeenNthCalledWith(2, 'alt'); +}); + +test('getLastSuccessfulConnection', () => { + nativeMock.getLastSuccessfulConnection.mockReturnValue('passthrough'); + + expect(client.getLastSuccessfulConnection()).toBe('passthrough'); + expect(client.getLastSuccessfulConnection('alt')).toBe('passthrough'); + + expect(nativeMock.getLastSuccessfulConnection).toHaveBeenCalledTimes(2); + expect(nativeMock.getLastSuccessfulConnection).toHaveBeenNthCalledWith(1, 'default'); + expect(nativeMock.getLastSuccessfulConnection).toHaveBeenNthCalledWith(2, 'alt'); +}); + +test('getLastFailedConnection', () => { + nativeMock.getLastFailedConnection.mockReturnValue('passthrough'); + + expect(client.getLastFailedConnection()).toBe('passthrough'); + expect(client.getLastFailedConnection('alt')).toBe('passthrough'); + + expect(nativeMock.getLastFailedConnection).toHaveBeenCalledTimes(2); + expect(nativeMock.getLastFailedConnection).toHaveBeenNthCalledWith(1, 'default'); + expect(nativeMock.getLastFailedConnection).toHaveBeenNthCalledWith(2, 'alt'); +}); + +test('getLastFailure', () => { + nativeMock.getLastFailure.mockReturnValue('passthrough'); + + expect(client.getLastFailure()).toBe('passthrough'); + expect(client.getLastFailure('alt')).toBe('passthrough'); + + expect(nativeMock.getLastFailure).toHaveBeenCalledTimes(2); + expect(nativeMock.getLastFailure).toHaveBeenNthCalledWith(1, 'default'); + expect(nativeMock.getLastFailure).toHaveBeenNthCalledWith(2, 'alt'); +}); diff --git a/package.json b/package.json index 50b3f74..8ddfafe 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "types": "index.d.ts", "scripts": { "check-typescript": "node_modules/typescript/bin/tsc", - "test": "echo \"Error: no test specified\" && exit 1" + "test": "jest", + "test:junit": "jest --testResultsProcessor jest-junit" }, "repository": { "type": "git", @@ -25,7 +26,23 @@ "react-native": "~0.64" }, "devDependencies": { - "metro-react-native-babel-preset": "0.59.0", + "jest": "^26.6.3", + "jest-junit": "^11.1.0", "typescript": "^3.8.3" + }, + "jest": { + "preset": "react-native", + "setupFiles": [ + "./__mocks__/native.js" + ], + "transform": { + "^.+\\.js$": "/node_modules/react-native/jest/preprocessor.js" + }, + "testPathIgnorePatterns": [ + "/node_modules/" + ], + "transformIgnorePatterns": [ + "node_modules/(?!(jest-)?react-native|@react-native-community|@react-native)" + ] } } From 3da41af4dc91543f4b973ec9369b812469ffc098 Mon Sep 17 00:00:00 2001 From: Gavin Whelan Date: Wed, 16 Jun 2021 11:01:04 -0700 Subject: [PATCH 18/32] Docs improvements (#84) --- README.md | 8 +++++--- index.d.ts | 1 - 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5e3eab0..e4ad87b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ LaunchDarkly Client-Side SDK for React Native =========================== -[![CircleCI](https://circleci.com/gh/launchdarkly/react-native-client-sdk.svg?style=svg)](https://circleci.com/gh/launchdarkly/react-native-client-sdk) +[![NPM](https://img.shields.io/npm/v/launchdarkly-react-native-client-sdk.svg)](https://www.npmjs.com/package/launchdarkly-react-native-client-sdk) +[![CircleCI](https://circleci.com/gh/launchdarkly/react-native-client-sdk.svg?style=shield)](https://circleci.com/gh/launchdarkly/react-native-client-sdk) +[![Documentation](https://img.shields.io/static/v1?label=GitHub+Pages&message=API+reference&color=00add8)](https://launchdarkly.github.io/react-native-client-sdk) LaunchDarkly overview ------------------------- @@ -23,12 +25,12 @@ This SDK is currently compatible with React Native 0.64.x and Xcode 12 and is te Getting started --------------- -Refer to the [SDK documentation](https://docs.launchdarkly.com/sdk/client-side/react-native#getting-started) for instructions on getting started with using the SDK. +Refer to the [SDK documentation](https://docs.launchdarkly.com/sdk/client-side/react/react-native#getting-started) for instructions on getting started with using the SDK. Learn more ----------- -Check out our [documentation](https://docs.launchdarkly.com) for in-depth instructions on configuring and using LaunchDarkly. You can also head straight to the [complete reference guide for this SDK](https://docs.launchdarkly.com/sdk/client-side/react-native). +Check out our [documentation](https://docs.launchdarkly.com) for in-depth instructions on configuring and using LaunchDarkly. You can also head straight to the [complete reference guide for this SDK](https://docs.launchdarkly.com/sdk/client-side/react/react-native). Testing ------- diff --git a/index.d.ts b/index.d.ts index 860e678..e368e77 100644 --- a/index.d.ts +++ b/index.d.ts @@ -640,7 +640,6 @@ declare module 'launchdarkly-react-native-client-sdk' { * Optional environment name to obtain the result from the corresponding secondary environment * @returns * A promise containing an object in which each key is a feature flag key and each value is the flag value. - * The promise will be rejected if the SDK has not yet completed initialization. * Note that there is no way to specify a default value for each flag as there is with the * `*Variation` methods, so any flag that cannot be evaluated will have a null value. */ From 493d554e105b23cd7e29b92ac28088a57cfcc573 Mon Sep 17 00:00:00 2001 From: Gavin Whelan Date: Wed, 16 Jun 2021 11:02:54 -0700 Subject: [PATCH 19/32] [ch94513] Add aliasing support (#85) --- __mocks__/native.js | 1 + .../LaunchdarklyReactNativeClientModule.java | 17 ++++++++++++- index.d.ts | 17 +++++++++++++ index.js | 13 +++++----- index.test.js | 19 ++++++++++---- ios/LaunchdarklyReactNativeClient.swift | 25 ++++++++++++++++--- ios/LaunchdarklyReactNativeClientBridge.m | 2 ++ 7 files changed, 77 insertions(+), 17 deletions(-) diff --git a/__mocks__/native.js b/__mocks__/native.js index 4be8f95..9e90407 100644 --- a/__mocks__/native.js +++ b/__mocks__/native.js @@ -59,6 +59,7 @@ const mockNativeModule = { flush: jest.fn(), close: jest.fn(), identify: jest.fn(), + alias: jest.fn(), getConnectionMode: jest.fn(), getLastSuccessfulConnection: jest.fn(), getLastFailedConnection: jest.fn(), diff --git a/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java b/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java index 5229988..f652ff0 100644 --- a/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java +++ b/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java @@ -70,7 +70,8 @@ enum ConfigMapping { CONFIG_MAX_CACHED_USERS("maxCachedUsers", ConfigEntryType.Integer), CONFIG_DIAGNOSTIC_OPT_OUT("diagnosticOptOut", ConfigEntryType.Boolean), CONFIG_DIAGNOSTIC_RECORDING_INTERVAL("diagnosticRecordingIntervalMillis", ConfigEntryType.Integer), - CONFIG_SECONDARY_MOBILE_KEYS("secondaryMobileKeys", ConfigEntryType.Map); + CONFIG_SECONDARY_MOBILE_KEYS("secondaryMobileKeys", ConfigEntryType.Map), + CONFIG_AUTO_ALIASING_OPT_OUT("autoAliasingOptOut", ConfigEntryType.Boolean); final String key; final ConfigEntryType type; @@ -771,6 +772,20 @@ public void run() { background.start(); } + @ReactMethod + public void alias(String environment, ReadableMap user, ReadableMap previousUser) { + LDUser.Builder userBuilder = userBuild(user); + LDUser.Builder previousUserBuilder = userBuild(previousUser); + if (userBuilder == null || previousUserBuilder == null) { + return; + } + try { + LDClient.getForMobileKey(environment).alias(userBuilder.build(), previousUserBuilder.build()); + } catch (LaunchDarklyException e) { + Timber.w("LaunchDarkly alias called with invalid environment"); + } + } + @ReactMethod public void getConnectionMode(String environment,Promise promise) { try { diff --git a/index.d.ts b/index.d.ts index e368e77..1fd275f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -732,6 +732,23 @@ declare module 'launchdarkly-react-native-client-sdk' { * A promise indicating when this operation is complete (meaning that flags are ready for evaluation). */ identify(user: LDUser): Promise; + + /** + * Alias associates two users for analytics purposes by generating an alias event. + * + * This can be helpful in the situation where a person is represented by multiple + * LaunchDarkly users. This may happen, for example, when a person initially logs into + * an application-- the person might be represented by an anonymous user prior to logging + * in and a different user after logging in, as denoted by a different user key. + * + * @param user + * The new user context + * @param previousUser + * The original user context + * @param environment + * Optional string to execute the function in a different environment than the default. + */ + alias(user: LDUser, previousUser: LDUser, environment?: string): void; /** * Registers a callback to be called when the flag with key `flagKey` changes from its current value. diff --git a/index.js b/index.js index 3198084..760a738 100644 --- a/index.js +++ b/index.js @@ -34,9 +34,9 @@ export default class LDClient { }, config); if (timeout == undefined) { - return LaunchdarklyReactNativeClient.configure(configWithOverriddenDefaults, this._addUserOverrides(user)); + return LaunchdarklyReactNativeClient.configure(configWithOverriddenDefaults, user); } else { - return LaunchdarklyReactNativeClient.configureWithTimeout(configWithOverriddenDefaults, this._addUserOverrides(user), timeout); + return LaunchdarklyReactNativeClient.configureWithTimeout(configWithOverriddenDefaults, user, timeout); } } ); @@ -216,13 +216,12 @@ export default class LDClient { } identify(user) { - return LaunchdarklyReactNativeClient.identify(this._addUserOverrides(user)); + return LaunchdarklyReactNativeClient.identify(user); } - _addUserOverrides(user) { - return Object.assign({ - anonymous: false // the iOS SDK defaults this to true - }, user); + alias(user, previousUser, environment) { + const env = environment !== undefined ? environment : "default"; + LaunchdarklyReactNativeClient.alias(env, user, previousUser); } _flagUpdateListener(changedFlag) { diff --git a/index.test.js b/index.test.js index c5c50a5..d0eb264 100644 --- a/index.test.js +++ b/index.test.js @@ -220,12 +220,21 @@ test('identify', () => { nativeMock.identify.mockReturnValueOnce('pass1'); expect(client.identify({ name: 'John Smith' })).toBe('pass1'); expect(nativeMock.identify).toHaveBeenCalledTimes(1); - expect(nativeMock.identify).toHaveBeenNthCalledWith(1, { name: 'John Smith', anonymous: false }); + expect(nativeMock.identify).toHaveBeenNthCalledWith(1, { name: 'John Smith' }); +}); - nativeMock.identify.mockReturnValueOnce('pass2'); - expect(client.identify({ name: 'Joe Smith', anonymous: true })).toBe('pass2'); - expect(nativeMock.identify).toHaveBeenCalledTimes(2); - expect(nativeMock.identify).toHaveBeenNthCalledWith(2, { name: 'Joe Smith', anonymous: true }); +test('alias', () => { + client.alias({ key: 'anon', anonymous: true }, { key: 'abc' }); + expect(nativeMock.alias).toHaveBeenCalledTimes(1); + expect(nativeMock.alias) + .toHaveBeenNthCalledWith(1, 'default', + { key: 'anon', anonymous: true }, + { key: 'abc' }); + + client.alias({ key: 'abc' }, { key: 'def' }, 'alt'); + expect(nativeMock.alias).toHaveBeenCalledTimes(2); + expect(nativeMock.alias) + .toHaveBeenNthCalledWith(2, 'alt', { key: 'abc' }, { key: 'def' }); }); test('featureFlagListener', () => { diff --git a/ios/LaunchdarklyReactNativeClient.swift b/ios/LaunchdarklyReactNativeClient.swift index da7d816..93d89e2 100644 --- a/ios/LaunchdarklyReactNativeClient.swift +++ b/ios/LaunchdarklyReactNativeClient.swift @@ -143,15 +143,21 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { if config["allUserAttributesPrivate"] != nil { ldConfig.allUserAttributesPrivate = config["allUserAttributesPrivate"] as! Bool } - - ldConfig.autoAliasingOptOut = true + + if config["autoAliasingOptOut"] != nil { + ldConfig.autoAliasingOptOut = config["autoAliasingOptOut"] as! Bool + } return ldConfig } private func userBuild(userDict: NSDictionary) -> LDUser? { - var user = LDUser() - user.key = userDict["key"] as! String + guard let userKey = userDict["key"] as? String + else { + return nil + } + + var user = LDUser(key: userKey) if userDict["secondary"] != nil { user.secondary = userDict["secondary"] as? String @@ -484,6 +490,17 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { reject(ERROR_IDENTIFY, "User could not be built using supplied configuration", nil) } } + + @objc func alias(_ environment: String, user: NSDictionary, previousUser: NSDictionary) -> Void { + let builtUser = userBuild(userDict: user) + let builtPreviousUser = userBuild(userDict: previousUser) + guard let user = builtUser, + let previousUser = builtPreviousUser + else { + return + } + LDClient.get(environment: environment)!.alias(context: user, previousContext: previousUser) + } @objc func allFlags(_ environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { var allFlagsDict: [String: Any] = [:] diff --git a/ios/LaunchdarklyReactNativeClientBridge.m b/ios/LaunchdarklyReactNativeClientBridge.m index 835e4b2..97a00d0 100644 --- a/ios/LaunchdarklyReactNativeClientBridge.m +++ b/ios/LaunchdarklyReactNativeClientBridge.m @@ -99,6 +99,8 @@ @interface RCT_EXTERN_MODULE(LaunchdarklyReactNativeClient, RCTEventEmitter) RCT_EXTERN_METHOD(identify:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(alias:(NSString *)environment user:(NSDictionary *)user previousUser:(NSDictionary *)previousUser) + RCT_EXTERN_METHOD(allFlags:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) RCT_EXTERN_METHOD(registerFeatureFlagListener:(NSString *)flagKey environment:(NSString *)environment) From a735babbeab0583ffba18bfefb984ca4ae5ac82e Mon Sep 17 00:00:00 2001 From: Gavin Whelan Date: Tue, 29 Jun 2021 14:58:04 -0700 Subject: [PATCH 20/32] Requiring default values to be specified. Remove intVariation. (#86) --- __mocks__/native.js | 12 +- .../LaunchdarklyReactNativeClientModule.java | 102 +++------- index.d.ts | 46 +---- index.js | 89 ++++---- index.test.js | 192 +++++++++--------- ios/LaunchdarklyReactNativeClient.swift | 86 +------- ios/LaunchdarklyReactNativeClientBridge.m | 32 +-- test-types.ts | 12 +- 8 files changed, 185 insertions(+), 386 deletions(-) diff --git a/__mocks__/native.js b/__mocks__/native.js index 9e90407..a4a9fce 100644 --- a/__mocks__/native.js +++ b/__mocks__/native.js @@ -8,10 +8,8 @@ const mockNativeModule = { boolVariation: jest.fn(), boolVariationDefaultValue: jest.fn(), - intVariation: jest.fn(), - intVariationDefaultValue: jest.fn(), - floatVariation: jest.fn(), - floatVariationDefaultValue: jest.fn(), + numberVariation: jest.fn(), + numberVariationDefaultValue: jest.fn(), stringVariation: jest.fn(), stringVariationDefaultValue: jest.fn(), jsonVariationNone: jest.fn(), @@ -23,10 +21,8 @@ const mockNativeModule = { boolVariationDetail: jest.fn(), boolVariationDetailDefaultValue: jest.fn(), - intVariationDetail: jest.fn(), - intVariationDetailDefaultValue: jest.fn(), - floatVariationDetail: jest.fn(), - floatVariationDetailDefaultValue: jest.fn(), + numberVariationDetail: jest.fn(), + numberVariationDetailDefaultValue: jest.fn(), stringVariationDetail: jest.fn(), stringVariationDetailDefaultValue: jest.fn(), jsonVariationDetailNone: jest.fn(), diff --git a/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java b/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java index f652ff0..0468f65 100644 --- a/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java +++ b/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java @@ -4,6 +4,7 @@ import android.net.Uri; import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.Dynamic; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; @@ -304,12 +305,7 @@ private LDUser.Builder userBuild(ReadableMap options) { } @ReactMethod - public void boolVariation(String flagKey, String environment, Promise promise) { - boolVariationDefaultValue(flagKey, null, environment, promise); - } - - @ReactMethod - public void boolVariationDefaultValue(String flagKey, Boolean defaultValue, String environment, Promise promise) { + public void boolVariation(String flagKey, boolean defaultValue, String environment, Promise promise) { try { promise.resolve(LDClient.getForMobileKey(environment).boolVariation(flagKey, defaultValue)); } catch (Exception e) { @@ -318,40 +314,16 @@ public void boolVariationDefaultValue(String flagKey, Boolean defaultValue, Stri } @ReactMethod - public void intVariation(String flagKey, String environment, Promise promise) { - intVariationDefaultValue(flagKey, null, environment, promise); - } - - @ReactMethod - public void intVariationDefaultValue(String flagKey, Integer defaultValue, String environment, Promise promise) { - try { - promise.resolve(LDClient.getForMobileKey(environment).intVariation(flagKey, defaultValue)); - } catch (Exception e) { - promise.resolve(defaultValue); - } - } - - @ReactMethod - public void floatVariation(String flagKey, String environment, Promise promise) { - floatVariationDefaultValue(flagKey, null, environment, promise); - } - - @ReactMethod - public void floatVariationDefaultValue(String flagKey, Float defaultValue, String environment, Promise promise) { + public void floatVariation(String flagKey, double defaultValue, String environment, Promise promise) { try { - promise.resolve(LDClient.getForMobileKey(environment).doubleVariation(flagKey, defaultValue.doubleValue())); + promise.resolve(LDClient.getForMobileKey(environment).doubleVariation(flagKey, defaultValue)); } catch (Exception e) { promise.resolve(defaultValue); } } @ReactMethod - public void stringVariation(String flagKey, String environment, Promise promise) { - stringVariationDefaultValue(flagKey, null, environment, promise); - } - - @ReactMethod - public void stringVariationDefaultValue(String flagKey, String defaultValue, String environment, Promise promise) { + public void stringVariation(String flagKey, String defaultValue, String environment, Promise promise) { try { promise.resolve(LDClient.getForMobileKey(environment).stringVariation(flagKey, defaultValue)); } catch (Exception e) { @@ -390,12 +362,7 @@ public void jsonVariationObject(String flagKey, ReadableMap defaultValue, String } @ReactMethod - public void boolVariationDetail(String flagKey, String environment, Promise promise) { - boolVariationDetailDefaultValue(flagKey, null, environment, promise); - } - - @ReactMethod - public void boolVariationDetailDefaultValue(String flagKey, Boolean defaultValue, String environment, Promise promise) { + public void boolVariationDetail(String flagKey, boolean defaultValue, String environment, Promise promise) { EvaluationDetail detailResult; try { detailResult = LDClient.getForMobileKey(environment).boolVariationDetail(flagKey, defaultValue); @@ -413,42 +380,13 @@ public void boolVariationDetailDefaultValue(String flagKey, Boolean defaultValue } @ReactMethod - public void intVariationDetail(String flagKey, String environment, Promise promise) { - intVariationDetailDefaultValue(flagKey, null, environment, promise); - } - - @ReactMethod - public void intVariationDetailDefaultValue(String flagKey, Integer defaultValue, String environment, Promise promise) { - EvaluationDetail detailResult; - try { - detailResult = LDClient.getForMobileKey(environment).intVariationDetail(flagKey, defaultValue); - } catch (Exception e) { - Timber.w(e); - detailResult = EvaluationDetail.fromValue(defaultValue, EvaluationDetail.NO_VARIATION, EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION)); - } - WritableMap result = detailToMap(detailResult); - if (detailResult.getValue() == null) { - result.putNull("value"); - } else { - result.putInt("value", detailResult.getValue()); - } - promise.resolve(result); - } - - @ReactMethod - public void floatVariationDetail(String flagKey, String environment, Promise promise) { - floatVariationDetailDefaultValue(flagKey, null, environment, promise); - } - - @ReactMethod - public void floatVariationDetailDefaultValue(String flagKey, Float defaultValue, String environment, Promise promise) { + public void numberVariationDetail(String flagKey, double defaultValue, String environment, Promise promise) { EvaluationDetail detailResult; - Double doubleValue = defaultValue.doubleValue(); try { - detailResult = LDClient.getForMobileKey(environment).doubleVariationDetail(flagKey, doubleValue); + detailResult = LDClient.getForMobileKey(environment).doubleVariationDetail(flagKey, defaultValue); } catch (Exception e) { Timber.w(e); - detailResult = EvaluationDetail.fromValue(doubleValue, EvaluationDetail.NO_VARIATION, EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION)); + detailResult = EvaluationDetail.fromValue(defaultValue, EvaluationDetail.NO_VARIATION, EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION)); } WritableMap result = detailToMap(detailResult); if (detailResult.getValue() == null) { @@ -460,12 +398,7 @@ public void floatVariationDetailDefaultValue(String flagKey, Float defaultValue, } @ReactMethod - public void stringVariationDetail(String flagKey, String environment, Promise promise) { - stringVariationDetailDefaultValue(flagKey, null, environment, promise); - } - - @ReactMethod - public void stringVariationDetailDefaultValue(String flagKey, String defaultValue, String environment, Promise promise) { + public void stringVariationDetail(String flagKey, String defaultValue, String environment, Promise promise) { EvaluationDetail detailResult; try { detailResult = LDClient.getForMobileKey(environment).stringVariationDetail(flagKey, defaultValue); @@ -940,6 +873,21 @@ public void unregisterAllFlagsListener(String listenerId, String environment) { } } + private static LDValue toLDValue(Dynamic data) { + if (data == null) { + return LDValue.ofNull(); + } + switch (data.getType()) { + case Boolean: return LDValue.of(data.asBoolean()); + case Number: return LDValue.of(data.asDouble()); + case String: return LDValue.of(data.asString()); + case Array: return toLDValue(data.asArray()); + case Map: return toLDValue(data.asMap()); + default: return LDValue.ofNull(); + } + } + + private static LDValue toLDValue(ReadableArray readableArray) { if (readableArray == null) { return LDValue.ofNull(); diff --git a/index.d.ts b/index.d.ts index 1fd275f..b578f04 100644 --- a/index.d.ts +++ b/index.d.ts @@ -454,7 +454,7 @@ declare module 'launchdarkly-react-native-client-sdk' { boolVariation(flagKey: string, defaultValue: boolean, environment?: string): Promise; /** - * Determines the variation of an integer feature flag for the current user. + * Determines the variation of a numeric feature flag for the current user. * * @param flagKey * The unique key of the feature flag. @@ -465,21 +465,7 @@ declare module 'launchdarkly-react-native-client-sdk' { * @returns * A promise containing the flag's value. */ - intVariation(flagKey: string, defaultValue: number, environment?: string): Promise; - - /** - * Determines the variation of a floating-point feature flag for the current user. - * - * @param flagKey - * The unique key of the feature flag. - * @param defaultValue - * The default value of the flag, to be used if the value is not available from LaunchDarkly. - * @param environment - * Optional environment name to obtain the result from the corresponding secondary environment - * @returns - * A promise containing the flag's value. - */ - floatVariation(flagKey: string, defaultValue: number, environment?: string): Promise; + numberVariation(flagKey: string, defaultValue: number, environment?: string): Promise; /** * Determines the variation of a string feature flag for the current user. @@ -538,31 +524,7 @@ declare module 'launchdarkly-react-native-client-sdk' { ): Promise>; /** - * Determines the variation of an integer feature flag for a user, along with information about how it was - * calculated. - * - * Note that this will only work if you have set `evaluationReasons` to true in [[LDConfig]]. - * Otherwise, the `reason` property of the result will be null. - * - * For more information, see the [SDK reference guide](https://docs.launchdarkly.com/sdk/concepts/evaluation-reasons). - * - * @param flagKey - * The unique key of the feature flag. - * @param defaultValue - * The default value of the flag, to be used if the value is not available from LaunchDarkly. - * @param environment - * Optional environment name to obtain the result from the corresponding secondary environment - * @returns - * A promise containing an [[LDEvaluationDetail]] object containing the value and explanation. - */ - intVariationDetail( - flagKey: string, - defaultValue: number, - environment?: string - ): Promise>; - - /** - * Determines the variation of a floating-point feature flag for a user, along with information about how it was + * Determines the variation of a numeric feature flag for a user, along with information about how it was * calculated. * * Note that this will only work if you have set `evaluationReasons` to true in [[LDConfig]]. @@ -579,7 +541,7 @@ declare module 'launchdarkly-react-native-client-sdk' { * @returns * A promise containing an [[LDEvaluationDetail]] object containing the value and explanation. */ - floatVariationDetail( + numberVariationDetail( flagKey: string, defaultValue: number, environment?: string diff --git a/index.js b/index.js index 760a738..f489adb 100644 --- a/index.js +++ b/index.js @@ -42,40 +42,42 @@ export default class LDClient { ); } - boolVariation(flagKey, defaultValue, environment) { - const env = environment !== undefined ? environment : "default"; - if (defaultValue == undefined) { - return LaunchdarklyReactNativeClient.boolVariation(flagKey, env); - } else { - return LaunchdarklyReactNativeClient.boolVariationDefaultValue(flagKey, defaultValue, env); + _validateDefault(defaultType, defaultValue, validator) { + if (typeof defaultValue !== defaultType || + (typeof validator === 'function' && !validator(defaultValue))) { + return Promise.reject(new Error('Missing or invalid defaultValue for variation call')); } + return Promise.resolve(); } - intVariation(flagKey, defaultValue, environment) { - const env = environment !== undefined ? environment : "default"; - if (defaultValue == undefined) { - return LaunchdarklyReactNativeClient.intVariation(flagKey, env); - } else { - return LaunchdarklyReactNativeClient.intVariationDefaultValue(flagKey, defaultValue, env); - } + _validateInt(val) { + return !isNaN(val) && val > -0x80000001 && val < 0x80000000; } - floatVariation(flagKey, defaultValue, environment) { - const env = environment !== undefined ? environment : "default"; - if (defaultValue == undefined) { - return LaunchdarklyReactNativeClient.floatVariation(flagKey, env); - } else { - return LaunchdarklyReactNativeClient.floatVariationDefaultValue(flagKey, defaultValue, env); + _normalizeEnv(environment) { + if (typeof environment !== 'string') { + return 'default'; } + return environment; + } + + boolVariation(flagKey, defaultValue, environment) { + return this._validateDefault('boolean', defaultValue) + .then(() => LaunchdarklyReactNativeClient.boolVariation(flagKey, defaultValue, this._normalizeEnv(environment))); + } + + numberVariation(flagKey, defaultValue, environment) { + return this._validateDefault('number', defaultValue, val => !isNaN(val)) + .then(() => LaunchdarklyReactNativeClient.numberVariation(flagKey, defaultValue, this._normalizeEnv(environment))); } stringVariation(flagKey, defaultValue, environment) { - const env = environment !== undefined ? environment : "default"; - if (defaultValue == undefined) { - return LaunchdarklyReactNativeClient.stringVariation(flagKey, env); - } else { - return LaunchdarklyReactNativeClient.stringVariationDefaultValue(flagKey, defaultValue, env); + if (defaultValue != null && typeof defaultValue !== 'string') { + return Promise.reject(new Error('Missing or invalid defaultValue for variation call')); + } else if (defaultValue === undefined) { + defaultValue = null; } + return LaunchdarklyReactNativeClient.stringVariation(flagKey, defaultValue, this._normalizeEnv(environment)); } jsonVariation(flagKey, defaultValue, environment) { @@ -97,39 +99,22 @@ export default class LDClient { } boolVariationDetail(flagKey, defaultValue, environment) { - const env = environment !== undefined ? environment : "default"; - if (defaultValue == undefined) { - return LaunchdarklyReactNativeClient.boolVariationDetail(flagKey, env); - } else { - return LaunchdarklyReactNativeClient.boolVariationDetailDefaultValue(flagKey, defaultValue, env); - } - } - - intVariationDetail(flagKey, defaultValue, environment) { - const env = environment !== undefined ? environment : "default"; - if (defaultValue == undefined) { - return LaunchdarklyReactNativeClient.intVariationDetail(flagKey, env); - } else { - return LaunchdarklyReactNativeClient.intVariationDetailDefaultValue(flagKey, defaultValue, env); - } + return this._validateDefault('boolean', defaultValue) + .then(() => LaunchdarklyReactNativeClient.boolVariationDetail(flagKey, defaultValue, this._normalizeEnv(environment))); } - floatVariationDetail(flagKey, defaultValue, environment) { - const env = environment !== undefined ? environment : "default"; - if (defaultValue == undefined) { - return LaunchdarklyReactNativeClient.floatVariationDetail(flagKey, env); - } else { - return LaunchdarklyReactNativeClient.floatVariationDetailDefaultValue(flagKey, defaultValue, env); - } + numberVariationDetail(flagKey, defaultValue, environment) { + return this._validateDefault('number', defaultValue, val => !isNaN(val)) + .then(() => LaunchdarklyReactNativeClient.numberVariationDetail(flagKey, defaultValue, this._normalizeEnv(environment))); } stringVariationDetail(flagKey, defaultValue, environment) { - const env = environment !== undefined ? environment : "default"; - if (defaultValue == undefined) { - return LaunchdarklyReactNativeClient.stringVariationDetail(flagKey, env); - } else { - return LaunchdarklyReactNativeClient.stringVariationDetailDefaultValue(flagKey, defaultValue, env); + if (defaultValue != null && typeof defaultValue !== 'string') { + return Promise.reject(new Error('Missing or invalid defaultValue for variation call')); + } else if (defaultValue === undefined) { + defaultValue = null; } + return LaunchdarklyReactNativeClient.stringVariationDetail(flagKey, defaultValue, this._normalizeEnv(environment)); } jsonVariationDetail(flagKey, defaultValue, environment) { @@ -157,7 +142,7 @@ export default class LDClient { track(eventName, data, metricValue, environment) { const env = environment !== undefined ? environment : "default"; - if (metricValue) { + if (typeof metricValue === 'number') { if (data === null || typeof data === 'undefined') { LaunchdarklyReactNativeClient.trackMetricValue(eventName, metricValue, env); } else if (typeof data === 'number') { diff --git a/index.test.js b/index.test.js index d0eb264..20318e8 100644 --- a/index.test.js +++ b/index.test.js @@ -1,6 +1,8 @@ import { NativeModules, NativeEventEmitter } from 'react-native'; import LDClient from './index.js'; +const defValErr = new Error('Missing or invalid defaultValue for variation call'); + var client; var addListenerMock; let nativeMock = NativeModules.LaunchdarklyReactNativeClient; @@ -43,124 +45,128 @@ test('constructor', () => { expect(addListenerMock.calls[2][0]).toBe(nativeMock.CONNECTION_MODE_PREFIX); }); -test('boolVariation', () => { - client.boolVariation('key1'); - client.boolVariation('key2', undefined, 'env1'); - client.boolVariation('key3', true); - client.boolVariation('key4', false, 'env2'); +describe('boolVariation', () => { + test('validates defaultValue', async () => { + expect.assertions(11); - expect(nativeMock.boolVariation).toHaveBeenCalledTimes(2); - expect(nativeMock.boolVariation).toHaveBeenNthCalledWith(1, 'key1', 'default'); - expect(nativeMock.boolVariation).toHaveBeenNthCalledWith(2, 'key2', 'env1'); + await expect(client.boolVariation('flagKey')).rejects.toEqual(defValErr); + await expect(client.boolVariation('flagKey', null)).rejects.toEqual(defValErr); + await expect(client.boolVariation('flagKey', 5)).rejects.toEqual(defValErr); + await expect(client.boolVariationDetail('flagKey')).rejects.toEqual(defValErr); + await expect(client.boolVariationDetail('flagKey', null)).rejects.toEqual(defValErr); + await expect(client.boolVariationDetail('flagKey', 5)).rejects.toEqual(defValErr); - expect(nativeMock.boolVariationDefaultValue).toHaveBeenCalledTimes(2); - expect(nativeMock.boolVariationDefaultValue).toHaveBeenNthCalledWith(1, 'key3', true, 'default'); - expect(nativeMock.boolVariationDefaultValue).toHaveBeenNthCalledWith(2, 'key4', false, 'env2'); -}); + expect(nativeMock.boolVariation).toHaveBeenCalledTimes(0); + expect(nativeMock.boolVariationDetail).toHaveBeenCalledTimes(0); + }); -test('intVariation', () => { - client.intVariation('key1'); - client.intVariation('key2', undefined, 'env1'); - client.intVariation('key3', 0); - client.intVariation('key4', 5, 'env2'); + test('calls native', async () => { + nativeMock.boolVariation.mockImplementation((k, def, env) => Promise.resolve(!def)); - expect(nativeMock.intVariation).toHaveBeenCalledTimes(2); - expect(nativeMock.intVariation).toHaveBeenNthCalledWith(1, 'key1', 'default'); - expect(nativeMock.intVariation).toHaveBeenNthCalledWith(2, 'key2', 'env1'); + await expect(client.boolVariation('key1', true)).resolves.toEqual(false); + await expect(client.boolVariation('key2', false, 'alt')).resolves.toEqual(true); + await expect(client.boolVariation('key3', false, 5)).resolves.toEqual(true); - expect(nativeMock.intVariationDefaultValue).toHaveBeenCalledTimes(2); - expect(nativeMock.intVariationDefaultValue).toHaveBeenNthCalledWith(1, 'key3', 0, 'default'); - expect(nativeMock.intVariationDefaultValue).toHaveBeenNthCalledWith(2, 'key4', 5, 'env2'); -}); + expect(nativeMock.boolVariation).toHaveBeenCalledTimes(3); + expect(nativeMock.boolVariation).toHaveBeenNthCalledWith(1, 'key1', true, 'default'); + expect(nativeMock.boolVariation).toHaveBeenNthCalledWith(2, 'key2', false, 'alt'); + expect(nativeMock.boolVariation).toHaveBeenNthCalledWith(3, 'key3', false, 'default'); + }); -test('floatVariation', () => { - client.floatVariation('key1'); - client.floatVariation('key2', undefined, 'env1'); - client.floatVariation('key3', 1.5); - client.floatVariation('key4', 5.5, 'env2'); + test('detailed calls native', async () => { + nativeMock.boolVariationDetail.mockImplementation((k, def, env) => Promise.resolve(!def)); - expect(nativeMock.floatVariation).toHaveBeenCalledTimes(2); - expect(nativeMock.floatVariation).toHaveBeenNthCalledWith(1, 'key1', 'default'); - expect(nativeMock.floatVariation).toHaveBeenNthCalledWith(2, 'key2', 'env1'); + await expect(client.boolVariationDetail('key1', true)).resolves.toEqual(false); + await expect(client.boolVariationDetail('key2', false, 'alt')).resolves.toEqual(true); + await expect(client.boolVariationDetail('key3', false, 5)).resolves.toEqual(true); - expect(nativeMock.floatVariationDefaultValue).toHaveBeenCalledTimes(2); - expect(nativeMock.floatVariationDefaultValue).toHaveBeenNthCalledWith(1, 'key3', 1.5, 'default'); - expect(nativeMock.floatVariationDefaultValue).toHaveBeenNthCalledWith(2, 'key4', 5.5, 'env2'); + expect(nativeMock.boolVariationDetail).toHaveBeenCalledTimes(3); + expect(nativeMock.boolVariationDetail).toHaveBeenNthCalledWith(1, 'key1', true, 'default'); + expect(nativeMock.boolVariationDetail).toHaveBeenNthCalledWith(2, 'key2', false, 'alt'); + expect(nativeMock.boolVariationDetail).toHaveBeenNthCalledWith(3, 'key3', false, 'default'); + }); }); -test('stringVariation', () => { - client.stringVariation('key1'); - client.stringVariation('key2', undefined, 'env1'); - client.stringVariation('key3', ''); - client.stringVariation('key4', 'abc', 'env2'); +describe('numberVariation', () => { + test('validates defaultValue', async () => { + expect.assertions(13); + + await expect(client.numberVariation('flagKey')).rejects.toEqual(defValErr); + await expect(client.numberVariation('flagKey', null)).rejects.toEqual(defValErr); + await expect(client.numberVariation('flagKey', false)).rejects.toEqual(defValErr); + await expect(client.numberVariation('flagKey', NaN)).rejects.toEqual(defValErr); + await expect(client.numberVariationDetail('flagKey')).rejects.toEqual(defValErr); + await expect(client.numberVariationDetail('flagKey', null)).rejects.toEqual(defValErr); + await expect(client.numberVariationDetail('flagKey', false)).rejects.toEqual(defValErr); + await expect(client.numberVariationDetail('flagKey', NaN)).rejects.toEqual(defValErr); + + expect(nativeMock.numberVariation).toHaveBeenCalledTimes(0); + expect(nativeMock.numberVariationDetail).toHaveBeenCalledTimes(0); + }); - expect(nativeMock.stringVariation).toHaveBeenCalledTimes(2); - expect(nativeMock.stringVariation).toHaveBeenNthCalledWith(1, 'key1', 'default'); - expect(nativeMock.stringVariation).toHaveBeenNthCalledWith(2, 'key2', 'env1'); + test('calls native', async () => { + nativeMock.numberVariation.mockImplementation((k, def, env) => Promise.resolve(def + 1.5)); - expect(nativeMock.stringVariationDefaultValue).toHaveBeenCalledTimes(2); - expect(nativeMock.stringVariationDefaultValue).toHaveBeenNthCalledWith(1, 'key3', '', 'default'); - expect(nativeMock.stringVariationDefaultValue).toHaveBeenNthCalledWith(2, 'key4', 'abc', 'env2'); -}); + await expect(client.numberVariation('key1', 0)).resolves.toEqual(1.5); + await expect(client.numberVariation('key2', 5, 'alt')).resolves.toEqual(6.5); + await expect(client.numberVariation('key3', 2.5, 5)).resolves.toEqual(4); -test('boolVariationDetail', () => { - client.boolVariationDetail('key1'); - client.boolVariationDetail('key2', undefined, 'env1'); - client.boolVariationDetail('key3', true); - client.boolVariationDetail('key4', false, 'env2'); + expect(nativeMock.numberVariation).toHaveBeenCalledTimes(3); + expect(nativeMock.numberVariation).toHaveBeenNthCalledWith(1, 'key1', 0, 'default'); + expect(nativeMock.numberVariation).toHaveBeenNthCalledWith(2, 'key2', 5, 'alt'); + expect(nativeMock.numberVariation).toHaveBeenNthCalledWith(3, 'key3', 2.5, 'default'); + }); + + test('detailed calls native', async () => { + nativeMock.numberVariationDetail.mockImplementation((k, def, env) => Promise.resolve(def + 1.5)); - expect(nativeMock.boolVariationDetail).toHaveBeenCalledTimes(2); - expect(nativeMock.boolVariationDetail).toHaveBeenNthCalledWith(1, 'key1', 'default'); - expect(nativeMock.boolVariationDetail).toHaveBeenNthCalledWith(2, 'key2', 'env1'); + await expect(client.numberVariationDetail('key1', 0)).resolves.toEqual(1.5); + await expect(client.numberVariationDetail('key2', 5, 'alt')).resolves.toEqual(6.5); + await expect(client.numberVariationDetail('key3', 2.5, 5)).resolves.toEqual(4); - expect(nativeMock.boolVariationDetailDefaultValue).toHaveBeenCalledTimes(2); - expect(nativeMock.boolVariationDetailDefaultValue).toHaveBeenNthCalledWith(1, 'key3', true, 'default'); - expect(nativeMock.boolVariationDetailDefaultValue).toHaveBeenNthCalledWith(2, 'key4', false, 'env2'); + expect(nativeMock.numberVariationDetail).toHaveBeenCalledTimes(3); + expect(nativeMock.numberVariationDetail).toHaveBeenNthCalledWith(1, 'key1', 0, 'default'); + expect(nativeMock.numberVariationDetail).toHaveBeenNthCalledWith(2, 'key2', 5, 'alt'); + expect(nativeMock.numberVariationDetail).toHaveBeenNthCalledWith(3, 'key3', 2.5, 'default'); + }); }); -test('intVariationDetail', () => { - client.intVariationDetail('key1'); - client.intVariationDetail('key2', undefined, 'env1'); - client.intVariationDetail('key3', 0); - client.intVariationDetail('key4', 5, 'env2'); +describe('stringVariation', () => { + test('validates defaultValue', async () => { + expect.assertions(7); - expect(nativeMock.intVariationDetail).toHaveBeenCalledTimes(2); - expect(nativeMock.intVariationDetail).toHaveBeenNthCalledWith(1, 'key1', 'default'); - expect(nativeMock.intVariationDetail).toHaveBeenNthCalledWith(2, 'key2', 'env1'); + await expect(client.stringVariation('flagKey', false)).rejects.toEqual(defValErr); + await expect(client.stringVariationDetail('flagKey', false)).rejects.toEqual(defValErr); - expect(nativeMock.intVariationDetailDefaultValue).toHaveBeenCalledTimes(2); - expect(nativeMock.intVariationDetailDefaultValue).toHaveBeenNthCalledWith(1, 'key3', 0, 'default'); - expect(nativeMock.intVariationDetailDefaultValue).toHaveBeenNthCalledWith(2, 'key4', 5, 'env2'); -}); + expect(nativeMock.stringVariation).toHaveBeenCalledTimes(0); + expect(nativeMock.stringVariationDetail).toHaveBeenCalledTimes(0); + }); -test('floatVariationDetail', () => { - client.floatVariationDetail('key1'); - client.floatVariationDetail('key2', undefined, 'env1'); - client.floatVariationDetail('key3', 1.5); - client.floatVariationDetail('key4', 5.5, 'env2'); + test('calls native', async () => { + nativeMock.stringVariation.mockImplementation((k, def, env) => Promise.resolve('foo'.concat(def))); - expect(nativeMock.floatVariationDetail).toHaveBeenCalledTimes(2); - expect(nativeMock.floatVariationDetail).toHaveBeenNthCalledWith(1, 'key1', 'default'); - expect(nativeMock.floatVariationDetail).toHaveBeenNthCalledWith(2, 'key2', 'env1'); + await expect(client.stringVariation('key1', '1')).resolves.toEqual('foo1'); + await expect(client.stringVariation('key2', null, 'alt')).resolves.toEqual('foonull'); + await expect(client.stringVariation('key3', undefined, 5)).resolves.toEqual('foonull'); - expect(nativeMock.floatVariationDetailDefaultValue).toHaveBeenCalledTimes(2); - expect(nativeMock.floatVariationDetailDefaultValue).toHaveBeenNthCalledWith(1, 'key3', 1.5, 'default'); - expect(nativeMock.floatVariationDetailDefaultValue).toHaveBeenNthCalledWith(2, 'key4', 5.5, 'env2'); -}); + expect(nativeMock.stringVariation).toHaveBeenCalledTimes(3); + expect(nativeMock.stringVariation).toHaveBeenNthCalledWith(1, 'key1', '1', 'default'); + expect(nativeMock.stringVariation).toHaveBeenNthCalledWith(2, 'key2', null, 'alt'); + expect(nativeMock.stringVariation).toHaveBeenNthCalledWith(3, 'key3', null, 'default'); + }); -test('stringVariationDetail', () => { - client.stringVariationDetail('key1'); - client.stringVariationDetail('key2', undefined, 'env1'); - client.stringVariationDetail('key3', ''); - client.stringVariationDetail('key4', 'abc', 'env2'); + test('detailed calls native', async () => { + nativeMock.stringVariationDetail.mockImplementation((k, def, env) => Promise.resolve('foo'.concat(def))); - expect(nativeMock.stringVariationDetail).toHaveBeenCalledTimes(2); - expect(nativeMock.stringVariationDetail).toHaveBeenNthCalledWith(1, 'key1', 'default'); - expect(nativeMock.stringVariationDetail).toHaveBeenNthCalledWith(2, 'key2', 'env1'); + await expect(client.stringVariationDetail('key1', '1')).resolves.toEqual('foo1'); + await expect(client.stringVariationDetail('key2', null, 'alt')).resolves.toEqual('foonull'); + await expect(client.stringVariationDetail('key3', undefined, 5)).resolves.toEqual('foonull'); - expect(nativeMock.stringVariationDetailDefaultValue).toHaveBeenCalledTimes(2); - expect(nativeMock.stringVariationDetailDefaultValue).toHaveBeenNthCalledWith(1, 'key3', '', 'default'); - expect(nativeMock.stringVariationDetailDefaultValue).toHaveBeenNthCalledWith(2, 'key4', 'abc', 'env2'); + expect(nativeMock.stringVariationDetail).toHaveBeenCalledTimes(3); + expect(nativeMock.stringVariationDetail).toHaveBeenNthCalledWith(1, 'key1', '1', 'default'); + expect(nativeMock.stringVariationDetail).toHaveBeenNthCalledWith(2, 'key2', null, 'alt'); + expect(nativeMock.stringVariationDetail).toHaveBeenNthCalledWith(3, 'key3', null, 'default'); + }); }); test('allFlags', () => { diff --git a/ios/LaunchdarklyReactNativeClient.swift b/ios/LaunchdarklyReactNativeClient.swift index 93d89e2..77f13e6 100644 --- a/ios/LaunchdarklyReactNativeClient.swift +++ b/ios/LaunchdarklyReactNativeClient.swift @@ -206,42 +206,18 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { return user } - @objc func boolVariationDefaultValue(_ flagKey: String, defaultValue: ObjCBool, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + @objc func boolVariation(_ flagKey: String, defaultValue: ObjCBool, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { resolve(LDClient.get(environment: environment)!.variation(forKey: flagKey, defaultValue: defaultValue.boolValue) as Bool) } - @objc func intVariationDefaultValue(_ flagKey: String, defaultValue: Int, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.get(environment: environment)!.variation(forKey: flagKey, defaultValue: defaultValue) as Int) - } - - @objc func floatVariationDefaultValue(_ flagKey: String, defaultValue: CGFloat, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + @objc func numberVariation(_ flagKey: String, defaultValue: Double, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { resolve(LDClient.get(environment: environment)!.variation(forKey: flagKey, defaultValue: Double(defaultValue)) as Double) } - @objc func stringVariationDefaultValue(_ flagKey: String, defaultValue: String, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + @objc func stringVariation(_ flagKey: String, defaultValue: String, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { resolve(LDClient.get(environment: environment)!.variation(forKey: flagKey, defaultValue: defaultValue) as String) } - @objc func boolVariation(_ flagKey: String, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let boolFlagValue: Bool? = LDClient.get(environment: environment)!.variation(forKey: flagKey) - resolve(boolFlagValue) - } - - @objc func intVariation(_ flagKey: String, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let intFlagValue: Int? = LDClient.get(environment: environment)!.variation(forKey: flagKey) - resolve(intFlagValue) - } - - @objc func floatVariation(_ flagKey: String, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let floatFlagValue: Double? = LDClient.get(environment: environment)!.variation(forKey: flagKey) - resolve(floatFlagValue) - } - - @objc func stringVariation(_ flagKey: String, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let stringFlagValue: String? = LDClient.get(environment: environment)!.variation(forKey: flagKey) - resolve(stringFlagValue) - } - @objc func jsonVariationNone(_ flagKey: String, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { let jsonFlagValue: Dictionary? = LDClient.get(environment: environment)!.variation(forKey: flagKey) resolve(jsonFlagValue) @@ -267,7 +243,7 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { resolve(LDClient.get(environment: environment)!.variation(forKey: flagKey, defaultValue: defaultValue.swiftDictionary) as NSDictionary) } - @objc func boolVariationDetailDefaultValue(_ flagKey: String, defaultValue: ObjCBool, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + @objc func boolVariationDetail(_ flagKey: String, defaultValue: ObjCBool, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { let detail = LDClient.get(environment: environment)!.variationDetail(forKey: flagKey, defaultValue: defaultValue.boolValue) let jsonObject: NSDictionary = [ "value": (detail.value as Any), @@ -277,7 +253,7 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { resolve(jsonObject) } - @objc func intVariationDetailDefaultValue(_ flagKey: String, defaultValue: Int, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + @objc func numberVariationDetail(_ flagKey: String, defaultValue: Double, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { let detail = LDClient.get(environment: environment)!.variationDetail(forKey: flagKey, defaultValue: defaultValue) let jsonObject: NSDictionary = [ "value": (detail.value as Any), @@ -287,17 +263,7 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { resolve(jsonObject) } - @objc func floatVariationDetailDefaultValue(_ flagKey: String, defaultValue: CGFloat, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail = LDClient.get(environment: environment)!.variationDetail(forKey: flagKey, defaultValue: Double(defaultValue)) - let jsonObject: NSDictionary = [ - "value": (detail.value as Any), - "variationIndex": (detail.variationIndex as Any), - "reason": (detail.reason as Any) - ] - resolve(jsonObject) - } - - @objc func stringVariationDetailDefaultValue(_ flagKey: String, defaultValue: String, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + @objc func stringVariationDetail(_ flagKey: String, defaultValue: String, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { let detail = LDClient.get(environment: environment)!.variationDetail(forKey: flagKey, defaultValue: defaultValue) let jsonObject: NSDictionary = [ "value": (detail.value as Any), @@ -307,46 +273,6 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { resolve(jsonObject) } - @objc func boolVariationDetail(_ flagKey: String, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail: LDEvaluationDetail = LDClient.get(environment: environment)!.variationDetail(forKey: flagKey) - let jsonObject: NSDictionary = [ - "value": (detail.value as Any), - "variationIndex": (detail.variationIndex as Any), - "reason": (detail.reason as Any) - ] - resolve(jsonObject) - } - - @objc func intVariationDetail(_ flagKey: String, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail: LDEvaluationDetail = LDClient.get(environment: environment)!.variationDetail(forKey: flagKey) - let jsonObject: NSDictionary = [ - "value": (detail.value as Any), - "variationIndex": (detail.variationIndex as Any), - "reason": (detail.reason as Any) - ] - resolve(jsonObject) - } - - @objc func floatVariationDetail(_ flagKey: String, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail: LDEvaluationDetail = LDClient.get(environment: environment)!.variationDetail(forKey: flagKey) - let jsonObject: NSDictionary = [ - "value": (detail.value as Any), - "variationIndex": (detail.variationIndex as Any), - "reason": (detail.reason as Any) - ] - resolve(jsonObject) - } - - @objc func stringVariationDetail(_ flagKey: String, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let detail: LDEvaluationDetail = LDClient.get(environment: environment)!.variationDetail(forKey: flagKey) - let jsonObject: NSDictionary = [ - "value": (detail.value as Any), - "variationIndex": (detail.variationIndex as Any), - "reason": (detail.reason as Any) - ] - resolve(jsonObject) - } - @objc func jsonVariationDetailNone(_ flagKey: String, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { let detail: LDEvaluationDetail?> = LDClient.get(environment: environment)!.variationDetail(forKey: flagKey) let jsonObject: NSDictionary = [ diff --git a/ios/LaunchdarklyReactNativeClientBridge.m b/ios/LaunchdarklyReactNativeClientBridge.m index 97a00d0..5ddb17e 100644 --- a/ios/LaunchdarklyReactNativeClientBridge.m +++ b/ios/LaunchdarklyReactNativeClientBridge.m @@ -7,21 +7,11 @@ @interface RCT_EXTERN_MODULE(LaunchdarklyReactNativeClient, RCTEventEmitter) RCT_EXTERN_METHOD(configureWithTimeout:(NSDictionary *)config user:(NSDictionary *)user timeout:(NSInteger *)timeout resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(boolVariationDefaultValue:(NSString *)flagKey defaultValue:(BOOL *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(boolVariation:(NSString *)flagKey defaultValue:(BOOL *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(intVariationDefaultValue:(NSString *)flagKey defaultValue:(NSInteger *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(numberVariation:(NSString *)flagKey defaultValue:(Double *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(floatVariationDefaultValue:(NSString *)flagKey defaultValue:(CGFloat *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) - -RCT_EXTERN_METHOD(stringVariationDefaultValue:(NSString *)flagKey defaultValue:(NSString *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) - -RCT_EXTERN_METHOD(boolVariation:(NSString *)flagKey environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) - -RCT_EXTERN_METHOD(intVariation:(NSString *)flagKey environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) - -RCT_EXTERN_METHOD(floatVariation:(NSString *)flagKey environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) - -RCT_EXTERN_METHOD(stringVariation:(NSString *)flagKey environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(stringVariation:(NSString *)flagKey defaultValue:(NSString *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) RCT_EXTERN_METHOD(jsonVariationNone:(NSString *)flagKey environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) @@ -35,21 +25,11 @@ @interface RCT_EXTERN_MODULE(LaunchdarklyReactNativeClient, RCTEventEmitter) RCT_EXTERN_METHOD(jsonVariationObject:(NSString *)flagKey defaultValue:(NSDictionary *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(boolVariationDetailDefaultValue:(NSString *)flagKey defaultValue:(BOOL *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) - -RCT_EXTERN_METHOD(intVariationDetailDefaultValue:(NSString *)flagKey defaultValue:(NSInteger *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) - -RCT_EXTERN_METHOD(floatVariationDetailDefaultValue:(NSString *)flagKey defaultValue:(CGFloat *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) - -RCT_EXTERN_METHOD(stringVariationDetailDefaultValue:(NSString *)flagKey defaultValue:(NSString *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) - -RCT_EXTERN_METHOD(boolVariationDetail:(NSString *)flagKey environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) - -RCT_EXTERN_METHOD(intVariationDetail:(NSString *)flagKey environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(boolVariationDetail:(NSString *)flagKey defaultValue:(BOOL *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(floatVariationDetail:(NSString *)flagKey environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(numberVariationDetail:(NSString *)flagKey defaultValue:(Double *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(stringVariationDetail:(NSString *)flagKey environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(stringVariationDetail:(NSString *)flagKey defaultValue:(NSString *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) RCT_EXTERN_METHOD(jsonVariationDetailNone:(NSString *)flagKey environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) diff --git a/test-types.ts b/test-types.ts index 94b3b69..373c661 100644 --- a/test-types.ts +++ b/test-types.ts @@ -70,27 +70,23 @@ async function tests() { const identify: null = await client.identify(user); const boolFlagValue: boolean = await client.boolVariation('key', false); - const intFlagValue: number = await client.intVariation('key', 2); - const floatFlagValue: number = await client.floatVariation('key', 2.3); + const floatFlagValue: number = await client.numberVariation('key', 2.3); const stringFlagValue: string = await client.stringVariation('key', 'default'); const jsonFlagValue: Record = await client.jsonVariation('key', jsonObj); const boolDetail: LDEvaluationDetail = await client.boolVariationDetail('key', false); - const intDetail: LDEvaluationDetail = await client.intVariationDetail('key', 2); - const floatDetail: LDEvaluationDetail = await client.floatVariationDetail('key', 2.3); + const floatDetail: LDEvaluationDetail = await client.numberVariationDetail('key', 2.3); const stringDetail: LDEvaluationDetail = await client.stringVariationDetail('key', 'default'); const jsonDetail: LDEvaluationDetail> = await client.jsonVariationDetail('key', jsonObj); const boolDetailMulti: LDEvaluationDetail = await client.boolVariationDetail('key', false, 'test'); - const intDetailMulti: LDEvaluationDetail = await client.intVariationDetail('key', 2, 'test'); - const floatDetailMulti: LDEvaluationDetail = await client.floatVariationDetail('key', 2.3, 'test'); + const floatDetailMulti: LDEvaluationDetail = await client.numberVariationDetail('key', 2.3, 'test'); const stringDetailMulti: LDEvaluationDetail = await client.stringVariationDetail('key', 'default', 'test'); const jsonDetailMulti: LDEvaluationDetail> = await client.jsonVariationDetail('key', jsonObj, 'test'); const detailIndex: number | undefined = boolDetail.variationIndex; const detailReason: LDEvaluationReason = boolDetail.reason; const detailBoolValue: boolean = boolDetail.value; - const detailIntValue: number = intDetail.value; const detailFloatValue: number = floatDetail.value; const detailStringValue: string = stringDetail.value; const detailJsonValue: Record = jsonDetail.value; @@ -132,4 +128,4 @@ async function tests() { const version: String = client.getVersion(); }; -tests(); \ No newline at end of file +tests(); From 0364d511feb401aa4f17df87fbc38eb7464cedc4 Mon Sep 17 00:00:00 2001 From: Gavin Whelan Date: Thu, 1 Jul 2021 13:02:20 -0700 Subject: [PATCH 21/32] Safer and cleaner configuration on iOS (#87) --- ios/LaunchdarklyReactNativeClient.swift | 191 +++++++----------------- 1 file changed, 50 insertions(+), 141 deletions(-) diff --git a/ios/LaunchdarklyReactNativeClient.swift b/ios/LaunchdarklyReactNativeClient.swift index 77f13e6..9b2acf2 100644 --- a/ios/LaunchdarklyReactNativeClient.swift +++ b/ios/LaunchdarklyReactNativeClient.swift @@ -1,4 +1,3 @@ - import Foundation import LaunchDarkly @@ -54,155 +53,65 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { } } } - - private func configBuild(config: NSDictionary) -> LDConfig? { - let mobileKey = config["mobileKey"] - - if mobileKey == nil || !(mobileKey is String) { - return nil - } - - let safeKey = mobileKey as! String - var ldConfig = LDConfig(mobileKey: safeKey) - - if config["pollUri"] != nil { - ldConfig.baseUrl = URL.init(string: config["pollUri"] as! String)! - } - - if config["eventsUri"] != nil { - ldConfig.eventsUrl = URL.init(string: config["eventsUri"] as! String)! - } - - if config["streamUri"] != nil { - ldConfig.streamUrl = URL.init(string: config["streamUri"] as! String)! - } - - if config["eventsCapacity"] != nil { - ldConfig.eventCapacity = config["eventsCapacity"] as! Int - } - - if config["eventsFlushIntervalMillis"] != nil { - ldConfig.eventFlushInterval = TimeInterval(config["eventsFlushIntervalMillis"] as! Float / 1000) - } - - if config["connectionTimeoutMillis"] != nil { - ldConfig.connectionTimeout = TimeInterval(config["connectionTimeoutMillis"] as! Float / 1000) - } - - if config["pollingIntervalMillis"] != nil { - ldConfig.flagPollingInterval = TimeInterval(config["pollingIntervalMillis"] as! Float / 1000) - } - - if config["backgroundPollingIntervalMillis"] != nil { - ldConfig.backgroundFlagPollingInterval = TimeInterval(config["backgroundPollingIntervalMillis"] as! Float / 1000) - } - - if config["useReport"] != nil { - ldConfig.useReport = config["useReport"] as! Bool - } - - if config["stream"] != nil { - ldConfig.streamingMode = (config["stream"] as! Bool) ? LDStreamingMode.streaming : LDStreamingMode.polling - } - - if config["disableBackgroundUpdating"] != nil { - ldConfig.enableBackgroundUpdates = !(config["disableBackgroundUpdating"] as! Bool) - } - - if config["offline"] != nil { - ldConfig.startOnline = !(config["offline"] as! Bool) - } - - if config["debugMode"] != nil { - ldConfig.isDebugMode = config["debugMode"] as! Bool - } - - if config["evaluationReasons"] != nil { - ldConfig.evaluationReasons = config["evaluationReasons"] as! Bool - } - - ldConfig.wrapperName = config["wrapperName"] as? String - ldConfig.wrapperVersion = config["wrapperVersion"] as? String - - if config["maxCachedUsers"] != nil { - ldConfig.maxCachedUsers = config["maxCachedUsers"] as! Int - } - if config["diagnosticOptOut"] != nil { - ldConfig.diagnosticOptOut = config["diagnosticOptOut"] as! Bool + private func id(_ x: T) -> T { x } + private func millis(_ x: NSNumber) -> TimeInterval { TimeInterval(x.doubleValue / 1_000) } + private func url(_ x: String) -> URL { URL.init(string: x)! } + private func configField(_ field: inout T, _ value: Any?, _ transform: ((V) -> T?)) { + if let val = value as? V, let res = transform(val) { + field = res } - - if config["diagnosticRecordingIntervalMillis"] != nil { - ldConfig.diagnosticRecordingInterval = TimeInterval(config["diagnosticRecordingIntervalMillis"] as! Float / 1000) - } - - if config["secondaryMobileKeys"] != nil { - try! ldConfig.setSecondaryMobileKeys(config["secondaryMobileKeys"] as! [String: String]) - } - - if config["allUserAttributesPrivate"] != nil { - ldConfig.allUserAttributesPrivate = config["allUserAttributesPrivate"] as! Bool + } + + private func configBuild(config: NSDictionary) -> LDConfig? { + guard let mobileKey = config["mobileKey"] as? String + else { return nil } + + var ldConfig = LDConfig(mobileKey: mobileKey) + configField(&ldConfig.baseUrl, config["pollUri"], url) + configField(&ldConfig.eventsUrl, config["eventsUri"], url) + configField(&ldConfig.streamUrl, config["streamUri"], url) + configField(&ldConfig.eventCapacity, config["eventsCapacity"], { (x: NSNumber) in x.intValue }) + configField(&ldConfig.eventFlushInterval, config["eventsFlushIntervalMillis"], millis) + configField(&ldConfig.connectionTimeout, config["connectionTimeoutMillis"], millis) + configField(&ldConfig.flagPollingInterval, config["pollingIntervalMillis"], millis) + configField(&ldConfig.backgroundFlagPollingInterval, config["backgroundPollingIntervalMillis"], millis) + configField(&ldConfig.useReport, config["useReport"], id) + configField(&ldConfig.streamingMode, config["stream"], { $0 ? .streaming : .polling }) + configField(&ldConfig.enableBackgroundUpdates, config["disableBackgroundUpdating"], { !$0 }) + configField(&ldConfig.startOnline, config["offline"], { !$0 }) + configField(&ldConfig.isDebugMode, config["debugMode"], id) + configField(&ldConfig.evaluationReasons, config["evaluationReasons"], id) + configField(&ldConfig.wrapperName, config["wrapperName"], id) + configField(&ldConfig.wrapperVersion, config["wrapperVersion"], id) + configField(&ldConfig.maxCachedUsers, config["maxCachedUsers"], { (x: NSNumber) in x.intValue }) + configField(&ldConfig.diagnosticOptOut, config["diagnosticOptOut"], id) + configField(&ldConfig.diagnosticRecordingInterval, config["diagnosticRecordingIntervalMillis"], millis) + configField(&ldConfig.allUserAttributesPrivate, config["allUserAttributesPrivate"], id) + configField(&ldConfig.autoAliasingOptOut, config["autoAliasingOptOut"], id) + + if let val = config["secondaryMobileKeys"] as? [String: String] { + try! ldConfig.setSecondaryMobileKeys(val) } - if config["autoAliasingOptOut"] != nil { - ldConfig.autoAliasingOptOut = config["autoAliasingOptOut"] as! Bool - } - return ldConfig } private func userBuild(userDict: NSDictionary) -> LDUser? { guard let userKey = userDict["key"] as? String - else { - return nil - } - - var user = LDUser(key: userKey) - - if userDict["secondary"] != nil { - user.secondary = userDict["secondary"] as? String - } - - if userDict["name"] != nil { - user.name = userDict["name"] as? String - } - - if userDict["firstName"] != nil { - user.firstName = userDict["firstName"] as? String - } - - if userDict["lastName"] != nil { - user.lastName = userDict["lastName"] as? String - } - - if userDict["email"] != nil { - user.email = userDict["email"] as? String - } - - if userDict["anonymous"] != nil { - user.isAnonymous = userDict["anonymous"] as! Bool - } - - if userDict["country"] != nil { - user.country = userDict["country"] as? String - } - - if userDict["ip"] != nil { - user.ipAddress = userDict["ip"] as? String - } - - if userDict["avatar"] != nil { - user.avatar = userDict["avatar"] as? String - } - - if userDict["privateAttributeNames"] != nil { - user.privateAttributes = userDict["privateAttributeNames"] as? [String] - } - - if let customAttributes = userDict["custom"] as! [String: Any]? { - user.custom = customAttributes - } - + else { return nil } + + var user = LDUser(key: userKey, isAnonymous: userDict["anonymous"] as? Bool) + user.secondary = userDict["secondary"] as? String + user.name = userDict["name"] as? String + user.firstName = userDict["firstName"] as? String + user.lastName = userDict["lastName"] as? String + user.email = userDict["email"] as? String + user.country = userDict["country"] as? String + user.ipAddress = userDict["ip"] as? String + user.avatar = userDict["avatar"] as? String + user.privateAttributes = userDict["privateAttributeNames"] as? [String] + user.custom = userDict["custom"] as? [String: Any] return user } From eb1aecbd3ba2921620889c7442c661f33c840d06 Mon Sep 17 00:00:00 2001 From: Gavin Whelan Date: Thu, 1 Jul 2021 13:02:58 -0700 Subject: [PATCH 22/32] Cleanup Android variation implementation (#88) --- android/build.gradle | 5 +- .../LaunchdarklyReactNativeClientModule.java | 230 ++++++------------ index.js | 4 - 3 files changed, 80 insertions(+), 159 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 097fc57..9a2118e 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,4 +1,3 @@ - buildscript { ext { buildToolsVersion = "28.0.2" @@ -32,6 +31,10 @@ android { lintOptions { abortOnError false } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } allprojects { diff --git a/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java b/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java index 0468f65..31af933 100644 --- a/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java +++ b/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java @@ -3,6 +3,8 @@ import android.app.Application; import android.net.Uri; +import androidx.arch.core.util.Function; + import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Dynamic; import com.facebook.react.bridge.Promise; @@ -306,210 +308,149 @@ private LDUser.Builder userBuild(ReadableMap options) { @ReactMethod public void boolVariation(String flagKey, boolean defaultValue, String environment, Promise promise) { - try { - promise.resolve(LDClient.getForMobileKey(environment).boolVariation(flagKey, defaultValue)); - } catch (Exception e) { - promise.resolve(defaultValue); - } + variation(LDClient::boolVariation, LDValue::of, flagKey, defaultValue, environment, promise); } @ReactMethod - public void floatVariation(String flagKey, double defaultValue, String environment, Promise promise) { - try { - promise.resolve(LDClient.getForMobileKey(environment).doubleVariation(flagKey, defaultValue)); - } catch (Exception e) { - promise.resolve(defaultValue); - } + public void numberVariation(String flagKey, double defaultValue, String environment, Promise promise) { + variation(LDClient::doubleVariation, LDValue::of, flagKey, defaultValue, environment, promise); } @ReactMethod public void stringVariation(String flagKey, String defaultValue, String environment, Promise promise) { - try { - promise.resolve(LDClient.getForMobileKey(environment).stringVariation(flagKey, defaultValue)); - } catch (Exception e) { - promise.resolve(defaultValue); - } + variation(LDClient::stringVariation, LDValue::of, flagKey, defaultValue, environment, promise); } @ReactMethod public void jsonVariationNone(String flagKey, String environment, Promise promise) { - jsonVariationBase(flagKey, LDValue.ofNull(), environment, promise); + variation(LDClient::jsonValueVariation, id -> id, flagKey, LDValue.ofNull(), environment, promise); } @ReactMethod public void jsonVariationNumber(String flagKey, double defaultValue, String environment, Promise promise) { - jsonVariationBase(flagKey, LDValue.of(defaultValue), environment, promise); + variation(LDClient::jsonValueVariation, id -> id, flagKey, LDValue.of(defaultValue), environment, promise); } @ReactMethod public void jsonVariationBool(String flagKey, boolean defaultValue, String environment, Promise promise) { - jsonVariationBase(flagKey, LDValue.of(defaultValue), environment, promise); + variation(LDClient::jsonValueVariation, id -> id, flagKey, LDValue.of(defaultValue), environment, promise); } @ReactMethod public void jsonVariationString(String flagKey, String defaultValue, String environment, Promise promise) { - jsonVariationBase(flagKey, LDValue.of(defaultValue), environment, promise); + variation(LDClient::jsonValueVariation, id -> id, flagKey, LDValue.of(defaultValue), environment, promise); } @ReactMethod public void jsonVariationArray(String flagKey, ReadableArray defaultValue, String environment, Promise promise) { - jsonVariationBase(flagKey, toLDValue(defaultValue), environment, promise); + variation(LDClient::jsonValueVariation, id -> id, flagKey, toLDValue(defaultValue), environment, promise); } @ReactMethod public void jsonVariationObject(String flagKey, ReadableMap defaultValue, String environment, Promise promise) { - jsonVariationBase(flagKey, toLDValue(defaultValue), environment, promise); + variation(LDClient::jsonValueVariation, id -> id, flagKey, toLDValue(defaultValue), environment, promise); } - @ReactMethod - public void boolVariationDetail(String flagKey, boolean defaultValue, String environment, Promise promise) { - EvaluationDetail detailResult; + interface EvalCall { + T call(LDClient client, String flagKey, T defaultValue); + } + + private void variation(EvalCall eval, Function transform, + String flagKey, T defaultValue, String environment, Promise promise) { try { - detailResult = LDClient.getForMobileKey(environment).boolVariationDetail(flagKey, defaultValue); + promise.resolve(ldValueToBridge(transform.apply(eval.call(LDClient.getForMobileKey(environment), flagKey, defaultValue)))); } catch (Exception e) { - Timber.w(e); - detailResult = EvaluationDetail.fromValue(defaultValue, EvaluationDetail.NO_VARIATION, EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION)); + promise.resolve(ldValueToBridge(transform.apply(defaultValue))); } - WritableMap result = detailToMap(detailResult); - if (detailResult.getValue() == null) { - result.putNull("value"); - } else { - result.putBoolean("value", detailResult.getValue()); - } - promise.resolve(result); + } + + @ReactMethod + public void boolVariationDetail(String flagKey, boolean defaultValue, String environment, Promise promise) { + detailVariation(LDClient::boolVariationDetail, LDValue::of, flagKey, defaultValue, environment, promise); } @ReactMethod public void numberVariationDetail(String flagKey, double defaultValue, String environment, Promise promise) { - EvaluationDetail detailResult; - try { - detailResult = LDClient.getForMobileKey(environment).doubleVariationDetail(flagKey, defaultValue); - } catch (Exception e) { - Timber.w(e); - detailResult = EvaluationDetail.fromValue(defaultValue, EvaluationDetail.NO_VARIATION, EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION)); - } - WritableMap result = detailToMap(detailResult); - if (detailResult.getValue() == null) { - result.putNull("value"); - } else { - result.putDouble("value", detailResult.getValue()); - } - promise.resolve(result); + detailVariation(LDClient::doubleVariationDetail, LDValue::of, flagKey, defaultValue, environment, promise); } @ReactMethod public void stringVariationDetail(String flagKey, String defaultValue, String environment, Promise promise) { - EvaluationDetail detailResult; - try { - detailResult = LDClient.getForMobileKey(environment).stringVariationDetail(flagKey, defaultValue); - } catch (Exception e) { - Timber.w(e); - detailResult = EvaluationDetail.fromValue(defaultValue, EvaluationDetail.NO_VARIATION, EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION)); - } - WritableMap result = detailToMap(detailResult); - if (detailResult.getValue() == null) { - result.putNull("value"); - } else { - result.putString("value", detailResult.getValue()); - } - promise.resolve(result); + detailVariation(LDClient::stringVariationDetail, LDValue::of, flagKey, defaultValue, environment, promise); } @ReactMethod public void jsonVariationDetailNone(String flagKey, String environment, Promise promise) { - jsonVariationDetailBase(flagKey, LDValue.ofNull(), environment, promise); + detailVariation(LDClient::jsonValueVariationDetail, id -> id, flagKey, LDValue.ofNull(), environment, promise); } @ReactMethod public void jsonVariationDetailNumber(String flagKey, double defaultValue, String environment, Promise promise) { - jsonVariationDetailBase(flagKey, LDValue.of(defaultValue), environment, promise); + detailVariation(LDClient::jsonValueVariationDetail, id -> id, flagKey, LDValue.of(defaultValue), environment, promise); } @ReactMethod public void jsonVariationDetailBool(String flagKey, boolean defaultValue, String environment, Promise promise) { - jsonVariationDetailBase(flagKey, LDValue.of(defaultValue), environment, promise); + detailVariation(LDClient::jsonValueVariationDetail, id -> id, flagKey, LDValue.of(defaultValue), environment, promise); } @ReactMethod public void jsonVariationDetailString(String flagKey, String defaultValue, String environment, Promise promise) { - jsonVariationDetailBase(flagKey, LDValue.of(defaultValue), environment, promise); + detailVariation(LDClient::jsonValueVariationDetail, id -> id, flagKey, LDValue.of(defaultValue), environment, promise); } @ReactMethod public void jsonVariationDetailArray(String flagKey, ReadableArray defaultValue, String environment, Promise promise) { - jsonVariationDetailBase(flagKey, toLDValue(defaultValue), environment, promise); + detailVariation(LDClient::jsonValueVariationDetail, id -> id, flagKey, toLDValue(defaultValue), environment, promise); } @ReactMethod public void jsonVariationDetailObject(String flagKey, ReadableMap defaultValue, String environment, Promise promise) { - jsonVariationDetailBase(flagKey, toLDValue(defaultValue), environment, promise); + detailVariation(LDClient::jsonValueVariationDetail, id -> id, flagKey, toLDValue(defaultValue), environment, promise); } - private void jsonVariationBase(String flagKey, LDValue defaultValue, String environment, Promise promise) { - try { - LDValue value = LDClient.getForMobileKey(environment).jsonValueVariation(flagKey, defaultValue); - resolveValue(promise, value); - } catch (Exception e) { - resolveValue(promise, defaultValue); - } + interface EvalDetailCall { + EvaluationDetail call(LDClient client, String flagKey, T defaultValue); } - private void jsonVariationDetailBase(String flagKey, LDValue defaultValue, String environment, Promise promise) { - EvaluationDetail detailResult; + private void detailVariation(EvalDetailCall eval, Function transform, + String flagKey, T defaultValue, String environment, Promise promise) { try { - detailResult = LDClient.getForMobileKey(environment).jsonValueVariationDetail(flagKey, defaultValue); + LDClient client = LDClient.getForMobileKey(environment); + EvaluationDetail detail = eval.call(client, flagKey, defaultValue); + ObjectBuilder resultBuilder = objectBuilderFromDetail(detail); + resultBuilder.put("value", transform.apply(detail.getValue())); + promise.resolve(ldValueToBridge(resultBuilder.build())); } catch (Exception e) { - Timber.w(e); - detailResult = EvaluationDetail.fromValue(defaultValue, EvaluationDetail.NO_VARIATION, EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION)); + ObjectBuilder resultBuilder = LDValue.buildObject(); + resultBuilder.put("kind", EvaluationReason.Kind.ERROR.name()); + resultBuilder.put("errorKind", EvaluationReason.ErrorKind.EXCEPTION.name()); + resultBuilder.put("value", transform.apply(defaultValue)); + promise.resolve(ldValueToBridge(resultBuilder.build())); } - resolveEvaluationDetailValue(promise, detailResult); } - private void resolveValue(Promise promise, LDValue value) { - switch (value.getType()) { - case NULL: promise.resolve(null); break; - case BOOLEAN: promise.resolve(value.booleanValue()); break; - case NUMBER: promise.resolve(value.doubleValue()); break; - case STRING: promise.resolve(value.stringValue()); break; - case ARRAY: promise.resolve(ldValueToArray(value)); break; - case OBJECT: promise.resolve(ldValueToMap(value)); break; - } - } - - private void resolveEvaluationDetailValue(Promise promise, EvaluationDetail detailValue) { - WritableMap result = detailToMap(detailValue); - switch (detailValue.getValue().getType()) { - case NULL: result.putNull("value"); break; - case BOOLEAN: result.putBoolean("value", detailValue.getValue().booleanValue()); break; - case NUMBER: result.putDouble("value", detailValue.getValue().doubleValue()); break; - case STRING: result.putString("value", detailValue.getValue().stringValue()); break; - case ARRAY: result.putArray("value", ldValueToArray(detailValue.getValue())); break; - case OBJECT: result.putMap("value", ldValueToMap(detailValue.getValue())); break; - } - promise.resolve(result); - } - - private WritableMap detailToMap(EvaluationDetail detail) { - EvaluationReason reason = detail.getReason(); - WritableMap result = new WritableNativeMap(); + private ObjectBuilder objectBuilderFromDetail(EvaluationDetail detail) { + ObjectBuilder resultMap = LDValue.buildObject(); if (!detail.isDefaultValue()) { - result.putInt("variationIndex", detail.getVariationIndex()); + resultMap.put("variationIndex", detail.getVariationIndex()); } - WritableMap reasonMap = new WritableNativeMap(); - reasonMap.putString("kind", detail.getReason().getKind().name()); + EvaluationReason reason = detail.getReason(); + ObjectBuilder reasonMap = LDValue.buildObject(); + reasonMap.put("kind", reason.getKind().name()); switch (reason.getKind()) { case RULE_MATCH: - reasonMap.putInt("ruleIndex", reason.getRuleIndex()); + reasonMap.put("ruleIndex", reason.getRuleIndex()); if (reason.getRuleId() != null) { - reasonMap.putString("ruleId", reason.getRuleId()); + reasonMap.put("ruleId", reason.getRuleId()); } break; - case PREREQUISITE_FAILED: reasonMap.putString("prerequisiteKey", reason.getPrerequisiteKey()); break; - case ERROR: reasonMap.putString("errorKind", reason.getErrorKind().name()); break; + case PREREQUISITE_FAILED: reasonMap.put("prerequisiteKey", reason.getPrerequisiteKey()); break; + case ERROR: reasonMap.put("errorKind", reason.getErrorKind().name()); break; default: break; } - result.putMap("reason", reasonMap); - return result; + resultMap.put("reason", reasonMap.build()); + return resultMap; } @ReactMethod @@ -522,20 +463,11 @@ public void allFlags(String environment, Promise promise) { } try { - Map flags = LDClient.getForMobileKey(environment).allFlags(); - - WritableMap response = new WritableNativeMap(); - for (Map.Entry entry : flags.entrySet()) { - switch (entry.getValue().getType()) { - case NULL: response.putNull(entry.getKey()); break; - case BOOLEAN: response.putBoolean(entry.getKey(), entry.getValue().booleanValue()); break; - case NUMBER: response.putDouble(entry.getKey(), entry.getValue().doubleValue()); break; - case STRING: response.putString(entry.getKey(), entry.getValue().stringValue()); break; - case ARRAY: response.putArray(entry.getKey(), ldValueToArray(entry.getValue())); break; - case OBJECT: response.putMap(entry.getKey(), ldValueToMap(entry.getValue())); break; - } + ObjectBuilder resultBuilder = LDValue.buildObject(); + for (Map.Entry entry : LDClient.getForMobileKey(environment).allFlags().entrySet()) { + resultBuilder.put(entry.getKey(), entry.getValue()); } - promise.resolve(response); + promise.resolve(ldValueToBridge(resultBuilder.build())); } catch (LaunchDarklyException e) { // Since we confirmed the SDK has been configured, this exception should only be thrown if the env doesn't exist promise.reject(ERROR_UNKNOWN, "SDK not configured with requested environment"); @@ -887,45 +819,35 @@ private static LDValue toLDValue(Dynamic data) { } } - private static LDValue toLDValue(ReadableArray readableArray) { - if (readableArray == null) { - return LDValue.ofNull(); - } ArrayBuilder array = LDValue.buildArray(); for (int i = 0; i < readableArray.size(); i++) { - switch (readableArray.getType(i)) { - case Null: array.add(LDValue.ofNull()); break; - case Boolean: array.add(readableArray.getBoolean(i)); break; - case Number: array.add(readableArray.getDouble(i)); break; - case String: array.add(readableArray.getString(i)); break; - case Array: array.add(toLDValue(readableArray.getArray(i))); break; - case Map: array.add(toLDValue(readableArray.getMap(i))); break; - } + array.add(toLDValue(readableArray.getDynamic(i))); } return array.build(); } private static LDValue toLDValue(ReadableMap readableMap) { - if (readableMap == null) { - return LDValue.ofNull(); - } ObjectBuilder object = LDValue.buildObject(); ReadableMapKeySetIterator iter = readableMap.keySetIterator(); while (iter.hasNextKey()) { String key = iter.nextKey(); - switch (readableMap.getType(key)) { - case Null: object.put(key, LDValue.ofNull()); break; - case Boolean: object.put(key, readableMap.getBoolean(key)); break; - case Number: object.put(key, readableMap.getDouble(key)); break; - case String: object.put(key, readableMap.getString(key)); break; - case Array: object.put(key, toLDValue(readableMap.getArray(key))); break; - case Map: object.put(key, toLDValue(readableMap.getMap(key))); break; - } + object.put(key, toLDValue(readableMap.getDynamic(key))); } return object.build(); } + private static Object ldValueToBridge(LDValue value) { + switch (value.getType()) { + case BOOLEAN: return value.booleanValue(); + case NUMBER: return value.doubleValue(); + case STRING: return value.stringValue(); + case ARRAY: return ldValueToArray(value); + case OBJECT: return ldValueToMap(value); + default: return null; + } + } + private static WritableArray ldValueToArray(LDValue value) { WritableArray result = new WritableNativeArray(); for (LDValue val : value.values()) { diff --git a/index.js b/index.js index f489adb..97a9237 100644 --- a/index.js +++ b/index.js @@ -50,10 +50,6 @@ export default class LDClient { return Promise.resolve(); } - _validateInt(val) { - return !isNaN(val) && val > -0x80000001 && val < 0x80000000; - } - _normalizeEnv(environment) { if (typeof environment !== 'string') { return 'default'; From cbad40274b4b2b07ff5e8e08d134ff03bee01bd0 Mon Sep 17 00:00:00 2001 From: Gavin Whelan Date: Thu, 19 Aug 2021 17:59:42 +0000 Subject: [PATCH 23/32] Use latest iOS and Android SDKs (#89) --- android/build.gradle | 2 +- ios/LaunchdarklyReactNativeClient.podspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 9a2118e..0ccd3a9 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -52,7 +52,7 @@ allprojects { dependencies { implementation("com.facebook.react:react-native:+") - implementation("com.launchdarkly:launchdarkly-android-client-sdk:3.0.0") + implementation("com.launchdarkly:launchdarkly-android-client-sdk:3.1.0") implementation("com.jakewharton.timber:timber:4.7.1") implementation("com.google.code.gson:gson:2.8.6") } diff --git a/ios/LaunchdarklyReactNativeClient.podspec b/ios/LaunchdarklyReactNativeClient.podspec index 67246ae..453f4fb 100644 --- a/ios/LaunchdarklyReactNativeClient.podspec +++ b/ios/LaunchdarklyReactNativeClient.podspec @@ -16,6 +16,6 @@ Pod::Spec.new do |s| s.swift_version = "5.0" s.dependency "React" - s.dependency "LaunchDarkly", "5.4.1" + s.dependency "LaunchDarkly", "5.4.3" end From f0ade3f7e3a4e88971b448cd67da5f928695ae54 Mon Sep 17 00:00:00 2001 From: Gavin Whelan Date: Fri, 20 Aug 2021 21:25:33 +0000 Subject: [PATCH 24/32] Fix issue with numeric variation calls. (#90) --- ios/LaunchdarklyReactNativeClient.swift | 2 +- ios/LaunchdarklyReactNativeClientBridge.m | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ios/LaunchdarklyReactNativeClient.swift b/ios/LaunchdarklyReactNativeClient.swift index 9b2acf2..b498551 100644 --- a/ios/LaunchdarklyReactNativeClient.swift +++ b/ios/LaunchdarklyReactNativeClient.swift @@ -120,7 +120,7 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { } @objc func numberVariation(_ flagKey: String, defaultValue: Double, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.get(environment: environment)!.variation(forKey: flagKey, defaultValue: Double(defaultValue)) as Double) + resolve(LDClient.get(environment: environment)!.variation(forKey: flagKey, defaultValue: defaultValue) as Double) } @objc func stringVariation(_ flagKey: String, defaultValue: String, environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { diff --git a/ios/LaunchdarklyReactNativeClientBridge.m b/ios/LaunchdarklyReactNativeClientBridge.m index 5ddb17e..42ed580 100644 --- a/ios/LaunchdarklyReactNativeClientBridge.m +++ b/ios/LaunchdarklyReactNativeClientBridge.m @@ -9,7 +9,7 @@ @interface RCT_EXTERN_MODULE(LaunchdarklyReactNativeClient, RCTEventEmitter) RCT_EXTERN_METHOD(boolVariation:(NSString *)flagKey defaultValue:(BOOL *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(numberVariation:(NSString *)flagKey defaultValue:(Double *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(numberVariation:(NSString *)flagKey defaultValue:(NSNumber *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) RCT_EXTERN_METHOD(stringVariation:(NSString *)flagKey defaultValue:(NSString *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) @@ -27,7 +27,7 @@ @interface RCT_EXTERN_MODULE(LaunchdarklyReactNativeClient, RCTEventEmitter) RCT_EXTERN_METHOD(boolVariationDetail:(NSString *)flagKey defaultValue:(BOOL *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(numberVariationDetail:(NSString *)flagKey defaultValue:(Double *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(numberVariationDetail:(NSString *)flagKey defaultValue:(NSNumber *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) RCT_EXTERN_METHOD(stringVariationDetail:(NSString *)flagKey defaultValue:(NSString *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) From db3ca38578db66935c45edd86b2b7a829a255949 Mon Sep 17 00:00:00 2001 From: Gavin Whelan Date: Fri, 3 Sep 2021 18:55:34 +0000 Subject: [PATCH 25/32] Add consumer Proguard file to Android build configuration. (#91) --- android/build.gradle | 1 + android/consumer-proguard-rules.pro | 13 +++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 android/consumer-proguard-rules.pro diff --git a/android/build.gradle b/android/build.gradle index 0ccd3a9..67368ec 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -27,6 +27,7 @@ android { targetSdkVersion 26 versionCode 1 versionName "1.0" + consumerProguardFiles("consumer-proguard-rules.pro") } lintOptions { abortOnError false diff --git a/android/consumer-proguard-rules.pro b/android/consumer-proguard-rules.pro new file mode 100644 index 0000000..df05664 --- /dev/null +++ b/android/consumer-proguard-rules.pro @@ -0,0 +1,13 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in ~/Library/Android/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +-keep class com.launchdarkly.sdk.LDUser$Builder { public ; } +-keep class com.launchdarkly.sdk.android.LDConfig$Builder { public ; } From cb9aab381fc72c4cc9b69fec9280a1ca898a8c15 Mon Sep 17 00:00:00 2001 From: Gavin Whelan Date: Fri, 3 Sep 2021 21:28:43 +0000 Subject: [PATCH 26/32] Update Android SDK to 3.1.1 (#92) --- android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/build.gradle b/android/build.gradle index 67368ec..77fbb44 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -53,7 +53,7 @@ allprojects { dependencies { implementation("com.facebook.react:react-native:+") - implementation("com.launchdarkly:launchdarkly-android-client-sdk:3.1.0") + implementation("com.launchdarkly:launchdarkly-android-client-sdk:3.1.1") implementation("com.jakewharton.timber:timber:4.7.1") implementation("com.google.code.gson:gson:2.8.6") } From c4a98d719d2c8a8b2f1f8bf81cecc458750a5fff Mon Sep 17 00:00:00 2001 From: Gavin Whelan Date: Fri, 17 Sep 2021 08:24:57 -0700 Subject: [PATCH 27/32] Fix for absolute paths in cocoapods generated build files by running pod install in CI. (#93) --- .circleci/config.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index f401b45..d9b441c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -46,6 +46,11 @@ jobs: - run: cd ../hello-react-native && npm install - run: sudo npm install -g react-native-cli - run: cp -r ../project/ ../hello-react-native/node_modules/launchdarkly-react-native-client-sdk/ + # Newer cocoapods required for following pod install + - run: sudo gem install cocoapods + # RN 0.64 has an issue with embedding absolute paths in the cocoapods generated build files, so must regenerate. + # https://github.com/facebook/react-native/issues/31121 + - run: cd ../hello-react-native/ios && pod install - run: cd ../hello-react-native && react-native run-ios --configuration Release --simulator rn-ios common: From 4511f914419118234a8cc346b239288dd160a974 Mon Sep 17 00:00:00 2001 From: Ben Woskow <48036130+bwoskow-ld@users.noreply.github.com> Date: Mon, 20 Sep 2021 08:54:45 -0700 Subject: [PATCH 28/32] Re-introduce compatibility for React Native 0.63 (#94) --- .circleci/config.yml | 29 ++- README.md | 12 +- package-lock.json | 506 +++++++++++-------------------------------- package.json | 2 +- 4 files changed, 156 insertions(+), 393 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d9b441c..e390e3d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,9 @@ -version: 2 +version: 2.1 jobs: android: + parameters: + hello-app-branch: + type: string docker: - image: circleci/android@sha256:9d2e4571898fd9b1dc97bfb9da942aaeea46859eb5b1e4cfc85a44c42733643c environment: @@ -26,7 +29,7 @@ jobs: no_output_timeout: 20m - run: sudo npm install -g react-native-cli - run: cd .. && git clone https://github.com/launchdarkly/hello-react-native.git - - run: cd ../hello-react-native && npm install + - run: cd ../hello-react-native && git checkout <> && npm install - run: cp -r ../project/ ../hello-react-native/node_modules/launchdarkly-react-native-client-sdk/ - run: circle-android wait-for-boot - run: @@ -35,6 +38,9 @@ jobs: timeout: 1200 ios: + parameters: + hello-app-branch: + type: string macos: xcode: "11.4" steps: @@ -43,14 +49,14 @@ jobs: - run: xcrun simctl create rn-ios com.apple.CoreSimulator.SimDeviceType.iPhone-X com.apple.CoreSimulator.SimRuntime.iOS-12-2 - run: xcrun simctl boot rn-ios - run: cd .. && git clone https://github.com/launchdarkly/hello-react-native.git - - run: cd ../hello-react-native && npm install + - run: cd ../hello-react-native && git checkout <> && npm install - run: sudo npm install -g react-native-cli - run: cp -r ../project/ ../hello-react-native/node_modules/launchdarkly-react-native-client-sdk/ # Newer cocoapods required for following pod install - run: sudo gem install cocoapods # RN 0.64 has an issue with embedding absolute paths in the cocoapods generated build files, so must regenerate. # https://github.com/facebook/react-native/issues/31121 - - run: cd ../hello-react-native/ios && pod install + - run: cd ../hello-react-native/ios && pod update && pod install - run: cd ../hello-react-native && react-native run-ios --configuration Release --simulator rn-ios common: @@ -77,8 +83,23 @@ workflows: jobs: - common - android: + name: Android + RN 0.63 + hello-app-branch: rn-0.63 requires: - common - ios: + name: iOS + RN 0.63 + hello-app-branch: rn-0.63 requires: - common + - android: + name: Android + RN 0.64 + hello-app-branch: rn-0.64 + requires: + - common + - ios: + name: iOS + RN 0.64 + hello-app-branch: rn-0.64 + requires: + - common + diff --git a/README.md b/README.md index e4ad87b..b147e3b 100644 --- a/README.md +++ b/README.md @@ -14,13 +14,13 @@ LaunchDarkly overview Supported versions ------------------------- -This SDK is currently compatible with React Native 0.64.x and Xcode 12 and is tested in Android 30 and iOS 14. Earlier versions of this SDK are compatible with prior versions of React Native, Android, and iOS. +This SDK is currently compatible with React Native 0.63.x - 0.64.x and Xcode 12 and is tested in Android 30 and iOS 14. Earlier versions of this SDK are compatible with prior versions of React Native, Android, and iOS. -| SDK version | React Native version | -|-----------------|----------------------| -| 4.1.x - current | 0.64.x | -| 3.2.x - 4.0.x | 0.63.x | -| 3.1.x | 0.62.x | +| SDK version | React Native version | +|----------------------------------|----------------------| +| 4.1.x - current | 0.64.x | +| 5.0.2 - current
3.2.x - 4.0.x | 0.63.x | +| 3.1.x | 0.62.x | Getting started --------------- diff --git a/package-lock.json b/package-lock.json index f9a6f5d..716577a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,12 +5,12 @@ "requires": true, "dependencies": { "@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", "dev": true, "requires": { - "@babel/highlight": "^7.10.4" + "@babel/highlight": "^7.14.5" } }, "@babel/compat-data": { @@ -42,180 +42,6 @@ "source-map": "^0.5.0" }, "dependencies": { - "@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.14.5" - } - }, - "@babel/generator": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.0.tgz", - "integrity": "sha512-eKl4XdMrbpYvuB505KTta4AV9g+wWzmVBW69tX0H2NwKVKd2YJbKgyK6M8j/rgLbmHOYJn6rUklV677nOyJrEQ==", - "dev": true, - "requires": { - "@babel/types": "^7.15.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", - "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.14.5", - "@babel/template": "^7.14.5", - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", - "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.0.tgz", - "integrity": "sha512-Jq8H8U2kYiafuj2xMTPQwkTBnEEdGKpT35lJEQsRRjnG0LW3neucsaMWLgKcwu3OHKNeYugfw+Z20BXBSEs2Lg==", - "dev": true, - "requires": { - "@babel/types": "^7.15.0" - } - }, - "@babel/helper-module-imports": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz", - "integrity": "sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-module-transforms": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.0.tgz", - "integrity": "sha512-RkGiW5Rer7fpXv9m1B3iHIFDZdItnO2/BLfWVW/9q7+KqQSDY5kUfQEbzdXM1MVhJGcugKV7kRrNVzNxmk7NBg==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.14.5", - "@babel/helper-replace-supers": "^7.15.0", - "@babel/helper-simple-access": "^7.14.8", - "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/helper-validator-identifier": "^7.14.9", - "@babel/template": "^7.14.5", - "@babel/traverse": "^7.15.0", - "@babel/types": "^7.15.0" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz", - "integrity": "sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-replace-supers": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.0.tgz", - "integrity": "sha512-6O+eWrhx+HEra/uJnifCwhwMd6Bp5+ZfZeJwbqUTuqkhIT6YcRhiZCOOFChRypOIe0cV46kFrRBlm+t5vHCEaA==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.15.0", - "@babel/helper-optimise-call-expression": "^7.14.5", - "@babel/traverse": "^7.15.0", - "@babel/types": "^7.15.0" - } - }, - "@babel/helper-simple-access": { - "version": "7.14.8", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.14.8.tgz", - "integrity": "sha512-TrFN4RHh9gnWEU+s7JloIho2T76GPwRHhdzOWLqTrMnlas8T9O7ec+oEDNsRXndOmru9ymH9DFrEOxpzPoSbdg==", - "dev": true, - "requires": { - "@babel/types": "^7.14.8" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", - "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", - "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==", - "dev": true - }, - "@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.15.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.3.tgz", - "integrity": "sha512-O0L6v/HvqbdJawj0iBEfVQMc3/6WP+AeOsovsIgBFyJaG+W2w7eqvZB7puddATmWuARlm1SX7DwxJ/JJUnDpEA==", - "dev": true - }, - "@babel/template": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", - "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.14.5", - "@babel/types": "^7.14.5" - } - }, - "@babel/traverse": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.0.tgz", - "integrity": "sha512-392d8BN0C9eVxVWd8H6x9WfipgVH5IaIoLp23334Sc1vbKKWINnvwRpb4us0xtPaCumlwbTtIYNA0Dv/32sVFw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.0", - "@babel/helper-function-name": "^7.14.5", - "@babel/helper-hoist-variables": "^7.14.5", - "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/parser": "^7.15.0", - "@babel/types": "^7.15.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.0.tgz", - "integrity": "sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -225,14 +51,13 @@ } }, "@babel/generator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.4.tgz", - "integrity": "sha512-toLIHUIAgcQygFZRAQcsLQV3CBuX6yOIru1kJk/qqqvcRmZrYe6WavZTSG+bB8MxhnL9YPf+pKQfuiP161q7ng==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.4.tgz", + "integrity": "sha512-d3itta0tu+UayjEORPNz6e1T3FtvWlP5N4V5M+lhp/CxT4oAA7/NcScnpRyspUMLK6tu9MNHmQHxRykuN2R7hw==", "dev": true, "requires": { - "@babel/types": "^7.10.4", + "@babel/types": "^7.15.4", "jsesc": "^2.5.1", - "lodash": "^4.17.13", "source-map": "^0.5.0" } }, @@ -257,71 +82,117 @@ } }, "@babel/helper-function-name": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", - "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", + "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" + "@babel/helper-get-function-arity": "^7.15.4", + "@babel/template": "^7.15.4", + "@babel/types": "^7.15.4" } }, "@babel/helper-get-function-arity": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", - "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", + "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", "dev": true, "requires": { - "@babel/types": "^7.10.4" + "@babel/types": "^7.15.4" } }, "@babel/helper-hoist-variables": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz", - "integrity": "sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz", + "integrity": "sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==", "dev": true, "requires": { - "@babel/types": "^7.14.5" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", - "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==", - "dev": true - }, - "@babel/types": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.0.tgz", - "integrity": "sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - } + "@babel/types": "^7.15.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz", + "integrity": "sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA==", + "dev": true, + "requires": { + "@babel/types": "^7.15.4" + } + }, + "@babel/helper-module-imports": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz", + "integrity": "sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==", + "dev": true, + "requires": { + "@babel/types": "^7.15.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.4.tgz", + "integrity": "sha512-9fHHSGE9zTC++KuXLZcB5FKgvlV83Ox+NLUmQTawovwlJ85+QMhk1CnVk406CQVj97LaWod6KVjl2Sfgw9Aktw==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.15.4", + "@babel/helper-replace-supers": "^7.15.4", + "@babel/helper-simple-access": "^7.15.4", + "@babel/helper-split-export-declaration": "^7.15.4", + "@babel/helper-validator-identifier": "^7.14.9", + "@babel/template": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.4" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz", + "integrity": "sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw==", + "dev": true, + "requires": { + "@babel/types": "^7.15.4" } }, "@babel/helper-plugin-utils": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", - "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", + "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", "dev": true }, + "@babel/helper-replace-supers": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz", + "integrity": "sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.15.4", + "@babel/helper-optimise-call-expression": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.4" + } + }, + "@babel/helper-simple-access": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.15.4.tgz", + "integrity": "sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg==", + "dev": true, + "requires": { + "@babel/types": "^7.15.4" + } + }, "@babel/helper-split-export-declaration": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz", - "integrity": "sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", + "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", "dev": true, "requires": { - "@babel/types": "^7.10.4" + "@babel/types": "^7.15.4" } }, "@babel/helper-validator-identifier": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", - "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "version": "7.14.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", + "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==", "dev": true }, "@babel/helper-validator-option": { @@ -339,135 +210,23 @@ "@babel/template": "^7.14.5", "@babel/traverse": "^7.15.0", "@babel/types": "^7.15.0" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.14.5" - } - }, - "@babel/generator": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.0.tgz", - "integrity": "sha512-eKl4XdMrbpYvuB505KTta4AV9g+wWzmVBW69tX0H2NwKVKd2YJbKgyK6M8j/rgLbmHOYJn6rUklV677nOyJrEQ==", - "dev": true, - "requires": { - "@babel/types": "^7.15.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", - "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.14.5", - "@babel/template": "^7.14.5", - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", - "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", - "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", - "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==", - "dev": true - }, - "@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.15.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.3.tgz", - "integrity": "sha512-O0L6v/HvqbdJawj0iBEfVQMc3/6WP+AeOsovsIgBFyJaG+W2w7eqvZB7puddATmWuARlm1SX7DwxJ/JJUnDpEA==", - "dev": true - }, - "@babel/template": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", - "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.14.5", - "@babel/types": "^7.14.5" - } - }, - "@babel/traverse": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.0.tgz", - "integrity": "sha512-392d8BN0C9eVxVWd8H6x9WfipgVH5IaIoLp23334Sc1vbKKWINnvwRpb4us0xtPaCumlwbTtIYNA0Dv/32sVFw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.0", - "@babel/helper-function-name": "^7.14.5", - "@babel/helper-hoist-variables": "^7.14.5", - "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/parser": "^7.15.0", - "@babel/types": "^7.15.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.0.tgz", - "integrity": "sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - } } }, "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.10.4", + "@babel/helper-validator-identifier": "^7.14.5", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.4.tgz", - "integrity": "sha512-8jHII4hf+YVDsskTF6WuMB3X4Eh+PsUkC2ljq22so5rHvH+T8BzyL94VOdyFLNR8tBSVXOTbNHOKpR4TfRxVtA==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.4.tgz", + "integrity": "sha512-xmzz+7fRpjrvDUj+GV7zfz/R3gSK2cOxGlazaXooxspCr539cbTXJKvBJzSVI2pPhcRGquoOtaIkKCsHQUiO3w==", "dev": true }, "@babel/plugin-syntax-async-generators": { @@ -495,14 +254,6 @@ "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.12.13" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", - "dev": true - } } }, "@babel/plugin-syntax-import-meta": { @@ -584,52 +335,43 @@ "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.14.5" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", - "dev": true - } } }, "@babel/template": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", - "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", + "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", "dev": true, "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.10.4", - "@babel/types": "^7.10.4" + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.15.4", + "@babel/types": "^7.15.4" } }, "@babel/traverse": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.4.tgz", - "integrity": "sha512-aSy7p5THgSYm4YyxNGz6jZpXf+Ok40QF3aA2LyIONkDHpAcJzDUqlCKXv6peqYUs2gmic849C/t2HKw2a2K20Q==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", + "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", "dev": true, "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.10.4", - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.10.4", - "@babel/parser": "^7.10.4", - "@babel/types": "^7.10.4", + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.15.4", + "@babel/helper-function-name": "^7.15.4", + "@babel/helper-hoist-variables": "^7.15.4", + "@babel/helper-split-export-declaration": "^7.15.4", + "@babel/parser": "^7.15.4", + "@babel/types": "^7.15.4", "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" + "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.4.tgz", - "integrity": "sha512-UTCFOxC3FsFHb7lkRMVvgLzaRVamXuAs2Tz4wajva4WxtVY82eZeaUBtC2Zt95FU9TiznuC0Zk35tsim8jeVpg==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.4.tgz", + "integrity": "sha512-0f1HJFuGmmbrKTCZtbm3cU+b/AqdEYk5toj5iQur58xkVMlS0JWaKxTBSmCXd47uiN7vbcozAupm6Mvs80GNhw==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "lodash": "^4.17.13", + "@babel/helper-validator-identifier": "^7.14.9", "to-fast-properties": "^2.0.0" } }, diff --git a/package.json b/package.json index 15bbe1f..9378311 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ }, "homepage": "https://docs.launchdarkly.com/sdk/client-side/react-native", "peerDependencies": { - "react-native": "~0.64" + "react-native": "~0.63 || ~0.64" }, "devDependencies": { "jest": "^26.6.3", From 432605cb65c66244d3a43f5adc5dbba5ab6b662a Mon Sep 17 00:00:00 2001 From: Ember Stevens Date: Sun, 26 Sep 2021 16:04:14 -0700 Subject: [PATCH 29/32] Updates docs URLs --- CHANGELOG.md | 2 +- README.md | 2 +- index.d.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1eafa77..4e36a1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,7 +80,7 @@ All notable changes to the LaunchDarkly React Native SDK will be documented in t ## [4.2.0] - 2021-05-19 ### Added: -- `LDUser` now has an optional `secondary` attribute to match other LaunchDarkly SDKs. For more on the behavior of this attribute see [the documentation on targeting users](https://docs.launchdarkly.com/home/managing-flags/targeting-users). +- `LDUser` now has an optional `secondary` attribute to match other LaunchDarkly SDKs. For more on the behavior of this attribute see [the documentation on targeting users](https://docs.launchdarkly.com/home/flags/targeting-users). - Support for multiple LaunchDarkly projects or environments. Each set of feature flags associated with a mobile key is called an environment. ([#10](https://github.com/launchdarkly/react-native-client-sdk/issues/10)) - `secondaryMobileKeys` is now a config option which allows a mapping of names to the SDK keys for each additional environment. `mobileKey` is still required, and represents the primary environment. - Many methods including variations, track, and listeners now support an optional `environment` parameter to evaluate the method against the given `environment`. diff --git a/README.md b/README.md index b147e3b..d7f7805 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ About LaunchDarkly * Gradually roll out a feature to an increasing percentage of users, and track the effect that the feature has on key metrics (for instance, how likely is a user to complete a purchase if they have feature A versus feature B?). * Turn off a feature that you realize is causing performance problems in production, without needing to re-deploy, or even restart the application with a changed configuration file. * Grant access to certain features based on user attributes, like payment plan (eg: users on the ‘gold’ plan get access to more features than users in the ‘silver’ plan). Disable parts of your application to facilitate maintenance, without taking everything offline. -* LaunchDarkly provides feature flag SDKs for a wide variety of languages and technologies. Check out [our documentation](https://docs.launchdarkly.com) for a complete list. +* LaunchDarkly provides feature flag SDKs for a wide variety of languages and technologies. Read [our documentation](https://docs.launchdarkly.com/sdk) for a complete list. * Explore LaunchDarkly * [launchdarkly.com](https://www.launchdarkly.com/ "LaunchDarkly Main Website") for more information * [docs.launchdarkly.com](https://docs.launchdarkly.com/ "LaunchDarkly Documentation") for our documentation and SDK reference guides diff --git a/index.d.ts b/index.d.ts index b578f04..9a38217 100644 --- a/index.d.ts +++ b/index.d.ts @@ -186,7 +186,7 @@ declare module 'launchdarkly-react-native-client-sdk' { /** * The secondary key for the user. See the - * [documentation](https://docs.launchdarkly.com/home/managing-flags/targeting-users#percentage-rollout-logic) + * [documentation](https://docs.launchdarkly.com/home/flags/targeting-users#percentage-rollouts) * for more information on it's use for percentage rollout bucketing. */ secondary?: string; From 51504f947a71b7066c67017193af8737e6243bbd Mon Sep 17 00:00:00 2001 From: Gavin Whelan Date: Wed, 29 Sep 2021 10:46:08 -0700 Subject: [PATCH 30/32] Add configuration option `inlineUsersInEvents` and typescript definition for `autoAliasingOptOut`. (#96) --- .../LaunchdarklyReactNativeClientModule.java | 3 ++- index.d.ts | 16 ++++++++++++++++ ios/LaunchdarklyReactNativeClient.swift | 1 + test-types.ts | 2 ++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java b/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java index bf71c6f..41ce108 100644 --- a/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java +++ b/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java @@ -74,7 +74,8 @@ enum ConfigMapping { CONFIG_DIAGNOSTIC_OPT_OUT("diagnosticOptOut", ConfigEntryType.Boolean), CONFIG_DIAGNOSTIC_RECORDING_INTERVAL("diagnosticRecordingIntervalMillis", ConfigEntryType.Integer), CONFIG_SECONDARY_MOBILE_KEYS("secondaryMobileKeys", ConfigEntryType.Map), - CONFIG_AUTO_ALIASING_OPT_OUT("autoAliasingOptOut", ConfigEntryType.Boolean); + CONFIG_AUTO_ALIASING_OPT_OUT("autoAliasingOptOut", ConfigEntryType.Boolean), + CONFIG_INLINE_USERS_IN_EVENTS("inlineUsersInEvents", ConfigEntryType.Boolean); final String key; final ConfigEntryType type; diff --git a/index.d.ts b/index.d.ts index 9a38217..8ad40a4 100644 --- a/index.d.ts +++ b/index.d.ts @@ -172,6 +172,22 @@ declare module 'launchdarkly-react-native-client-sdk' { * The default is false. */ allUserAttributesPrivate?: boolean; + + /** + * Whether to disable the automatic sending of an alias event when [[LDClient.identify]] is + * called with a non-anonymous user when the previous user is anonymous. + * + * The default value is `false`. + */ + autoAliasingOptOut?: boolean; + + /** + * Whether to include full user details in every analytics event. + * + * The default is `false`: events will only include the user key, except for one "identify" event + * that provides the full details for the user. + */ + inlineUsersInEvents?: boolean; }; /** diff --git a/ios/LaunchdarklyReactNativeClient.swift b/ios/LaunchdarklyReactNativeClient.swift index b498551..4878be4 100644 --- a/ios/LaunchdarklyReactNativeClient.swift +++ b/ios/LaunchdarklyReactNativeClient.swift @@ -89,6 +89,7 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { configField(&ldConfig.diagnosticRecordingInterval, config["diagnosticRecordingIntervalMillis"], millis) configField(&ldConfig.allUserAttributesPrivate, config["allUserAttributesPrivate"], id) configField(&ldConfig.autoAliasingOptOut, config["autoAliasingOptOut"], id) + configField(&ldConfig.inlineUserInEvents, config["inlineUsersInEvents"], id) if let val = config["secondaryMobileKeys"] as? [String: String] { try! ldConfig.setSecondaryMobileKeys(val) diff --git a/test-types.ts b/test-types.ts index 373c661..2245036 100644 --- a/test-types.ts +++ b/test-types.ts @@ -46,6 +46,8 @@ async function tests() { diagnosticOptOut: true, diagnosticRecordingIntervalMillis: 100000, allUserAttributesPrivate: true, + autoAliasingOptOut: true, + inlineUsersInEvents: true, }; const userWithKeyOnly: LDUser = { key: 'user' }; const user: LDUser = { From b2104a206f559e6987cc9ee6d0b66859db243d80 Mon Sep 17 00:00:00 2001 From: Ben Woskow <48036130+bwoskow-ld@users.noreply.github.com> Date: Thu, 30 Sep 2021 10:55:34 -0700 Subject: [PATCH 31/32] minor doc improvement (#97) --- index.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.d.ts b/index.d.ts index 924160a..5699882 100644 --- a/index.d.ts +++ b/index.d.ts @@ -676,12 +676,12 @@ declare module 'launchdarkly-react-native-client-sdk' { * the client has successfully connected to LaunchDarkly and received feature flags, or the * client has been put into offline mode (in which case it will return only default flag values). * - * This function will return a rejected promise in case it has not been isInitialized. + * This function will return a rejected promise in case the client has not been initialized. * * @param environment * Optional environment name to obtain the result from the corresponding secondary environment * @returns - * A promise contianing true if the client is initialized or offline, otherwise a rejected promise + * A promise containing true if the client is initialized or offline, otherwise a rejected promise */ isInitialized(environment?: string): Promise; From 2fc3cac785554699e977c2e12245ea195d9768fc Mon Sep 17 00:00:00 2001 From: Gavin Whelan Date: Thu, 30 Sep 2021 13:33:18 -0700 Subject: [PATCH 32/32] Update version in README to match what version we'll actually be releasing with (#98) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d7f7805..6b02dfa 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ This SDK is currently compatible with React Native 0.63.x - 0.64.x and Xcode 12 | SDK version | React Native version | |----------------------------------|----------------------| | 4.1.x - current | 0.64.x | -| 5.0.2 - current
3.2.x - 4.0.x | 0.63.x | +| 5.1.0 - current
3.2.x - 4.0.x | 0.63.x | | 3.1.x | 0.62.x | Getting started