From e2ed7e297f3642a8648232f97141b0c6595d60bc Mon Sep 17 00:00:00 2001 From: Joe Cieslik <5600929+torchhound@users.noreply.github.com> Date: Mon, 23 Dec 2019 16:29:47 -0800 Subject: [PATCH 1/3] Prepare 2.1.0 release (#30) * Bump iOS client version for use_frameworks fix * Added RCTConvert.h to bridging header * Bumped version, added CHANGELOG entry, added experimentation, evaluation reasons, all flags listener, connection status, iOS identify, and iOS and Android SDK version bumps * Fix minor errors * Added Android imports and fixed types, fixed iOS Evaluation Detail types * Fixed android listeners and connectionInfo JSON * Added resolveJsonElementDetail * Minor type fixes * Minor type fixes * Fixed iOS type coercion * Changed allFlagListener to allFlagsListener, fixed utility method reference * Bumped package.json to 2.1.0 * Updated iOS client SDK to 4.3.2 * Pass callback to _unregisterListener * Moved callback to correctly pass to _unregisterListener * Added new event prefixes, extra handlers for listeners * Fixed listener methods reversed, event emitter listeners * Removed all flags and connection mode listeners from being stored in an Array * Fixed all flags typo, incorrect json key in connection mode * Fixed function typos, change is nil to does not equal nil in iOS layer * Remove changelog so releaser can handle it, revert iOS listener change because we don't want overwrites --- README.md | 2 +- android/build.gradle | 2 +- .../LaunchdarklyReactNativeClientModule.java | 214 +++++++++++++++++- index.js | 152 +++++++++++-- ios/LaunchdarklyReactNativeClient.podspec | 4 +- ios/LaunchdarklyReactNativeClient.swift | 153 +++++++++++-- ios/LaunchdarklyReactNativeClientBridge.m | 50 ++++ package.json | 2 +- 8 files changed, 541 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index b3dbe8a..45af4dc 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ LaunchDarkly overview Supported versions ------------------------- -This SDK is compatible with React Native 0.59.9 and Xcode 10.2.1 and is tested in Android 27 and iOS 12.2. Earlier versions of this SDK are compatible with prior versions of React Native, Android, and iOS. +This SDK is compatible with React Native 0.61.2 and Xcode 10.2.1 and is tested in Android 27 and iOS 12.2. 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 147ceec..5c34599 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -48,7 +48,7 @@ allprojects { dependencies { implementation 'com.facebook.react:react-native:+' - implementation 'com.launchdarkly:launchdarkly-android-client-sdk:2.8.5' + implementation 'com.launchdarkly:launchdarkly-android-client-sdk:2.9.0' 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 1cdfafd..e66c2bc 100644 --- a/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java +++ b/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java @@ -17,6 +17,7 @@ import com.facebook.react.bridge.WritableNativeArray; 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; @@ -29,6 +30,11 @@ 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.LDFailure; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -37,6 +43,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.List; import java.util.concurrent.ExecutionException; import timber.log.Timber; @@ -154,6 +161,8 @@ void loadFromMap(ReadableMap map, LDUser.Builder builder, Set privateAtt private LDClient ldClient; // Current feature flag listeners private Map listeners = new HashMap<>(); + private Map connectionModeListeners = new HashMap<>(); + private Map allFlagsListeners = new HashMap<>(); public LaunchdarklyReactNativeClientModule(ReactApplicationContext reactContext) { super(reactContext); @@ -178,7 +187,9 @@ public String getName() { private static final String ERROR_UNKNOWN = "E_UNKNOWN"; // Prefix for events sent over the React Native event bridge - private static final String EVENT_PREFIX = "LaunchDarkly--"; + 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-"; /** @@ -190,7 +201,9 @@ public String getName() { @Override public Map getConstants() { final Map constants = new HashMap<>(); - constants.put("EVENT_PREFIX", EVENT_PREFIX); + constants.put("FLAG_PREFIX", FLAG_PREFIX); + constants.put("ALL_FLAGS_PREFIX", ALL_FLAGS_PREFIX); + constants.put("CONNECTION_MODE_PREFIX", CONNECTION_MODE_PREFIX); return constants; } @@ -546,6 +559,92 @@ public void jsonVariationObject(String flagKey, ReadableMap fallback, Promise pr jsonVariationBase(flagKey, toJsonObject(fallback), promise); } + @ReactMethod + public void boolVariationDetail(String flagKey, Promise promise) { + boolVariationDetailFallback(flagKey, null, promise); + } + + @ReactMethod + public void boolVariationDetailFallback(String flagKey, Boolean fallback, Promise promise) { + try { + promise.resolve(ldClient.boolVariationDetail(flagKey, fallback)); + } catch (Exception e) { + promise.resolve(fallback); + } + } + + @ReactMethod + public void intVariationDetail(String flagKey, Promise promise) { + intVariationDetailFallback(flagKey, null, promise); + } + + @ReactMethod + public void intVariationDetailFallback(String flagKey, Integer fallback, Promise promise) { + try { + promise.resolve(ldClient.intVariationDetail(flagKey, fallback)); + } catch (Exception e) { + promise.resolve(fallback); + } + } + + @ReactMethod + public void floatVariationDetail(String flagKey, Promise promise) { + floatVariationDetailFallback(flagKey, null, promise); + } + + @ReactMethod + public void floatVariationDetailFallback(String flagKey, Float fallback, Promise promise) { + try { + promise.resolve(ldClient.floatVariationDetail(flagKey, fallback)); + } catch (Exception e) { + promise.resolve(fallback); + } + } + + @ReactMethod + public void stringVariationDetail(String flagKey, Promise promise) { + stringVariationDetailFallback(flagKey, null, promise); + } + + @ReactMethod + public void stringVariationDetailFallback(String flagKey, String fallback, Promise promise) { + try { + promise.resolve(ldClient.stringVariationDetail(flagKey, fallback)); + } catch (Exception e) { + promise.resolve(fallback); + } + } + + @ReactMethod + public void jsonVariationDetailNone(String flagKey, Promise promise) { + jsonVariationDetailBase(flagKey, null, promise); + } + + @ReactMethod + public void jsonVariationDetailNumber(String flagKey, Double fallback, Promise promise) { + jsonVariationDetailBase(flagKey, new JsonPrimitive(fallback), promise); + } + + @ReactMethod + public void jsonVariationDetailBool(String flagKey, Boolean fallback, Promise promise) { + jsonVariationDetailBase(flagKey, new JsonPrimitive(fallback), promise); + } + + @ReactMethod + public void jsonVariationDetailString(String flagKey, String fallback, Promise promise) { + jsonVariationDetailBase(flagKey, new JsonPrimitive(fallback), promise); + } + + @ReactMethod + public void jsonVariationDetailArray(String flagKey, ReadableArray fallback, Promise promise) { + jsonVariationDetailBase(flagKey, toJsonArray(fallback), promise); + } + + @ReactMethod + public void jsonVariationDetailObject(String flagKey, ReadableMap fallback, Promise promise) { + jsonVariationDetailBase(flagKey, toJsonObject(fallback), promise); + } + /** * Helper for jsonVariation methods. * @@ -562,6 +661,15 @@ private void jsonVariationBase(String flagKey, JsonElement fallback, Promise pro } } + private void jsonVariationDetailBase(String flagKey, JsonElement fallback, Promise promise) { + try { + EvaluationDetail jsonElementDetail = ldClient.jsonVariationDetail(flagKey, fallback); + resolveJsonElementDetail(promise, jsonElementDetail); + } catch (Exception e) { + resolveJsonElement(promise, fallback); + } + } + /** * Converts the jsonElement to a React Native bridge compatible type and resolves the promise @@ -589,6 +697,11 @@ private void resolveJsonElement(Promise promise, JsonElement jsonElement) { } } + private void resolveJsonElementDetail(Promise promise, EvaluationDetail jsonElementDetail) { + JsonElement jsonElement = jsonElementDetail.getValue(); + resolveJsonElement(promise, jsonElement); + } + /** * Gets a object mapping of all flags and their values. * @@ -729,6 +842,36 @@ public void track(String eventName) { ldClient.track(eventName); } + @ReactMethod + public void trackNumberMetricValue(String eventName, Double data, Double metricValue) { + ldClient.track(eventName, new JsonPrimitive(data), metricValue); + } + + @ReactMethod + public void trackBoolMetricValue(String eventName, Boolean data, Double metricValue) { + ldClient.track(eventName, new JsonPrimitive(data), metricValue); + } + + @ReactMethod + public void trackStringMetricValue(String eventName, String data, Double metricValue) { + ldClient.track(eventName, new JsonPrimitive(data), metricValue); + } + + @ReactMethod + public void trackArrayMetricValue(String eventName, ReadableArray data, Double metricValue) { + ldClient.track(eventName, toJsonArray(data), metricValue); + } + + @ReactMethod + public void trackObjectMetricValue(String eventName, ReadableMap data, Double metricValue) { + ldClient.track(eventName, toJsonObject(data), metricValue); + } + + @ReactMethod + public void trackMetricValue(String eventName, Double metricValue) { + ldClient.track(eventName, new JsonPrimitive(""), metricValue); + } + /** * Shuts down any network connections maintained by the client and puts the client in offline * mode. @@ -842,6 +985,16 @@ public void isDisableBackgroundPolling(Promise promise) { } } + @ReactMethod + public void getConnectionInformation(Promise promise) { + try { + ConnectionInformation result = ldClient.getConnectionInformation(); + promise.resolve(result); + } catch (Exception e) { + promise.reject(ERROR_UNKNOWN, e); + } + } + @ReactMethod public void registerFeatureFlagListener(String flagKey) { FeatureFlagChangeListener listener = new FeatureFlagChangeListener() { @@ -852,7 +1005,7 @@ public void onFeatureFlagChange(String flagKey) { getReactApplicationContext() .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit(EVENT_PREFIX, result); + .emit(FLAG_PREFIX, result); } }; @@ -868,6 +1021,61 @@ public void unregisterFeatureFlagListener(String flagKey) { } } + @ReactMethod + public void registerCurrentConnectionModeListener(String listenerId) { + LDStatusListener listener = new LDStatusListener() { + @Override + public void onConnectionModeChanged(ConnectionInformation connectionInfo) { + WritableMap result = Arguments.createMap(); + result.putString("connectionMode", new Gson().toJson(connectionInfo)); + + getReactApplicationContext() + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit(CONNECTION_MODE_PREFIX, result); + } + + @Override + public void onInternalFailure(LDFailure ldFailure) {} + }; + + ldClient.registerStatusListener(listener); + connectionModeListeners.put(listenerId, listener); + } + + @ReactMethod + public void unregisterCurrentConnectionModeListener(String listenerId) { + if (connectionModeListeners.containsKey(listenerId)) { + ldClient.unregisterStatusListener(connectionModeListeners.get(listenerId)); + connectionModeListeners.remove(listenerId); + } + } + + @ReactMethod + public void registerAllFlagsListener(String listenerId) { + LDAllFlagsListener listener = new LDAllFlagsListener() { + @Override + public void onChange(List flagKeys) { + WritableMap result = Arguments.createMap(); + result.putString("flagKeys", new Gson().toJson(flagKeys)); + + getReactApplicationContext() + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit(ALL_FLAGS_PREFIX, result); + } + }; + + ldClient.registerAllFlagsListener(listener); + allFlagsListeners.put(listenerId, listener); + } + + @ReactMethod + public void unregisterAllFlagsListener(String listenerId) { + if (allFlagsListeners.containsKey(listenerId)) { + ldClient.unregisterAllFlagsListener(allFlagsListeners.get(listenerId)); + allFlagsListeners.remove(listenerId); + } + } + /** * Convert a ReadableMap into a JsonObject *

diff --git a/index.js b/index.js index aacaddd..5742671 100644 --- a/index.js +++ b/index.js @@ -6,7 +6,11 @@ export default class LDClient { constructor() { this.eventEmitter = new NativeEventEmitter(LaunchdarklyReactNativeClient); this.flagListeners = {}; - this.eventEmitter.addListener(LaunchdarklyReactNativeClient.EVENT_PREFIX, body => this._flagUpdateListener(body)); + this.allFlagsListeners = {}; + this.connectionModeListeners = {}; + this.eventEmitter.addListener(LaunchdarklyReactNativeClient.FLAG_PREFIX, body => this._flagUpdateListener(body)); + this.eventEmitter.addListener(LaunchdarklyReactNativeClient.ALL_FLAGS_PREFIX, body => this._allFlagsUpdateListener(body)); + this.eventEmitter.addListener(LaunchdarklyReactNativeClient.CONNECTION_MODE_PREFIX, body => this._connectionModeUpdateListener(body)); } configure(config, userConfig) { @@ -62,24 +66,90 @@ export default class LDClient { } } + boolVariationDetail(flagKey, fallback) { + if (fallback == undefined) { + return LaunchdarklyReactNativeClient.boolVariationDetail(flagKey); + } else { + return LaunchdarklyReactNativeClient.boolVariationDetailFallback(flagKey, fallback); + } + } + + intVariationDetail(flagKey, fallback) { + if (fallback == undefined) { + return LaunchdarklyReactNativeClient.intVariationDetail(flagKey); + } else { + return LaunchdarklyReactNativeClient.intVariationDetailFallback(flagKey, fallback); + } + } + + floatVariationDetail(flagKey, fallback) { + if (fallback == undefined) { + return LaunchdarklyReactNativeClient.floatVariationDetail(flagKey); + } else { + return LaunchdarklyReactNativeClient.floatVariationDetailFallback(flagKey, fallback); + } + } + + stringVariationDetail(flagKey, fallback) { + if (fallback == undefined) { + return LaunchdarklyReactNativeClient.stringVariationDetail(flagKey); + } else { + return LaunchdarklyReactNativeClient.stringVariationDetailFallback(flagKey, fallback); + } + } + + jsonVariationDetail(flagKey, fallback) { + if (fallback == 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 { + // Should be an object + return LaunchdarklyReactNativeClient.jsonVariationDetailObject(flagKey, fallback); + } + } + allFlags() { return LaunchdarklyReactNativeClient.allFlags(); } - track(eventName, data) { - if (data === null || typeof data === 'undefined') { - LaunchdarklyReactNativeClient.track(eventName); - } else if (typeof data === 'number') { - LaunchdarklyReactNativeClient.trackNumber(eventName, data); - } else if (typeof data === 'boolean') { - LaunchdarklyReactNativeClient.trackBool(eventName, data); - } else if (typeof data === 'string') { - LaunchdarklyReactNativeClient.trackString(eventName, data); - } else if (Array.isArray(data)) { - LaunchdarklyReactNativeClient.trackArray(eventName, data); + track(eventName, data, metricValue) { + if (metricValue) { + if (data === null || typeof data === 'undefined') { + LaunchdarklyReactNativeClient.trackMetricValue(eventName, metricValue); + } else if (typeof data === 'number') { + LaunchdarklyReactNativeClient.trackNumberMetricValue(eventName, data, metricValue); + } else if (typeof data === 'boolean') { + LaunchdarklyReactNativeClient.trackBoolMetricValuel(eventName, data, metricValue); + } else if (typeof data === 'string') { + LaunchdarklyReactNativeClient.trackStringMetricValue(eventName, data, metricValue); + } else if (Array.isArray(data)) { + LaunchdarklyReactNativeClient.trackArrayMetricValue(eventName, data, metricValue); + } else { + // should be an object + LaunchdarklyReactNativeClient.trackObjectMetricValue(eventName, data, metricValue); + } } else { - // should be an object - LaunchdarklyReactNativeClient.trackObject(eventName, data); + if (data === null || typeof data === 'undefined') { + LaunchdarklyReactNativeClient.track(eventName); + } else if (typeof data === 'number') { + LaunchdarklyReactNativeClient.trackNumber(eventName, data); + } else if (typeof data === 'boolean') { + LaunchdarklyReactNativeClient.trackBool(eventName, data); + } else if (typeof data === 'string') { + LaunchdarklyReactNativeClient.trackString(eventName, data); + } else if (Array.isArray(data)) { + LaunchdarklyReactNativeClient.trackArray(eventName, data); + } else { + // should be an object + LaunchdarklyReactNativeClient.trackObject(eventName, data); + } } } @@ -129,6 +199,22 @@ export default class LDClient { } } + _allFlagsUpdateListener(changedFlags) { + const flagKeys = changedFlags.flagKeys; + let listeners = Object.values(this.allFlagsListeners); + for (const listener of listeners) { + listener(flagKeys); + } + } + + _connectionModeUpdateListener(connectionStatus) { + const connectionMode = connectionStatus.connectionMode; + let listeners = Object.values(this.connectionModeListeners); + for (const listener of listeners) { + listener(connectionMode); + } + } + registerFeatureFlagListener(flagKey, callback) { if (typeof callback !== "function") { return; @@ -155,4 +241,42 @@ export default class LDClient { delete this.flagListeners[flagKey]; } } + + getConnectionInformation() { + return LaunchdarklyReactNativeClient.getConnectionInformation(); + } + + registerCurrentConnectionModeListener(listenerId, callback) { + if (typeof callback !== "function") { + return; + } + + this.connectionModeListeners[listenerId] = callback; + LaunchdarklyReactNativeClient.registerCurrentConnectionModeListener(listenerId); + } + + unregisterCurrentConnectionModeListener(listenerId) { + if (!this.connectionModeListeners.hasOwnProperty(listenerId)) + return; + + LaunchdarklyReactNativeClient.unregisterCurrentConnectionModeListener(listenerId); + delete this.connectionModeListeners[listenerId]; + } + + registerAllFlagsListener(listenerId, callback) { + if (typeof callback !== "function") { + return; + } + + this.allFlagsListeners[listenerId] = callback; + LaunchdarklyReactNativeClient.registerAllFlagsListener(listenerId); + } + + unregisterAllFlagsListener(listenerId) { + if (!this.allFlagsListeners.hasOwnProperty(listenerId)) + return; + + LaunchdarklyReactNativeClient.unregisterAllFlagsListener(listenerId); + delete this.allFlagsListeners[listenerId]; + } } diff --git a/ios/LaunchdarklyReactNativeClient.podspec b/ios/LaunchdarklyReactNativeClient.podspec index 928b770..f5ae839 100644 --- a/ios/LaunchdarklyReactNativeClient.podspec +++ b/ios/LaunchdarklyReactNativeClient.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "LaunchdarklyReactNativeClient" - s.version = "2.0.1" + s.version = "2.1.0" s.summary = "LaunchdarklyReactNativeClient" s.description = <<-DESC LaunchdarklyReactNativeClient @@ -16,6 +16,6 @@ Pod::Spec.new do |s| s.swift_version = "5.0" s.dependency "React" - s.dependency "LaunchDarkly", "4.1.2" + s.dependency "LaunchDarkly", "4.3.2" end diff --git a/ios/LaunchdarklyReactNativeClient.swift b/ios/LaunchdarklyReactNativeClient.swift index 9577b75..12be43e 100644 --- a/ios/LaunchdarklyReactNativeClient.swift +++ b/ios/LaunchdarklyReactNativeClient.swift @@ -6,16 +6,18 @@ import LaunchDarkly class LaunchdarklyReactNativeClient: RCTEventEmitter { private var listenerKeys: [String:LDObserverOwner] = [:] - private let EVENT_PREFIX = "LaunchDarkly--" + private let FLAG_PREFIX = "LaunchDarkly-Flag-" + private let ALL_FLAGS_PREFIX = "LaunchDarkly-All-Flags-" + private let CONNECTION_MODE_PREFIX = "LaunchDarkly-Connection-Mode-" private let ERROR_INIT = "E_INITIALIZE" private let ERROR_IDENTIFY = "E_IDENTIFY" override func supportedEvents() -> [String]! { - return [EVENT_PREFIX] + return [FLAG_PREFIX, ALL_FLAGS_PREFIX, CONNECTION_MODE_PREFIX] } override func constantsToExport() -> [AnyHashable: Any] { - return ["EVENT_PREFIX": EVENT_PREFIX] + return ["FLAG_PREFIX": FLAG_PREFIX, "ALL_FLAGS_PREFIX": ALL_FLAGS_PREFIX, "CONNECTION_MODE_PREFIX": CONNECTION_MODE_PREFIX] } override static func requiresMainQueueSetup() -> Bool { @@ -193,6 +195,67 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { @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 boolVariationDetailFallback(_ flagKey: String, fallback: ObjCBool, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + resolve(LDClient.shared.variationDetail(forKey: flagKey, fallback: fallback.boolValue)) + } + + @objc func intVariationDetailFallback(_ flagKey: String, fallback: Int, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + resolve(LDClient.shared.variationDetail(forKey: flagKey, fallback: fallback)) + } + + @objc func floatVariationDetailFallback(_ flagKey: String, fallback: CGFloat, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + resolve(LDClient.shared.variationDetail(forKey: flagKey, fallback: Double(fallback))) + } + + @objc func stringVariationDetailFallback(_ flagKey: String, fallback: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + resolve(LDClient.shared.variationDetail(forKey: flagKey, fallback: fallback)) + } + + @objc func boolDetailVariation(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let boolFlagValue: EvaluationDetail = LDClient.shared.variationDetail(forKey: flagKey) + resolve(boolFlagValue) + } + + @objc func intDetailVariation(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let intFlagValue: EvaluationDetail = LDClient.shared.variationDetail(forKey: flagKey) + resolve(intFlagValue) + } + + @objc func floatDetailVariation(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let floatFlagValue: EvaluationDetail = LDClient.shared.variationDetail(forKey: flagKey) + resolve(floatFlagValue) + } + + @objc func stringDetailVariation(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let stringFlagValue: EvaluationDetail = LDClient.shared.variationDetail(forKey: flagKey) + resolve(stringFlagValue) + } + + @objc func jsonVariationDetailNone(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let jsonFlagValue: EvaluationDetail?> = LDClient.shared.variationDetail(forKey: flagKey) + resolve(jsonFlagValue) + } + + @objc func jsonVariationDetailNumber(_ flagKey: String, fallback: Double, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + resolve(LDClient.shared.variationDetail(forKey: flagKey, fallback: fallback)) + } + + @objc func jsonVariationDetailBool(_ flagKey: String, fallback: Bool, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + resolve(LDClient.shared.variationDetail(forKey: flagKey, fallback: fallback)) + } + + @objc func jsonVariationDetailString(_ flagKey: String, fallback: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + resolve(LDClient.shared.variationDetail(forKey: flagKey, fallback: fallback)) + } + + @objc func jsonVariationDetailArray(_ flagKey: String, fallback: Array, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + resolve(LDClient.shared.variationDetail(forKey: flagKey, fallback: fallback)) + } + + @objc func jsonVariationDetailObject(_ flagKey: String, fallback: NSDictionary, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + resolve(LDClient.shared.variationDetail(forKey: flagKey, fallback: fallback.swiftDictionary)) + } @objc func trackNumber(_ eventName: String, data: NSNumber) -> Void { try? LDClient.shared.trackEvent(key: eventName, data: data) @@ -217,6 +280,30 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { @objc func track(_ eventName: String) -> Void { try? LDClient.shared.trackEvent(key: eventName) } + + @objc func trackNumberMetricValue(_ eventName: String, data: NSNumber, metricValue: Double) -> Void { + try? LDClient.shared.trackEvent(key: eventName, data: data, metricValue: metricValue) + } + + @objc func trackBoolMetricValue(_ eventName: String, data: ObjCBool, metricValue: Double) -> Void { + try? LDClient.shared.trackEvent(key: eventName, data: data.boolValue, metricValue: metricValue) + } + + @objc func trackStringMetricValue(_ eventName: String, data: String, metricValue: Double) -> Void { + try? LDClient.shared.trackEvent(key: eventName, data: data, metricValue: metricValue) + } + + @objc func trackArrayMetricValue(_ eventName: String, data: NSArray, metricValue: Double) -> Void { + try? LDClient.shared.trackEvent(key: eventName, data: data, metricValue: metricValue) + } + + @objc func trackObjectMetricValue(_ eventName: String, data: NSDictionary, metricValue: Double) -> Void { + try? LDClient.shared.trackEvent(key: eventName, data: data.swiftDictionary, metricValue: metricValue) + } + + @objc func trackMetricValue(_ eventName: String, metricValue: Double) -> Void { + try? LDClient.shared.trackEvent(key: eventName, metricValue: metricValue) + } @objc func setOffline(_ resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { LDClient.shared.setOnline(false) { @@ -246,15 +333,9 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { @objc func identify(_ options: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { let user = userBuild(userConfig: options) if let usr = user { - LDClient.shared.observeFlagsUnchanged(owner: self) { - LDClient.shared.stopObserving(owner: self as LDObserverOwner) + LDClient.shared.identify(user: usr) { resolve(nil) } - LDClient.shared.observeAll(owner: self) {_ in - LDClient.shared.stopObserving(owner: self as LDObserverOwner) - resolve(nil) - } - LDClient.shared.user = usr } else { reject(ERROR_IDENTIFY, "User could not be built using supplied configuration", nil) } @@ -279,24 +360,64 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { } LDClient.shared.observe(keys: [flagKey], owner: flagChangeOwner, handler: { (changedFlags) in if changedFlags[flagKey] != nil { - self.sendEvent(withName: self.EVENT_PREFIX, body: ["flagKey": flagKey]) + self.sendEvent(withName: self.FLAG_PREFIX, body: ["flagKey": flagKey]) } }) } - @objc func unregisterFeatureFlagListener(_ flagKey: String) -> Void { - let flagChangeOwner = flagKey as LDObserverOwner - if listenerKeys[flagKey] == nil { - listenerKeys.removeValue(forKey: flagKey) + private func unregisterListener(_ key: String) -> Void { + let owner = key as LDObserverOwner + if listenerKeys[key] != nil { + listenerKeys.removeValue(forKey: key) } else { return } - LDClient.shared.stopObserving(owner: flagChangeOwner) + LDClient.shared.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 getConnectionInformation(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + resolve(LDClient.shared.getConnectionInformation()) + } + + @objc func registerCurrentConnectionModeListener(_ listenerId: String) -> Void { + let currentConnectionModeOwner = listenerId as LDObserverOwner + if listenerKeys[listenerId] == nil { + listenerKeys.removeValue(forKey: listenerId) + } else { + return + } + LDClient.shared.observeCurrentConnectionMode(owner: currentConnectionModeOwner, handler: { (connectionMode) in + self.sendEvent(withName: self.CONNECTION_MODE_PREFIX, body: ["connectionMode": connectionMode]) + }) + } + + @objc func unregisterCurrentConnectionModeListener(_ listenerId: String) -> Void { + unregisterListener(listenerId) + } + + @objc func registerAllFlagsListener(_ listenerId: String) -> Void { + let flagChangeOwner = listenerId as LDObserverOwner + if listenerKeys[listenerId] == nil { + listenerKeys[listenerId] = flagChangeOwner + } else { + return + } + LDClient.shared.observeAll(owner: flagChangeOwner, handler: { (changedFlags) in + self.sendEvent(withName: self.ALL_FLAGS_PREFIX, body: ["flagKeys": changedFlags.description]) + }) + } + + @objc func unregisterAllFlagsListener(_ listenerId: String) -> Void { + unregisterListener(listenerId) + } } extension NSDictionary { diff --git a/ios/LaunchdarklyReactNativeClientBridge.m b/ios/LaunchdarklyReactNativeClientBridge.m index 01ce8fa..cdcbd3b 100644 --- a/ios/LaunchdarklyReactNativeClientBridge.m +++ b/ios/LaunchdarklyReactNativeClientBridge.m @@ -33,6 +33,34 @@ @interface RCT_EXTERN_MODULE(LaunchdarklyReactNativeClient, RCTEventEmitter) RCT_EXTERN_METHOD(jsonVariationObject:(NSString *)flagKey fallback:(NSDictionary *)fallback resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(boolVariationDetailFallback:(NSString *)flagKey fallback:(BOOL *)fallback resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(intVariationDetailFallback:(NSString *)flagKey fallback:(NSInteger *)fallback resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(floatVariationDetailFallback:(NSString *)flagKey fallback:(CGFloat *)fallback resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(stringVariationDetailFallback:(NSString *)flagKey fallback:(NSString *)fallback resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(boolVariationDetail:(NSString *)flagKey resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(intVariationDetail:(NSString *)flagKey resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(floatVariationDetail:(NSString *)flagKey resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(stringVariationDetail:(NSString *)flagKey resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) + +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(jsonVariationDetailBool:(NSString *)flagKey fallback:(BOOL *)fallback resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(jsonVariationDetailString:(NSString *)flagKey fallback:(NSString *)fallback resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(jsonVariationDetailArray:(NSString *)flagKey fallback:(NSArray *)fallback resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(jsonVariationDetailObject:(NSString *)flagKey fallback:(NSDictionary *)fallback resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) + RCT_EXTERN_METHOD(trackBool:(NSString *)eventName data:(BOOL *)data) RCT_EXTERN_METHOD(trackArray:(NSString *)eventName data:(NSArray *)data) @@ -45,6 +73,18 @@ @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(trackArrayMetricValue:(NSString *)eventName data:(NSArray *)data metricValue:(NSNumber *)metricValue) + +RCT_EXTERN_METHOD(trackNumberMetricValue:(NSString *)eventName data:(NSNumber *)data metricValue:(NSNumber *)metricValue) + +RCT_EXTERN_METHOD(trackStringMetricValue:(NSString *)eventName data:(NSString *)data metricValue:(NSNumber *)metricValue) + +RCT_EXTERN_METHOD(trackObjectMetricValue:(NSString *)eventName data:(NSDictionary *)data metricValue:(NSNumber *)metricValue) + +RCT_EXTERN_METHOD(trackMetricValue:(NSString *)eventName metricValue:(NSNumber *)metricValue) + RCT_EXTERN_METHOD(setOffline:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) RCT_EXTERN_METHOD(isOffline:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) @@ -65,4 +105,14 @@ @interface RCT_EXTERN_MODULE(LaunchdarklyReactNativeClient, RCTEventEmitter) 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) + +RCT_EXTERN_METHOD(registerAllFlagsListener:(NSString *)listenerId) + +RCT_EXTERN_METHOD(unregisterAllFlagsListener:(NSString *)listenerId) + @end diff --git a/package.json b/package.json index 8d3b962..45dd30c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "launchdarkly-react-native-client-sdk", - "version": "2.0.3", + "version": "2.1.0", "description": "", "main": "index.js", "scripts": { From 1e96bc5e46997112d826ca71830330f935c51794 Mon Sep 17 00:00:00 2001 From: Ben Woskow <48036130+bwoskow-ld@users.noreply.github.com> Date: Fri, 24 Jan 2020 11:57:43 -0800 Subject: [PATCH 2/3] script to update podspec version during releases (#31) --- .ldrelease/update-version.sh | 4 ++++ 1 file changed, 4 insertions(+) create mode 100755 .ldrelease/update-version.sh diff --git a/.ldrelease/update-version.sh b/.ldrelease/update-version.sh new file mode 100755 index 0000000..dc35c0d --- /dev/null +++ b/.ldrelease/update-version.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +sed -i.bak "s/\( *\)s\.version\( *\)=\( *\)\".*\"/\1s\.version\2=\3\"${LD_RELEASE_VERSION}\"/" ios/LaunchdarklyReactNativeClient.podspec +rm -f ios/LaunchdarklyReactNativeClient.podspec.bak From b94d7060fd3be9f9deaf33351063f4237457963c Mon Sep 17 00:00:00 2001 From: Joe Cieslik <5600929+torchhound@users.noreply.github.com> Date: Fri, 24 Jan 2020 19:13:23 -0500 Subject: [PATCH 3/3] Version 2.2.0 (#32) * Changed start to startCompleteWhenFlagsReceived, serialize EvaluationDetail to NSDictionary, bump version numbers * Added evaluationReasons config option, removed unnecessary iOS serialization * Fixed Android config enum syntax error * Serialize iOS and Android EvaluationDetail * Parsing object to JSON to JSONObject to WriteableMap in Android native * Fixed jsonString type * Revert version number bump * Added static Gson object, simplified EvaluationDetail parsing on Android native * Remove invalid closing p tag from Android native JavaDoc --- README.md | 2 +- .../LaunchdarklyReactNativeClientModule.java | 43 ++--- ios/LaunchdarklyReactNativeClient.podspec | 2 +- ios/LaunchdarklyReactNativeClient.swift | 147 ++++++++++++++---- 4 files changed, 139 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 45af4dc..462f328 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ LaunchDarkly overview Supported versions ------------------------- -This SDK is compatible with React Native 0.61.2 and Xcode 10.2.1 and is tested in Android 27 and iOS 12.2. Earlier versions of this SDK are compatible with prior versions of React Native, Android, and iOS. +This SDK is compatible with React Native 0.61.2 and Xcode 10.2.1 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. Getting started --------------- diff --git a/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java b/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java index e66c2bc..7a5dbc3 100644 --- a/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java +++ b/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java @@ -64,7 +64,6 @@ public class LaunchdarklyReactNativeClientModule extends ReactContextBaseJavaMod * 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"), @@ -80,7 +79,8 @@ enum ConfigMapping { 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_PRIVATE_ATTRIBUTES("privateAttributeNames", ConfigEntryType.StringSet, "setPrivateAttributeNames"), + CONFIG_EVALUATION_REASONS("evaluationReasons", ConfigEntryType.Boolean, "setEvaluationReasons"); final String key; final ConfigEntryType type; @@ -115,7 +115,6 @@ void loadFromMap(ReadableMap map, LDConfig.Builder builder) { * 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), @@ -164,6 +163,8 @@ void loadFromMap(ReadableMap map, LDUser.Builder builder, Set privateAtt private Map connectionModeListeners = new HashMap<>(); private Map allFlagsListeners = new HashMap<>(); + private static Gson gson = new Gson(); + public LaunchdarklyReactNativeClientModule(ReactApplicationContext reactContext) { super(reactContext); } @@ -267,7 +268,6 @@ public void run() { * *

* 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 @@ -287,7 +287,6 @@ private LDConfig.Builder configBuild(ReadableMap options) { * *

* 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 @@ -567,7 +566,10 @@ public void boolVariationDetail(String flagKey, Promise promise) { @ReactMethod public void boolVariationDetailFallback(String flagKey, Boolean fallback, Promise promise) { try { - promise.resolve(ldClient.boolVariationDetail(flagKey, fallback)); + EvaluationDetail detailResult = ldClient.boolVariationDetail(flagKey, fallback); + JsonObject jsonObject = gson.toJsonTree(detailResult).getAsJsonObject(); + WritableMap detailMap = fromJsonObject(jsonObject); + promise.resolve(detailMap); } catch (Exception e) { promise.resolve(fallback); } @@ -581,7 +583,10 @@ public void intVariationDetail(String flagKey, Promise promise) { @ReactMethod public void intVariationDetailFallback(String flagKey, Integer fallback, Promise promise) { try { - promise.resolve(ldClient.intVariationDetail(flagKey, fallback)); + EvaluationDetail detailResult = ldClient.intVariationDetail(flagKey, fallback); + JsonObject jsonObject = gson.toJsonTree(detailResult).getAsJsonObject(); + WritableMap detailMap = fromJsonObject(jsonObject); + promise.resolve(detailMap); } catch (Exception e) { promise.resolve(fallback); } @@ -595,7 +600,10 @@ public void floatVariationDetail(String flagKey, Promise promise) { @ReactMethod public void floatVariationDetailFallback(String flagKey, Float fallback, Promise promise) { try { - promise.resolve(ldClient.floatVariationDetail(flagKey, fallback)); + EvaluationDetail detailResult = ldClient.floatVariationDetail(flagKey, fallback); + JsonObject jsonObject = gson.toJsonTree(detailResult).getAsJsonObject(); + WritableMap detailMap = fromJsonObject(jsonObject); + promise.resolve(detailMap); } catch (Exception e) { promise.resolve(fallback); } @@ -609,7 +617,10 @@ public void stringVariationDetail(String flagKey, Promise promise) { @ReactMethod public void stringVariationDetailFallback(String flagKey, String fallback, Promise promise) { try { - promise.resolve(ldClient.stringVariationDetail(flagKey, fallback)); + EvaluationDetail detailResult = ldClient.stringVariationDetail(flagKey, fallback); + JsonObject jsonObject = gson.toJsonTree(detailResult).getAsJsonObject(); + WritableMap detailMap = fromJsonObject(jsonObject); + promise.resolve(detailMap); } catch (Exception e) { promise.resolve(fallback); } @@ -762,7 +773,6 @@ public void allFlags(Promise promise) { *

* 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 @@ -777,7 +787,6 @@ public void trackNumber(String eventName, Double 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 @@ -792,7 +801,6 @@ public void trackBool(String eventName, Boolean 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 @@ -807,7 +815,6 @@ public void trackString(String eventName, String 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 @@ -822,7 +829,6 @@ public void trackArray(String eventName, ReadableArray 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 @@ -1027,7 +1033,7 @@ public void registerCurrentConnectionModeListener(String listenerId) { @Override public void onConnectionModeChanged(ConnectionInformation connectionInfo) { WritableMap result = Arguments.createMap(); - result.putString("connectionMode", new Gson().toJson(connectionInfo)); + result.putString("connectionMode", gson.toJson(connectionInfo)); getReactApplicationContext() .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) @@ -1056,7 +1062,7 @@ public void registerAllFlagsListener(String listenerId) { @Override public void onChange(List flagKeys) { WritableMap result = Arguments.createMap(); - result.putString("flagKeys", new Gson().toJson(flagKeys)); + result.putString("flagKeys", gson.toJson(flagKeys)); getReactApplicationContext() .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) @@ -1081,7 +1087,6 @@ public void unregisterAllFlagsListener(String listenerId) { *

* 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. @@ -1128,7 +1133,6 @@ private static JsonObject toJsonObject(ReadableMap readableMap) { *

* 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 @@ -1172,7 +1176,6 @@ private static JsonArray toJsonArray(ReadableArray readableArray) { *

* 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 @@ -1209,7 +1212,6 @@ private static WritableArray fromJsonArray(JsonArray jsonArray) { *

* 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 @@ -1265,7 +1267,6 @@ interface ConvertFromReadable { * 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) { diff --git a/ios/LaunchdarklyReactNativeClient.podspec b/ios/LaunchdarklyReactNativeClient.podspec index f5ae839..d0c8bca 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.3.2" + s.dependency "LaunchDarkly", "4.4.0" end diff --git a/ios/LaunchdarklyReactNativeClient.swift b/ios/LaunchdarklyReactNativeClient.swift index 4af69d0..697d2a3 100644 --- a/ios/LaunchdarklyReactNativeClient.swift +++ b/ios/LaunchdarklyReactNativeClient.swift @@ -30,7 +30,7 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { let user = userBuild(userConfig: userConfig) if config != nil && user != nil { - LDClient.shared.start(config: config!, user: user, completion: {() -> Void in + LDClient.shared.startCompleteWhenFlagsReceived(config: config!, user: user, completion: {() -> Void in resolve(nil)}) } } @@ -96,6 +96,10 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { if config["debugMode"] != nil { ldConfig.isDebugMode = config["debugMode"] as! Bool } + + if config["evaluationReasons"] != nil { + ldConfig.evaluationReasons = config["evaluationReasons"] as! Bool + } return ldConfig } @@ -201,64 +205,143 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { } @objc func boolVariationDetailFallback(_ flagKey: String, fallback: ObjCBool, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.shared.variationDetail(forKey: flagKey, fallback: fallback.boolValue)) + let detail = LDClient.shared.variationDetail(forKey: flagKey, fallback: fallback.boolValue) + let jsonObject: NSDictionary = [ + "value": detail.value, + "variationIndex": detail.variationIndex, + "reason": detail.reason + ] + resolve(jsonObject) } @objc func intVariationDetailFallback(_ flagKey: String, fallback: Int, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.shared.variationDetail(forKey: flagKey, fallback: fallback)) + let detail = LDClient.shared.variationDetail(forKey: flagKey, fallback: fallback) + let jsonObject: NSDictionary = [ + "value": detail.value, + "variationIndex": detail.variationIndex, + "reason": detail.reason + ] + resolve(jsonObject) } @objc func floatVariationDetailFallback(_ flagKey: String, fallback: CGFloat, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.shared.variationDetail(forKey: flagKey, fallback: Double(fallback))) + let detail = LDClient.shared.variationDetail(forKey: flagKey, fallback: Double(fallback)) + let jsonObject: NSDictionary = [ + "value": detail.value, + "variationIndex": detail.variationIndex, + "reason": detail.reason + ] + resolve(jsonObject) } @objc func stringVariationDetailFallback(_ flagKey: String, fallback: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.shared.variationDetail(forKey: flagKey, fallback: fallback)) - } - - @objc func boolDetailVariation(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let boolFlagValue: EvaluationDetail = LDClient.shared.variationDetail(forKey: flagKey) - resolve(boolFlagValue) - } - - @objc func intDetailVariation(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let intFlagValue: EvaluationDetail = LDClient.shared.variationDetail(forKey: flagKey) - resolve(intFlagValue) - } - - @objc func floatDetailVariation(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let floatFlagValue: EvaluationDetail = LDClient.shared.variationDetail(forKey: flagKey) - resolve(floatFlagValue) - } - - @objc func stringDetailVariation(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let stringFlagValue: EvaluationDetail = LDClient.shared.variationDetail(forKey: flagKey) - resolve(stringFlagValue) + let detail = LDClient.shared.variationDetail(forKey: flagKey, fallback: fallback) + let jsonObject: NSDictionary = [ + "value": detail.value, + "variationIndex": detail.variationIndex, + "reason": detail.reason + ] + resolve(jsonObject) + } + + @objc func boolVariationDetail(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let detail: EvaluationDetail = LDClient.shared.variationDetail(forKey: flagKey) + let jsonObject: NSDictionary = [ + "value": detail.value, + "variationIndex": detail.variationIndex, + "reason": detail.reason + ] + resolve(jsonObject) + } + + @objc func intVariationDetail(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let detail: EvaluationDetail = LDClient.shared.variationDetail(forKey: flagKey) + let jsonObject: NSDictionary = [ + "value": detail.value, + "variationIndex": detail.variationIndex, + "reason": detail.reason + ] + resolve(jsonObject) + } + + @objc func floatVariationDetail(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let detail: EvaluationDetail = LDClient.shared.variationDetail(forKey: flagKey) + let jsonObject: NSDictionary = [ + "value": detail.value, + "variationIndex": detail.variationIndex, + "reason": detail.reason + ] + resolve(jsonObject) + } + + @objc func stringVariationDetail(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { + let detail: EvaluationDetail = LDClient.shared.variationDetail(forKey: flagKey) + let jsonObject: NSDictionary = [ + "value": detail.value, + "variationIndex": detail.variationIndex, + "reason": detail.reason + ] + resolve(jsonObject) } @objc func jsonVariationDetailNone(_ flagKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - let jsonFlagValue: EvaluationDetail?> = LDClient.shared.variationDetail(forKey: flagKey) - resolve(jsonFlagValue) + let detail: EvaluationDetail?> = LDClient.shared.variationDetail(forKey: flagKey) + let jsonObject: NSDictionary = [ + "value": detail.value, + "variationIndex": detail.variationIndex, + "reason": detail.reason + ] + resolve(jsonObject) } @objc func jsonVariationDetailNumber(_ flagKey: String, fallback: Double, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.shared.variationDetail(forKey: flagKey, fallback: fallback)) + let detail = LDClient.shared.variationDetail(forKey: flagKey, fallback: fallback) + let jsonObject: NSDictionary = [ + "value": detail.value, + "variationIndex": detail.variationIndex, + "reason": detail.reason + ] + resolve(jsonObject) } @objc func jsonVariationDetailBool(_ flagKey: String, fallback: Bool, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.shared.variationDetail(forKey: flagKey, fallback: fallback)) + let detail = LDClient.shared.variationDetail(forKey: flagKey, fallback: fallback) + let jsonObject: NSDictionary = [ + "value": detail.value, + "variationIndex": detail.variationIndex, + "reason": detail.reason + ] + resolve(jsonObject) } @objc func jsonVariationDetailString(_ flagKey: String, fallback: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.shared.variationDetail(forKey: flagKey, fallback: fallback)) + let detail = LDClient.shared.variationDetail(forKey: flagKey, fallback: fallback) + let jsonObject: NSDictionary = [ + "value": detail.value, + "variationIndex": detail.variationIndex, + "reason": detail.reason + ] + resolve(jsonObject) } @objc func jsonVariationDetailArray(_ flagKey: String, fallback: Array, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.shared.variationDetail(forKey: flagKey, fallback: fallback)) + let detail = LDClient.shared.variationDetail(forKey: flagKey, fallback: fallback) + let jsonObject: NSDictionary = [ + "value": detail.value, + "variationIndex": detail.variationIndex, + "reason": detail.reason + ] + resolve(jsonObject) } @objc func jsonVariationDetailObject(_ flagKey: String, fallback: NSDictionary, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { - resolve(LDClient.shared.variationDetail(forKey: flagKey, fallback: fallback.swiftDictionary)) + let detail = LDClient.shared.variationDetail(forKey: flagKey, fallback: fallback.swiftDictionary) + let jsonObject: NSDictionary = [ + "value": detail.value, + "variationIndex": detail.variationIndex, + "reason": detail.reason + ] + resolve(jsonObject) } @objc func trackNumber(_ eventName: String, data: NSNumber) -> Void {