diff --git a/.circleci/config.yml b/.circleci/config.yml index 832349c..721e407 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -138,7 +138,7 @@ workflows: name: rn<>-xc<>-build-apps-using-template matrix: parameters: - rn-version: ["0.64.3", "0.65.2", "0.66.4", "0.67.2"] + rn-version: ["0.64.3", "0.65.2", "0.66.4", "0.67.3"] xcode-version: ["12.2.0", "12.5.1", "13.2.1"] requires: - test-javascript diff --git a/ManualTestApp/README.md b/ManualTestApp/README.md index 14218e6..d7362ea 100644 --- a/ManualTestApp/README.md +++ b/ManualTestApp/README.md @@ -1,3 +1,3 @@ ## Manual testing application -Add your mobile key in `App.js`, `npx pod-install`, `yarn run `. +Add your mobile key in `App.js`, `npx yarn install`, `npx pod-install`, `npx yarn run `. diff --git a/ManualTestApp/yarn.lock b/ManualTestApp/yarn.lock index 9af434c..acfc270 100644 --- a/ManualTestApp/yarn.lock +++ b/ManualTestApp/yarn.lock @@ -3137,7 +3137,7 @@ kleur@^3.0.3: integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== "launchdarkly-react-native-client-sdk@file:..": - version "5.1.1" + version "6.0.0" leven@^3.1.0: version "3.1.0" diff --git a/android/build.gradle b/android/build.gradle index e106f95..c3ff29e 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -45,7 +45,7 @@ allprojects { dependencies { implementation("com.facebook.react:react-native:+") - implementation("com.launchdarkly:launchdarkly-android-client-sdk:3.1.2") + implementation("com.launchdarkly:launchdarkly-android-client-sdk:3.1.3") implementation("com.jakewharton.timber:timber:5.0.1") implementation("com.google.code.gson:gson:2.8.9") } diff --git a/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java b/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java index a3cdb1b..5797202 100644 --- a/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java +++ b/android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientModule.java @@ -187,6 +187,16 @@ public Map getConstants() { return constants; } + @ReactMethod + public void addListener(String eventName) { + // Keep: Required for RN built in Event Emitter Calls. + } + + @ReactMethod + public void removeListeners(Integer count) { + // Keep: Required for RN built in Event Emitter Calls. + } + @ReactMethod public void configure(ReadableMap config, ReadableMap user, final Promise promise) { internalConfigure(config, user, null, promise); diff --git a/index.d.ts b/index.d.ts index 6a5b7ad..9adf329 100644 --- a/index.d.ts +++ b/index.d.ts @@ -189,6 +189,14 @@ declare module 'launchdarkly-react-native-client-sdk' { * that provides the full details for the user. */ inlineUsersInEvents?: boolean; + + /** + * The names of user attributes that should be marked as private, and not sent to + * LaunchDarkly in analytics events. + * + * You can also specify this on a per-user basis with [[LDUser.privateAttributeNames]]. + */ + privateAttributeNames?: string[] }; /** diff --git a/ios/LaunchdarklyReactNativeClient.swift b/ios/LaunchdarklyReactNativeClient.swift index 19042d4..11df989 100644 --- a/ios/LaunchdarklyReactNativeClient.swift +++ b/ios/LaunchdarklyReactNativeClient.swift @@ -3,15 +3,17 @@ import LaunchDarkly @objc(LaunchdarklyReactNativeClient) class LaunchdarklyReactNativeClient: RCTEventEmitter { - private var listenerKeys: [String:LDObserverOwner] = [:] - 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" private let ERROR_UNKNOWN = "E_UNKNOWN" - + + private var flagListenerOwners: [String: ObserverOwner] = [:] + private var allFlagsListenerOwners: [String: ObserverOwner] = [:] + private var connectionModeListenerOwners: [String: ObserverOwner] = [:] + override func supportedEvents() -> [String]! { return [FLAG_PREFIX, ALL_FLAGS_PREFIX, CONNECTION_MODE_PREFIX] } @@ -89,6 +91,7 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { configField(&ldConfig.allUserAttributesPrivate, config["allUserAttributesPrivate"], id) configField(&ldConfig.autoAliasingOptOut, config["autoAliasingOptOut"], id) configField(&ldConfig.inlineUserInEvents, config["inlineUsersInEvents"], id) + configField(&ldConfig.privateUserAttributes, config["privateAttributeNames"], id) if let val = config["secondaryMobileKeys"] as? [String: String] { try! ldConfig.setSecondaryMobileKeys(val) @@ -382,53 +385,56 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { @objc func registerFeatureFlagListener(_ flagKey: String, environment: String) -> Void { 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 + let owner = ObserverOwner() + flagListenerOwners[multiListenerId] = owner + LDClient.get(environment: environment)!.observe(key: flagKey, owner: owner) { 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 multiListenerId = envConcat(environment: environment, identifier: key) - let owner = multiListenerId as LDObserverOwner - if listenerKeys.removeValue(forKey: multiListenerId) != nil { - LDClient.get(environment: environment)!.stopObserving(owner: owner) } } @objc func unregisterFeatureFlagListener(_ flagKey: String, environment: String) -> Void { - unregisterListener(flagKey, environment) + let multiListenerId = envConcat(environment: environment, identifier: flagKey) + if let owner = flagListenerOwners.removeValue(forKey: multiListenerId) { + LDClient.get(environment: environment)!.stopObserving(owner: owner) + } } @objc func registerCurrentConnectionModeListener(_ listenerId: String, environment: String) -> Void { let multiListenerId = envConcat(environment: environment, identifier: listenerId) - let currentConnectionModeOwner = multiListenerId as LDObserverOwner - LDClient.get(environment: environment)!.observeCurrentConnectionMode(owner: currentConnectionModeOwner, handler: { connectionMode in + let owner = ObserverOwner() + connectionModeListenerOwners[multiListenerId] = owner + LDClient.get(environment: environment)!.observeCurrentConnectionMode(owner: owner) { connectionMode in if self.bridge != nil { self.sendEvent(withName: self.CONNECTION_MODE_PREFIX, body: ["connectionMode": connectionMode, "listenerId": multiListenerId]) } - }) + } } @objc func unregisterCurrentConnectionModeListener(_ listenerId: String, environment: String) -> Void { - unregisterListener(listenerId, environment) + let multiListenerId = envConcat(environment: environment, identifier: listenerId) + if let owner = connectionModeListenerOwners.removeValue(forKey: multiListenerId) { + LDClient.get(environment: environment)!.stopObserving(owner: owner) + } } @objc func registerAllFlagsListener(_ listenerId: String, environment: String) -> Void { let multiListenerId = envConcat(environment: environment, identifier: listenerId) - let flagChangeOwner = multiListenerId as LDObserverOwner - LDClient.get(environment: environment)!.observeAll(owner: flagChangeOwner, handler: { changedFlags in + let owner = ObserverOwner() + allFlagsListenerOwners[multiListenerId] = owner + LDClient.get(environment: environment)!.observeAll(owner: owner) { changedFlags in if self.bridge != nil { self.sendEvent(withName: self.ALL_FLAGS_PREFIX, body: ["flagKeys": Array(changedFlags.keys), "listenerId": multiListenerId]) } - }) + } } @objc func unregisterAllFlagsListener(_ listenerId: String, environment: String) -> Void { - unregisterListener(listenerId, environment) + let multiListenerId = envConcat(environment: environment, identifier: listenerId) + if let owner = allFlagsListenerOwners.removeValue(forKey: multiListenerId) { + LDClient.get(environment: environment)!.stopObserving(owner: owner) + } } @objc func isInitialized(_ environment: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void { @@ -442,6 +448,8 @@ class LaunchdarklyReactNativeClient: RCTEventEmitter { } } +class ObserverOwner{} + extension NSDictionary { @objc var swiftDictionary: Dictionary { var swiftDictionary = Dictionary() diff --git a/test-types.ts b/test-types.ts index a9ea90e..34b7da1 100644 --- a/test-types.ts +++ b/test-types.ts @@ -48,6 +48,7 @@ async function tests() { allUserAttributesPrivate: true, autoAliasingOptOut: true, inlineUsersInEvents: true, + privateAttributeNames: ['abc', 'def'], }; const userEmpty: LDUser = {}; const userWithKeyOnly: LDUser = { key: 'user' };