├── .gitattributes ├── ManualTestApp ├── .watchmanconfig ├── jest.config.js ├── .bundle │ └── config ├── app.json ├── types │ └── env.d.ts ├── .eslintrc.js ├── android │ ├── app │ │ ├── src │ │ │ ├── main │ │ │ │ ├── res │ │ │ │ │ ├── values │ │ │ │ │ │ ├── strings.xml │ │ │ │ │ │ └── styles.xml │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ └── drawable │ │ │ │ │ │ └── rn_edit_text_material.xml │ │ │ │ ├── java │ │ │ │ │ └── com │ │ │ │ │ │ └── manualtestapp │ │ │ │ │ │ ├── MainActivity.kt │ │ │ │ │ │ └── MainApplication.kt │ │ │ │ └── AndroidManifest.xml │ │ │ └── debug │ │ │ │ └── AndroidManifest.xml │ │ ├── debug.keystore │ │ ├── proguard-rules.pro │ │ └── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── settings.gradle │ ├── build.gradle │ ├── gradle.properties │ ├── gradlew.bat │ └── gradlew ├── ios │ ├── ManualTestApp │ │ ├── Images.xcassets │ │ │ ├── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── AppDelegate.h │ │ ├── main.m │ │ ├── AppDelegate.mm │ │ ├── Info.plist │ │ └── LaunchScreen.storyboard │ ├── ManualTestApp.xcworkspace │ │ └── contents.xcworkspacedata │ ├── .xcode.env │ ├── ManualTestAppTests │ │ ├── Info.plist │ │ └── ManualTestAppTests.m │ ├── Podfile │ └── ManualTestApp.xcodeproj │ │ └── xcshareddata │ │ └── xcschemes │ │ └── ManualTestApp.xcscheme ├── tsconfig.json ├── .prettierrc.js ├── Gemfile ├── index.js ├── babel.config.js ├── metro.config.js ├── README.md ├── .gitignore ├── package.json ├── clean-all.sh └── App.tsx ├── android ├── gradle.properties ├── settings.gradle ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── launchdarkly │ │ │ └── reactnative │ │ │ ├── LaunchdarklyReactNativeClientPackage.java │ │ │ └── utils │ │ │ └── LDUtil.java │ └── test │ │ └── java │ │ └── LDUtilTest.java ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── consumer-proguard-rules.pro ├── build.gradle ├── gradlew.bat └── gradlew ├── CODEOWNERS ├── babel.config.js ├── .prettierrc ├── .prettierignore ├── tsconfig.json ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.md │ └── bug_report.md ├── workflows │ ├── stale.yml │ └── main.yml └── pull_request_template.md ├── ios ├── LaunchdarklyReactNativeClient.xcworkspace │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── contents.xcworkspacedata ├── LaunchdarklyReactNativeClient-Bridging-Header.h ├── Podfile ├── LaunchdarklyReactNativeClient.xcodeproj │ └── xcshareddata │ │ └── xcschemes │ │ ├── Tests.xcscheme │ │ └── LaunchdarklyReactNativeClient.xcscheme ├── LaunchdarklyReactNativeClientBridge.m ├── Tests │ └── Tests.swift └── LaunchdarklyReactNativeClient.swift ├── docs └── typedoc.js ├── typedoc.js ├── .ldrelease └── config.yml ├── SECURITY.md ├── modd-android.conf ├── LICENSE ├── modd-ios.conf ├── .gitignore ├── launchdarkly-react-native-client-sdk.podspec ├── modd.conf ├── src ├── contextUtils.ts └── contextUtils.test.ts ├── link-dev.sh ├── CONTRIBUTING.md ├── clean-all.sh ├── __mocks__ └── native.js ├── package.json ├── .circleci └── config.yml ├── README.md ├── test-types.ts ├── index.js └── index.test.js /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text -------------------------------------------------------------------------------- /ManualTestApp/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | android.useAndroidX=true 2 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @yusinto @louis-launchdarkly @tanderson-ld 2 | -------------------------------------------------------------------------------- /ManualTestApp/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'react-native', 3 | }; 4 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | includeBuild('../node_modules/@react-native/gradle-plugin') 2 | -------------------------------------------------------------------------------- /ManualTestApp/.bundle/config: -------------------------------------------------------------------------------- 1 | BUNDLE_PATH: "vendor/bundle" 2 | BUNDLE_FORCE_RUBY_PLATFORM: 1 3 | -------------------------------------------------------------------------------- /ManualTestApp/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ManualTestApp", 3 | "displayName": "ManualTestApp" 4 | } 5 | -------------------------------------------------------------------------------- /ManualTestApp/types/env.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@env' { 2 | export const MOBILE_KEY: string; 3 | } 4 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:@react-native/babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /ManualTestApp/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: '@react-native', 4 | }; 5 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 120, 5 | "arrowParens": "always" 6 | } 7 | -------------------------------------------------------------------------------- /ManualTestApp/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ManualTestApp 3 | 4 | -------------------------------------------------------------------------------- /ManualTestApp/ios/ManualTestApp/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | **/android/app/build 3 | **/ios/build 4 | **/ios/Pods 5 | **/Images.xcassets 6 | package-lock.json 7 | yarn.lock 8 | -------------------------------------------------------------------------------- /ManualTestApp/android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/launchdarkly/react-native-client-sdk/HEAD/ManualTestApp/android/app/debug.keystore -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/launchdarkly/react-native-client-sdk/HEAD/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /ManualTestApp/ios/ManualTestApp/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : RCTAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /ManualTestApp/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@react-native/typescript-config/tsconfig.json", 3 | "compilerOptions": { 4 | "typeRoots": ["./types"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ManualTestApp/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/launchdarkly/react-native-client-sdk/HEAD/ManualTestApp/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /ManualTestApp/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: 'avoid', 3 | bracketSameLine: true, 4 | bracketSpacing: false, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | }; 8 | -------------------------------------------------------------------------------- /ManualTestApp/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/launchdarkly/react-native-client-sdk/HEAD/ManualTestApp/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /ManualTestApp/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/launchdarkly/react-native-client-sdk/HEAD/ManualTestApp/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /ManualTestApp/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/launchdarkly/react-native-client-sdk/HEAD/ManualTestApp/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ManualTestApp/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/launchdarkly/react-native-client-sdk/HEAD/ManualTestApp/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ManualTestApp/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/launchdarkly/react-native-client-sdk/HEAD/ManualTestApp/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ManualTestApp/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/launchdarkly/react-native-client-sdk/HEAD/ManualTestApp/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /ManualTestApp/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/launchdarkly/react-native-client-sdk/HEAD/ManualTestApp/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /ManualTestApp/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/launchdarkly/react-native-client-sdk/HEAD/ManualTestApp/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /ManualTestApp/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/launchdarkly/react-native-client-sdk/HEAD/ManualTestApp/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /ManualTestApp/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/launchdarkly/react-native-client-sdk/HEAD/ManualTestApp/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/react-native/tsconfig.json", 3 | "compilerOptions": { 4 | "lib": ["es6"] 5 | }, 6 | "include": ["src/**/*"], 7 | "files": ["index.d.ts", "test-types.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /ManualTestApp/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version 4 | ruby ">= 2.6.10" 5 | 6 | gem 'cocoapods', '~> 1.13' 7 | gem 'activesupport', '>= 6.1.7.3', '< 7.1.0' 8 | -------------------------------------------------------------------------------- /ManualTestApp/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import {AppRegistry} from 'react-native'; 6 | import App from './App'; 7 | import {name as appName} from './app.json'; 8 | 9 | AppRegistry.registerComponent(appName, () => App); 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Support request 4 | url: https://support.launchdarkly.com/hc/en-us/requests/new 5 | about: File your support requests with LaunchDarkly's support team 6 | -------------------------------------------------------------------------------- /ManualTestApp/ios/ManualTestApp/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | @autoreleasepool { 8 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ManualTestApp/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:@react-native/babel-preset'], 3 | plugins: [ 4 | [ 5 | 'module:react-native-dotenv', 6 | { 7 | safe: true, 8 | allowUndefined: false, 9 | }, 10 | ], 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Jan 26 10:47:00 PST 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: 'Close stale issues and PRs' 2 | on: 3 | workflow_dispatch: 4 | schedule: 5 | # Happen once per day at 1:30 AM 6 | - cron: '30 1 * * *' 7 | 8 | jobs: 9 | sdk-close-stale: 10 | uses: launchdarkly/gh-actions/.github/workflows/sdk-stale.yml@main 11 | -------------------------------------------------------------------------------- /ManualTestApp/android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'ManualTestApp' 2 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) 3 | include ':app' 4 | includeBuild('../node_modules/@react-native/gradle-plugin') 5 | -------------------------------------------------------------------------------- /ManualTestApp/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /ManualTestApp/ios/ManualTestApp.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/LaunchdarklyReactNativeClient.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/LaunchdarklyReactNativeClient-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import 6 | #import 7 | #import 8 | #import 9 | #import 10 | -------------------------------------------------------------------------------- /ios/LaunchdarklyReactNativeClient.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ManualTestApp/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /ManualTestApp/metro.config.js: -------------------------------------------------------------------------------- 1 | const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config'); 2 | 3 | /** 4 | * Metro configuration 5 | * https://facebook.github.io/metro/docs/configuration 6 | * 7 | * @type {import('metro-config').MetroConfig} 8 | */ 9 | const config = {}; 10 | 11 | module.exports = mergeConfig(getDefaultConfig(__dirname), config); 12 | -------------------------------------------------------------------------------- /docs/typedoc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | out: '/tmp/project-releaser/project/docs/build/html', 3 | exclude: ['**/node_modules/**', 'test-types.ts'], 4 | name: 'LaunchDarkly Client-Side SDK for React Native (7.0.0)', 5 | readme: 'none', // don't add a home page with a copy of README.md 6 | entryPoints: '/tmp/project-releaser/project/index.d.ts', 7 | entryPointStrategy: 'expand', 8 | }; 9 | -------------------------------------------------------------------------------- /ManualTestApp/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /ManualTestApp/README.md: -------------------------------------------------------------------------------- 1 | ## Example app 2 | 3 | To run the example app: 4 | 5 | 1. Ensure `yarn doctor` are all green. 6 | 2. Create a `.env` file at the same level as this README 7 | 3. Add your mobile key to that `.env` file: 8 | 9 | ```shell 10 | MOBILE_KEY=mob-xxx 11 | ``` 12 | 13 | 4. Finally 14 | 15 | ```shell 16 | # android 17 | yarn && yarn android 18 | 19 | # ios 20 | yarn && npx pod-install && yarn ios 21 | ``` 22 | -------------------------------------------------------------------------------- /typedoc.js: -------------------------------------------------------------------------------- 1 | let version = process.env.VERSION; 2 | if (!version) { 3 | const package = require('./package.json'); 4 | version = package.version; 5 | } 6 | 7 | module.exports = { 8 | out: './docs/build/html', 9 | exclude: ['**/node_modules/**', 'test-types.ts'], 10 | name: 'launchdarkly-react-native-client-sdk (' + version + ')', 11 | readme: 'none', // don't add a home page with a copy of README.md 12 | entryPoints: 'index.d.ts', 13 | }; 14 | -------------------------------------------------------------------------------- /ManualTestApp/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | -------------------------------------------------------------------------------- /.ldrelease/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | branches: 4 | - name: main 5 | description: 9.x 6 | - name: 8.x 7 | - name: 7.x 8 | - name: 6.x 9 | 10 | publications: 11 | - url: https://www.npmjs.com/package/launchdarkly-react-native-client-sdk 12 | description: npm 13 | 14 | jobs: 15 | - docker: 16 | image: node:16-buster 17 | template: 18 | name: npm 19 | 20 | documentation: 21 | gitHubPages: true 22 | title: LaunchDarkly Client-Side SDK for React Native 23 | 24 | sdk: 25 | displayName: React Native 26 | -------------------------------------------------------------------------------- /ManualTestApp/ios/.xcode.env: -------------------------------------------------------------------------------- 1 | # This `.xcode.env` file is versioned and is used to source the environment 2 | # used when running script phases inside Xcode. 3 | # To customize your local environment, you can create an `.xcode.env.local` 4 | # file that is not versioned. 5 | 6 | # NODE_BINARY variable contains the PATH to the node executable. 7 | # 8 | # Customize the NODE_BINARY variable here. 9 | # For example, to use nvm with brew, add the following line 10 | # . "$(brew --prefix nvm)/nvm.sh" --no-use 11 | export NODE_BINARY=$(command -v node) 12 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting and Fixing Security Issues 2 | 3 | Please report all security issues to the LaunchDarkly security team by submitting a bug bounty report to our [HackerOne program](https://hackerone.com/launchdarkly?type=team). LaunchDarkly will triage and address all valid security issues following the response targets defined in our program policy. Valid security issues may be eligible for a bounty. 4 | 5 | Please do not open issues or pull requests for security issues. This makes the problem immediately visible to everyone, including potentially malicious actors. 6 | -------------------------------------------------------------------------------- /android/consumer-proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in ~/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | -keep class com.launchdarkly.sdk.android.LDConfig$Builder { public ; } 13 | -------------------------------------------------------------------------------- /modd-android.conf: -------------------------------------------------------------------------------- 1 | android/src/main/**/* index.js src/**/* ManualTestApp/**/*.ts* ManualTestApp/.env { 2 | prep: rsync -av android ManualTestApp/node_modules/launchdarkly-react-native-client-sdk --exclude .gradle --exclude .idea --exclude gradle --exclude src/test 3 | prep: rsync -av src ManualTestApp/node_modules/launchdarkly-react-native-client-sdk 4 | prep: rsync -aq index.js ManualTestApp/node_modules/launchdarkly-react-native-client-sdk 5 | prep: rsync -aq index.d.ts ManualTestApp/node_modules/launchdarkly-react-native-client-sdk 6 | prep: yarn --cwd ManualTestApp android 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Catamorphic, Co. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /ManualTestApp/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | buildToolsVersion = "34.0.0" 4 | minSdkVersion = 21 5 | compileSdkVersion = 34 6 | targetSdkVersion = 34 7 | ndkVersion = "25.1.8937393" 8 | kotlinVersion = "1.8.0" 9 | } 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | dependencies { 15 | classpath("com.android.tools.build:gradle") 16 | classpath("com.facebook.react:react-native-gradle-plugin") 17 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") 18 | } 19 | } 20 | 21 | apply plugin: "com.facebook.react.rootproject" 22 | -------------------------------------------------------------------------------- /modd-ios.conf: -------------------------------------------------------------------------------- 1 | ios/LaunchdarklyReactNativeClient.swift index.js src/**/* ManualTestApp/**/*.ts* ManualTestApp/.env { 2 | prep: rsync -av ios ManualTestApp/node_modules/launchdarkly-react-native-client-sdk --exclude LaunchdarklyReactNativeClient.xcworkspace --exclude build --exclude Pods --exclude Tests --exclude Podfile --exclude Podfile.lock 3 | prep: rsync -av src ManualTestApp/node_modules/launchdarkly-react-native-client-sdk 4 | prep: rsync -aq index.js ManualTestApp/node_modules/launchdarkly-react-native-client-sdk 5 | prep: rsync -aq index.d.ts ManualTestApp/node_modules/launchdarkly-react-native-client-sdk 6 | prep: yarn --cwd ManualTestApp ios 7 | } 8 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | require_relative '../node_modules/react-native/scripts/react_native_pods' 2 | require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' 3 | 4 | platform :ios, '13.4' 5 | 6 | target 'LaunchdarklyReactNativeClient' do 7 | config = use_native_modules! 8 | 9 | use_react_native!( 10 | :path => config[:reactNativePath], 11 | # to enable hermes on iOS, change `false` to `true` and then install pods 12 | :hermes_enabled => false 13 | ) 14 | 15 | post_install do |installer| 16 | react_native_post_install(installer) 17 | end 18 | 19 | pod 'LaunchDarkly', '9.11.0' 20 | 21 | target 'Tests' do 22 | inherit! :complete 23 | end 24 | end 25 | 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I would love to see the SDK [...does something new...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # OSX 3 | # 4 | .DS_Store 5 | 6 | # node.js 7 | # 8 | node_modules/ 9 | npm-debug.log 10 | yarn-error.log 11 | target/ 12 | 13 | # Xcode 14 | # 15 | build/ 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | xcuserdata 25 | *.xccheckout 26 | *.moved-aside 27 | DerivedData 28 | *.hmap 29 | *.ipa 30 | *.xcuserstate 31 | project.xcworkspace 32 | Pods 33 | 34 | # Android/IntelliJ 35 | .idea 36 | .gradle 37 | local.properties 38 | *.iml 39 | org.eclipse* 40 | android/.project 41 | android/.classpath 42 | 43 | # BUCK 44 | buck-out/ 45 | \.buckd/ 46 | *.keystore 47 | 48 | # Auto-generated 49 | test-types.js 50 | 51 | .env 52 | -------------------------------------------------------------------------------- /launchdarkly-react-native-client-sdk.podspec: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | package = JSON.parse(File.read(File.join(__dir__, "package.json"))) 4 | 5 | Pod::Spec.new do |s| 6 | s.name = package["name"] 7 | s.version = package["version"] 8 | s.summary = package["description"] 9 | s.homepage = package["homepage"] 10 | s.license = { :type => "Apache-2.0", :file => "LICENSE" } 11 | s.author = { "author" => "support@launchdarkly.com" } 12 | s.platform = :ios, "13.4" 13 | s.source = { :git => "https://github.com/launchdarkly/react-native-client-sdk.git", :tag => s.version } 14 | s.source_files = "ios/**/*.{h,m,swift}" 15 | s.swift_version = "5.0" 16 | 17 | s.dependency "React-Core" 18 | s.dependency "LaunchDarkly", "9.11.0" 19 | 20 | end 21 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | **Requirements** 2 | 3 | - [ ] I have added test coverage for new or changed functionality 4 | - [ ] I have followed the repository's [pull request submission guidelines](../blob/main/CONTRIBUTING.md#submitting-pull-requests) 5 | - [ ] I have validated my changes against all supported platform versions 6 | 7 | **Related issues** 8 | 9 | Provide links to any issues in this repository or elsewhere relating to this pull request. 10 | 11 | **Describe the solution you've provided** 12 | 13 | Provide a clear and concise description of what you expect to happen. 14 | 15 | **Describe alternatives you've considered** 16 | 17 | Provide a clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | 21 | Add any other context about the pull request here. 22 | -------------------------------------------------------------------------------- /modd.conf: -------------------------------------------------------------------------------- 1 | android/src/main/**/* ios/LaunchdarklyReactNativeClient.swift index.js src/**/* ManualTestApp/**/*.ts* ManualTestApp/.env { 2 | prep: rsync -av android ManualTestApp/node_modules/launchdarkly-react-native-client-sdk --exclude .gradle --exclude build --exclude .idea --exclude gradle --exclude src/test 3 | prep: rsync -av ios ManualTestApp/node_modules/launchdarkly-react-native-client-sdk --exclude LaunchdarklyReactNativeClient.xcworkspace --exclude build --exclude Pods --exclude Tests --exclude Podfile --exclude Podfile.lock 4 | prep: rsync -av src ManualTestApp/node_modules/launchdarkly-react-native-client-sdk 5 | prep: rsync -aq index.js ManualTestApp/node_modules/launchdarkly-react-native-client-sdk 6 | prep: rsync -aq index.d.ts ManualTestApp/node_modules/launchdarkly-react-native-client-sdk 7 | prep: yarn --cwd ManualTestApp android 8 | } 9 | -------------------------------------------------------------------------------- /ManualTestApp/ios/ManualTestAppTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/contextUtils.ts: -------------------------------------------------------------------------------- 1 | import { LDContext } from 'launchdarkly-js-sdk-common'; 2 | 3 | /** 4 | * Returns true if the argument has anonymous true and has no key. 5 | * 6 | * @param LDContext 7 | * @returns {boolean} 8 | */ 9 | export function isAnonymousAndNoKey(context: LDContext) { 10 | const key = context.key?.trim() ?? ''; 11 | const anonymousTrue = context.anonymous === true; 12 | const isKeySpecified = key !== ''; 13 | 14 | return !isKeySpecified && anonymousTrue; 15 | } 16 | 17 | /** 18 | * A basic check to validate if a context is valid. This will be expanded 19 | * to be more thorough in the future. 20 | * 21 | * @param LDContext 22 | * @returns {boolean} 23 | */ 24 | export function validateContext(context: LDContext) { 25 | if (!context || Object.keys(context).length === 0) { 26 | return false; 27 | } 28 | 29 | if (!('kind' in context)) { 30 | return false; 31 | } 32 | 33 | return true; 34 | } 35 | -------------------------------------------------------------------------------- /ManualTestApp/ios/ManualTestApp/AppDelegate.mm: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | 3 | #import 4 | 5 | @implementation AppDelegate 6 | 7 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 8 | { 9 | self.moduleName = @"ManualTestApp"; 10 | // You can add your custom initial props in the dictionary below. 11 | // They will be passed down to the ViewController used by React Native. 12 | self.initialProps = @{}; 13 | 14 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 15 | } 16 | 17 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge 18 | { 19 | return [self getBundleURL]; 20 | } 21 | 22 | - (NSURL *)getBundleURL 23 | { 24 | #if DEBUG 25 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"]; 26 | #else 27 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 28 | #endif 29 | } 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /ManualTestApp/android/app/src/main/java/com/manualtestapp/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.manualtestapp 2 | 3 | import com.facebook.react.ReactActivity 4 | import com.facebook.react.ReactActivityDelegate 5 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled 6 | import com.facebook.react.defaults.DefaultReactActivityDelegate 7 | 8 | class MainActivity : ReactActivity() { 9 | 10 | /** 11 | * Returns the name of the main component registered from JavaScript. This is used to schedule 12 | * rendering of the component. 13 | */ 14 | override fun getMainComponentName(): String = "ManualTestApp" 15 | 16 | /** 17 | * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate] 18 | * which allows you to enable New Architecture with a single boolean flags [fabricEnabled] 19 | */ 20 | override fun createReactActivityDelegate(): ReactActivityDelegate = 21 | DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) 22 | } 23 | -------------------------------------------------------------------------------- /link-dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "===== Installing all dependencies..." 4 | yarn 5 | 6 | declare -a examples=(ManualTestApp) 7 | 8 | for example in "${examples[@]}" 9 | do 10 | echo "===== Linking to $example" 11 | MODULES_DIR=$example/node_modules 12 | SDK_DIR=$MODULES_DIR/launchdarkly-react-native-client-sdk 13 | 14 | mkdir -p "$MODULES_DIR" 15 | rm -rf "$SDK_DIR" 16 | mkdir -p "$SDK_DIR"/node_modules 17 | 18 | rsync -aq package.json "$SDK_DIR" 19 | rsync -aq index.js "$SDK_DIR" 20 | rsync -aq index.d.ts "$SDK_DIR" 21 | rsync -aq launchdarkly-react-native-client-sdk.podspec "$SDK_DIR" 22 | rsync -aq LICENSE "$SDK_DIR" 23 | rsync -aq node_modules "$SDK_DIR" 24 | rsync -av ios "$SDK_DIR" --exclude LaunchdarklyReactNativeClient.xcworkspace --exclude build --exclude Pods --exclude Tests --exclude Podfile --exclude Podfile.lock 25 | rsync -av android "$SDK_DIR" --exclude .gradle --exclude build --exclude .idea --exclude gradle --exclude src/test 26 | rsync -av src "$SDK_DIR" 27 | done 28 | -------------------------------------------------------------------------------- /ManualTestApp/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /android/src/main/java/com/launchdarkly/reactnative/LaunchdarklyReactNativeClientPackage.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.reactnative; 2 | 3 | import com.facebook.react.ReactPackage; 4 | import com.facebook.react.bridge.JavaScriptModule; 5 | import com.facebook.react.bridge.NativeModule; 6 | import com.facebook.react.bridge.ReactApplicationContext; 7 | import com.facebook.react.uimanager.ViewManager; 8 | 9 | import java.util.Arrays; 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | public class LaunchdarklyReactNativeClientPackage implements ReactPackage { 14 | @Override 15 | public List createNativeModules(ReactApplicationContext reactContext) { 16 | return Arrays.asList(new LaunchdarklyReactNativeClientModule(reactContext)); 17 | } 18 | 19 | // Deprecated from RN 0.47 20 | public List> createJSModules() { 21 | return Collections.emptyList(); 22 | } 23 | 24 | @Override 25 | public List createViewManagers(ReactApplicationContext reactContext) { 26 | return Collections.emptyList(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ManualTestApp/ios/ManualTestApp/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "scale" : "1x", 46 | "size" : "1024x1024" 47 | } 48 | ], 49 | "info" : { 50 | "author" : "xcode", 51 | "version" : 1 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ManualTestApp/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | ios/.xcode.env.local 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 32 | *.hprof 33 | .cxx/ 34 | *.keystore 35 | !debug.keystore 36 | 37 | # node.js 38 | # 39 | node_modules/ 40 | npm-debug.log 41 | yarn-error.log 42 | # We use yarn for the manual test app 43 | package-lock.json 44 | 45 | # BUCK 46 | buck-out/ 47 | \.buckd/ 48 | *.keystore 49 | !debug.keystore 50 | 51 | # fastlane 52 | # 53 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 54 | # screenshots whenever they are needed. 55 | # For more information about the recommended setup visit: 56 | # https://docs.fastlane.tools/best-practices/source-control/ 57 | 58 | **/fastlane/report.xml 59 | **/fastlane/Preview.html 60 | **/fastlane/screenshots 61 | **/fastlane/test_output 62 | 63 | # Bundle artifact 64 | *.jsbundle 65 | 66 | # Ruby / CocoaPods 67 | /ios/Pods/ 68 | /vendor/bundle/ 69 | 70 | # Temporary files created by Metro to check the health of the file watcher 71 | .metro-health-check* 72 | 73 | # testing 74 | /coverage 75 | 76 | .env 77 | -------------------------------------------------------------------------------- /ManualTestApp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "manualtestapp", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "cc": "rimraf node_modules/.cache/babel-loader/*,", 7 | "android": "yarn run cc && react-native run-android --terminal a", 8 | "android-release": "yarn run cc && react-native run-android --terminal --variant release", 9 | "ios": "yarn run cc && react-native run-ios --terminal a", 10 | "start": "yarn run cc && react-native start --reset-cache", 11 | "postinstall": "cd ../ && yarn link-dev", 12 | "lint": "eslint .", 13 | "doctor": "react-native doctor", 14 | "clean-all": "./clean-all.sh" 15 | }, 16 | "dependencies": { 17 | "@react-native-picker/picker": "^2.2.1", 18 | "launchdarkly-react-native-client-sdk": "^9.0.0", 19 | "react": "18.2.0", 20 | "react-native": "0.73.4" 21 | }, 22 | "devDependencies": { 23 | "@babel/core": "^7.20.0", 24 | "@babel/preset-env": "^7.20.0", 25 | "@babel/runtime": "^7.20.0", 26 | "@react-native/babel-preset": "^0.73.18", 27 | "@react-native/eslint-config": "^0.73.1", 28 | "@react-native/metro-config": "^0.73.2", 29 | "@react-native/typescript-config": "^0.73.1", 30 | "@types/react": "^18.2.6", 31 | "@types/react-test-renderer": "^18.0.0", 32 | "babel-jest": "^29.6.3", 33 | "eslint": "^8.19.0", 34 | "jest": "^29.6.3", 35 | "prettier": "2.8.8", 36 | "react-native-dotenv": "3.4.9", 37 | "react-test-renderer": "18.2.0", 38 | "typescript": "5.0.4" 39 | }, 40 | "engines": { 41 | "node": ">=18" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | 7 | dependencies { 8 | classpath("com.android.tools.build:gradle") 9 | classpath("com.facebook.react:react-native-gradle-plugin") 10 | } 11 | } 12 | 13 | apply plugin: 'com.android.library' 14 | 15 | android { 16 | compileSdkVersion = 33 17 | buildToolsVersion = "30.0.3" 18 | 19 | namespace "com.launchdarkly.reactnative" 20 | defaultConfig { 21 | minSdkVersion(21) 22 | targetSdkVersion(33) 23 | versionCode = 1 24 | versionName = "1.0" 25 | consumerProguardFiles("consumer-proguard-rules.pro") 26 | } 27 | 28 | lintOptions { 29 | abortOnError false 30 | } 31 | 32 | compileOptions { 33 | sourceCompatibility JavaVersion.VERSION_1_8 34 | targetCompatibility JavaVersion.VERSION_1_8 35 | } 36 | } 37 | 38 | 39 | repositories { 40 | mavenLocal() 41 | google() 42 | mavenCentral() 43 | maven { 44 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 45 | url "$rootDir/../node_modules/react-native/android" 46 | } 47 | } 48 | 49 | dependencies { 50 | // The version of react-native is set by the React Native Gradle Plugin 51 | implementation("com.facebook.react:react-android:0.72.7") 52 | implementation("com.launchdarkly:launchdarkly-android-client-sdk:5.4.0") 53 | implementation("com.jakewharton.timber:timber:5.0.1") 54 | implementation("com.google.code.gson:gson:2.10.1") 55 | 56 | testImplementation "junit:junit:4.13.2" 57 | testImplementation 'com.google.code.gson:gson:2.10.1' 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/contextUtils.test.ts: -------------------------------------------------------------------------------- 1 | import { LDContext, LDMultiKindContext, LDSingleKindContext } from 'launchdarkly-js-sdk-common'; 2 | import { isAnonymousAndNoKey, validateContext } from './contextUtils'; 3 | 4 | describe('contextUtils', () => { 5 | describe('isAnonymousAndNoKey', () => { 6 | test('anonymous no key', () => { 7 | const c = { kind: 'car', anonymous: true }; 8 | expect(isAnonymousAndNoKey(c)).toBeTruthy(); 9 | }); 10 | 11 | test('anonymous with key', () => { 12 | const c = { kind: 'car', key: 'blr34F', anonymous: true }; 13 | expect(isAnonymousAndNoKey(c)).toBeFalsy(); 14 | }); 15 | 16 | test('known user', () => { 17 | const c = { kind: 'car', key: 'blr34F' }; 18 | expect(isAnonymousAndNoKey(c)).toBeFalsy(); 19 | }); 20 | }); 21 | 22 | describe('validateContext', () => { 23 | test('undefined, null, empty context', () => { 24 | expect(validateContext(undefined as any as LDContext)).toBeFalsy(); 25 | expect(validateContext(null as any as LDContext)).toBeFalsy(); 26 | expect(validateContext({})).toBeFalsy(); 27 | }); 28 | 29 | test('inject key for anonymous single context', () => { 30 | const c = { kind: 'car', key: 'blr34F', anonymous: true }; 31 | const isValid = validateContext(c); 32 | 33 | expect(c.key).toEqual('blr34F'); 34 | expect(isValid).toBeTruthy(); 35 | }); 36 | 37 | test('inject key for multi anonymous context', () => { 38 | // @ts-ignore 39 | const c: LDMultiKindContext = { kind: 'multi', car: { anonymous: true }, bus: { key: 'bbb-555' } }; 40 | const isValid = validateContext(c); 41 | 42 | expect(isValid).toBeTruthy(); 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /ManualTestApp/ios/ManualTestApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ManualTestApp 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(CURRENT_PROJECT_VERSION) 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSAllowsArbitraryLoads 30 | 31 | NSAllowsLocalNetworking 32 | 33 | 34 | NSLocationWhenInUseUsageDescription 35 | 36 | UILaunchStoryboardName 37 | LaunchScreen 38 | UIRequiredDeviceCapabilities 39 | 40 | armv7 41 | 42 | UISupportedInterfaceOrientations 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | UIViewControllerBasedStatusBarAppearance 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is this a support request?** 11 | This issue tracker is maintained by LaunchDarkly SDK developers and is intended for feedback on the SDK code. If you're not sure whether the problem you are having is specifically related to the SDK, or to the LaunchDarkly service overall, it may be more appropriate to contact the LaunchDarkly support team; they can help to investigate the problem and will consult the SDK team if necessary. You can submit a support request by going [here](https://support.launchdarkly.com/hc/en-us/requests/new) or by emailing support@launchdarkly.com. 12 | 13 | Note that issues filed on this issue tracker are publicly accessible. Do not provide any private account information on your issues. If your problem is specific to your account, you should submit a support request as described above. 14 | 15 | **Describe the bug** 16 | A clear and concise description of what the bug is. 17 | 18 | **To reproduce** 19 | Steps to reproduce the behavior. 20 | 21 | **Expected behavior** 22 | A clear and concise description of what you expected to happen. 23 | 24 | **Logs** 25 | If applicable, add any log output related to your problem. 26 | 27 | **SDK version** 28 | The version of this SDK that you are using. 29 | 30 | **Language version, developer tools** 31 | For instance, Go 1.11 or Ruby 2.5.3. If you are using a language that requires a separate compiler, such as C, please include the name and version of the compiler too. 32 | 33 | **OS/platform** 34 | For instance, Ubuntu 16.04, Windows 10, or Android 4.0.3. If your code is running in a browser, please also include the browser type and version. 35 | 36 | **Additional context** 37 | Add any other context about the problem here. 38 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing to the LaunchDarkly Client-Side SDK for React Native 2 | ================================================ 3 | 4 | LaunchDarkly has published an [SDK contributor's guide](https://docs.launchdarkly.com/sdk/concepts/contributors-guide) that provides a detailed explanation of how our SDKs work. See below for additional information on how to contribute to this SDK. 5 | 6 | Submitting bug reports and feature requests 7 | ------------------ 8 | 9 | The LaunchDarkly SDK team monitors the [issue tracker](https://github.com/launchdarkly/react-native-client-sdk/issues) in the SDK repository. Bug reports and feature requests specific to this SDK should be filed in this issue tracker. The SDK team will respond to all newly filed issues within two business days. 10 | 11 | Submitting pull requests 12 | ------------------ 13 | 14 | We encourage pull requests and other contributions from the community. Before submitting pull requests, ensure that all temporary or unintended code is removed. Don't worry about adding reviewers to the pull request; the LaunchDarkly SDK team will add themselves. The SDK team will acknowledge all pull requests within two business days. 15 | 16 | Build instructions 17 | ------------------ 18 | 19 | ### Prerequisites 20 | 21 | Follow the [React Native development environment setup guide](https://reactnative.dev/docs/environment-setup) to install all required tools for contributing to the project. 22 | 23 | ### Building and testing 24 | 25 | First, install the dependencies by running: 26 | ``` 27 | npm install 28 | ``` 29 | 30 | To run tests of the JavaScript portion of the implementation: 31 | ``` 32 | npm test 33 | ``` 34 | 35 | To validate the TypeScript module definition, run: 36 | ``` 37 | npm run check-typescript 38 | ``` 39 | 40 | Testing the native module implementation must be done by integrating the SDK into an application, such as one created with `npx react-native init`. 41 | -------------------------------------------------------------------------------- /ManualTestApp/android/app/src/main/java/com/manualtestapp/MainApplication.kt: -------------------------------------------------------------------------------- 1 | package com.manualtestapp 2 | 3 | import android.app.Application 4 | import com.facebook.react.PackageList 5 | import com.facebook.react.ReactApplication 6 | import com.facebook.react.ReactHost 7 | import com.facebook.react.ReactNativeHost 8 | import com.facebook.react.ReactPackage 9 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load 10 | import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost 11 | import com.facebook.react.defaults.DefaultReactNativeHost 12 | import com.facebook.react.flipper.ReactNativeFlipper 13 | import com.facebook.soloader.SoLoader 14 | 15 | class MainApplication : Application(), ReactApplication { 16 | 17 | override val reactNativeHost: ReactNativeHost = 18 | object : DefaultReactNativeHost(this) { 19 | override fun getPackages(): List { 20 | // Packages that cannot be autolinked yet can be added manually here, for example: 21 | // packages.add(new MyReactNativePackage()); 22 | return PackageList(this).packages 23 | } 24 | 25 | override fun getJSMainModuleName(): String = "index" 26 | 27 | override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG 28 | 29 | override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED 30 | override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED 31 | } 32 | 33 | override val reactHost: ReactHost 34 | get() = getDefaultReactHost(this.applicationContext, reactNativeHost) 35 | 36 | override fun onCreate() { 37 | super.onCreate() 38 | SoLoader.init(this, false) 39 | if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { 40 | // If you opted-in for the New Architecture, we load the native entry point for this app. 41 | load() 42 | } 43 | ReactNativeFlipper.initializeFlipper(this, reactNativeHost.reactInstanceManager) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /ManualTestApp/android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m 13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | # AndroidX package structure to make it clearer which packages are bundled with the 21 | # Android operating system, and which are packaged with your app's APK 22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 23 | android.useAndroidX=true 24 | # Automatically convert third-party libraries to use AndroidX 25 | android.enableJetifier=true 26 | 27 | # Use this property to specify which architecture you want to build. 28 | # You can also override it from the CLI using 29 | # ./gradlew -PreactNativeArchitectures=x86_64 30 | reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 31 | 32 | # Use this property to enable support to the new architecture. 33 | # This will allow you to use TurboModules and the Fabric render in 34 | # your application. You should enable this flag either if you want 35 | # to write custom TurboModules/Fabric components OR use libraries that 36 | # are providing them. 37 | newArchEnabled=false 38 | 39 | # Use this property to enable or disable the Hermes JS engine. 40 | # If set to false, you will be using JSC instead. 41 | hermesEnabled=true 42 | -------------------------------------------------------------------------------- /ManualTestApp/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Resolve react_native_pods.rb with node to allow for hoisting 2 | require Pod::Executable.execute_command('node', ['-p', 3 | 'require.resolve( 4 | "react-native/scripts/react_native_pods.rb", 5 | {paths: [process.argv[1]]}, 6 | )', __dir__]).strip 7 | 8 | platform :ios, min_ios_version_supported 9 | prepare_react_native_project! 10 | 11 | # If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set. 12 | # because `react-native-flipper` depends on (FlipperKit,...) that will be excluded 13 | # 14 | # To fix this you can also exclude `react-native-flipper` using a `react-native.config.js` 15 | # ```js 16 | # module.exports = { 17 | # dependencies: { 18 | # ...(process.env.NO_FLIPPER ? { 'react-native-flipper': { platforms: { ios: null } } } : {}), 19 | # ``` 20 | flipper_config = ENV['NO_FLIPPER'] == "1" ? FlipperConfiguration.disabled : FlipperConfiguration.enabled 21 | 22 | linkage = ENV['USE_FRAMEWORKS'] 23 | if linkage != nil 24 | Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green 25 | use_frameworks! :linkage => linkage.to_sym 26 | end 27 | 28 | target 'ManualTestApp' do 29 | config = use_native_modules! 30 | 31 | use_react_native!( 32 | :path => config[:reactNativePath], 33 | # Enables Flipper. 34 | # 35 | # Note that if you have use_frameworks! enabled, Flipper will not work and 36 | # you should disable the next line. 37 | :flipper_configuration => flipper_config, 38 | # An absolute path to your application root. 39 | :app_path => "#{Pod::Config.instance.installation_root}/.." 40 | ) 41 | 42 | target 'ManualTestAppTests' do 43 | inherit! :complete 44 | # Pods for testing 45 | end 46 | 47 | post_install do |installer| 48 | # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202 49 | react_native_post_install( 50 | installer, 51 | config[:reactNativePath], 52 | :mac_catalyst_enabled => false 53 | ) 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /ManualTestApp/android/app/src/main/res/drawable/rn_edit_text_material.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 21 | 22 | 23 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /ios/LaunchdarklyReactNativeClient.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 18 | 24 | 25 | 26 | 27 | 28 | 38 | 39 | 45 | 46 | 48 | 49 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /ManualTestApp/clean-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Ripped from: https://gist.githubusercontent.com/townofdon/087c7c0bb773adb158f20339c7e13408/raw/53ccb3430cf870c3cdeecead82bc12c3644f2f53/react-native-nuke.sh 3 | # ReactNative script to clean all the things 4 | # usage: 5 | # - add each item below as a separate script in package.json 6 | # - add one final script: 7 | # - "clean": "yarn clean-node-modules && yarn clean-pods && yarn clean-ios && yarn clean-android && yarn clean-rn-cache" 8 | # - alternatively, copy this shell script and add the following cmd to package.json: 9 | # - "clean": "./react-native-clean-sh" 10 | # - you may need to run `sudo chmod 777 ./react-native-clean-sh before this script can run` 11 | 12 | echo " ____ " 13 | echo " __,-~~/~ \`---. " 14 | echo " _/_,---( , ) " 15 | echo " __ / < / ) \___ " 16 | echo " ====------------------===;;;== " 17 | echo " \/ ~\"~\"~\"~\"~\"~\~\"~)~\",1/ " 18 | echo " (_ ( \ ( > \) " 19 | echo " \_( _ < >_>' " 20 | echo " ~ \`-i' ::>|--\" " 21 | echo " I;|.|.| " 22 | echo " <|i::|i|> " 23 | echo " |[::|.| " 24 | echo " ||: | " 25 | echo "______________________REACT NATIVE CLEAN ALL________________ " 26 | 27 | # clean-node-modules 28 | rm -rf node_modules && yarn 29 | 30 | # clean-pods 31 | cd ios/ && pod cache clean --all && pod deintegrate && rm -rf Pods && rm -rf Podfile.lock && pod install && cd ../ 32 | 33 | # clean-ios 34 | rm -rf ios/build && rm -rf ~/Library/Developer/Xcode/DerivedData && rm -rf ./ios/DerivedData 35 | 36 | # clean-android 37 | cd android && ./gradlew clean && cd .. 38 | 39 | # clean-rn-cache 40 | rm -rf $TMPDIR/react-* && rm -rf $TMPDIR/react-native-packager-cache-* && rm -rf $TMPDIR/metro-bundler-cache-* && yarn cache clean && watchman watch-del-all 41 | 42 | react-native start --reset-cache 43 | -------------------------------------------------------------------------------- /clean-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Ripped from: https://gist.githubusercontent.com/townofdon/087c7c0bb773adb158f20339c7e13408/raw/53ccb3430cf870c3cdeecead82bc12c3644f2f53/react-native-nuke.sh 3 | # ReactNative script to clean all the things 4 | # usage: 5 | # - add each item below as a separate script in package.json 6 | # - add one final script: 7 | # - "clean": "yarn clean-node-modules && yarn clean-pods && yarn clean-ios && yarn clean-android && yarn clean-rn-cache" 8 | # - alternatively, copy this shell script and add the following cmd to package.json: 9 | # - "clean": "./react-native-clean-sh" 10 | # - you may need to run `sudo chmod 777 ./react-native-clean-sh before this script can run` 11 | 12 | echo " ____ " 13 | echo " __,-~~/~ \`---. " 14 | echo " _/_,---( , ) " 15 | echo " __ / < / ) \___ " 16 | echo " ====------------------===;;;== " 17 | echo " \/ ~\"~\"~\"~\"~\"~\~\"~)~\",1/ " 18 | echo " (_ ( \ ( > \) " 19 | echo " \_( _ < >_>' " 20 | echo " ~ \`-i' ::>|--\" " 21 | echo " I;|.|.| " 22 | echo " <|i::|i|> " 23 | echo " |[::|.| " 24 | echo " ||: | " 25 | echo "______________________REACT NATIVE CLEAN ALL________________ " 26 | 27 | # clean-node-modules 28 | rm -rf node_modules && yarn 29 | 30 | # clean-pods 31 | cd ios/ && pod cache clean --all && pod deintegrate && rm -rf Pods && rm -rf Podfile.lock && pod install && cd ../ 32 | 33 | # clean-ios 34 | rm -rf ios/build && rm -rf ~/Library/Developer/Xcode/DerivedData && rm -rf ./ios/DerivedData 35 | 36 | # clean-android 37 | cd android && ./gradlew clean && cd .. 38 | 39 | # clean-rn-cache 40 | rm -rf $TMPDIR/react-* && rm -rf $TMPDIR/react-native-packager-cache-* && rm -rf $TMPDIR/metro-bundler-cache-* && yarn cache clean && watchman watch-del-all 41 | 42 | cd ManualTestApp && react-native start --reset-cache && cd .. 43 | -------------------------------------------------------------------------------- /ManualTestApp/ios/ManualTestAppTests/ManualTestAppTests.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | #import 5 | #import 6 | 7 | #define TIMEOUT_SECONDS 600 8 | #define TEXT_TO_LOOK_FOR @"Welcome to React" 9 | 10 | @interface ManualTestAppTests : XCTestCase 11 | 12 | @end 13 | 14 | @implementation ManualTestAppTests 15 | 16 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL (^)(UIView *view))test 17 | { 18 | if (test(view)) { 19 | return YES; 20 | } 21 | for (UIView *subview in [view subviews]) { 22 | if ([self findSubviewInView:subview matching:test]) { 23 | return YES; 24 | } 25 | } 26 | return NO; 27 | } 28 | 29 | - (void)testRendersWelcomeScreen 30 | { 31 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController]; 32 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 33 | BOOL foundElement = NO; 34 | 35 | __block NSString *redboxError = nil; 36 | #ifdef DEBUG 37 | RCTSetLogFunction( 38 | ^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 39 | if (level >= RCTLogLevelError) { 40 | redboxError = message; 41 | } 42 | }); 43 | #endif 44 | 45 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 46 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 47 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 48 | 49 | foundElement = [self findSubviewInView:vc.view 50 | matching:^BOOL(UIView *view) { 51 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 52 | return YES; 53 | } 54 | return NO; 55 | }]; 56 | } 57 | 58 | #ifdef DEBUG 59 | RCTSetLogFunction(RCTDefaultLogFunction); 60 | #endif 61 | 62 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 63 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 64 | } 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: [push] 3 | 4 | jobs: 5 | tsc: 6 | runs-on: ubuntu-latest 7 | name: Typescript 8 | steps: 9 | - uses: actions/checkout@v3 10 | - uses: actions/setup-node@v3 11 | with: 12 | node-version: '18.x' 13 | cache: 'yarn' 14 | - run: yarn && yarn tsc 15 | 16 | js-tests: 17 | runs-on: ubuntu-latest 18 | name: JS tests 19 | steps: 20 | - uses: actions/checkout@v3 21 | - uses: actions/setup-node@v3 22 | with: 23 | node-version: '18.x' 24 | cache: 'yarn' 25 | - run: yarn && yarn test 26 | 27 | android-tests: 28 | runs-on: ubuntu-latest 29 | name: Android tests 30 | steps: 31 | - uses: actions/checkout@v3 32 | - uses: actions/setup-node@v3 33 | with: 34 | node-version: '18.x' 35 | cache: 'yarn' 36 | - uses: actions/setup-java@v4 37 | with: 38 | distribution: 'temurin' # See 'Supported distributions' for available options 39 | java-version: '17' 40 | - run: yarn && cd android && ./gradlew test 41 | 42 | # ripped from these two places: 43 | # https://vmois.dev/xcode-github-actions/ 44 | # https://gist.github.com/ricardopereira/10198e68f27c14601d77ebc7a8352da1 45 | ios-tests: 46 | runs-on: macos-12 47 | strategy: 48 | matrix: 49 | destination: ['platform=iOS Simulator,name=iPhone 13,OS=16.2'] 50 | name: iOS tests 51 | steps: 52 | - uses: actions/checkout@v3 53 | - uses: actions/setup-node@v3 54 | with: 55 | node-version: '18.x' 56 | cache: 'yarn' 57 | - uses: actions/cache@v3 58 | id: cocoapods-cache 59 | with: 60 | path: ios/Pods 61 | key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock', 'package-lock.json', 'yarn.lock') }} 62 | restore-keys: | 63 | ${{ runner.os }}-pods- 64 | - name: CocoaPods 65 | run: cd ios && yarn && pod install 66 | - name: Select Xcode 67 | run: sudo xcode-select -switch /Applications/Xcode_14.2.app && /usr/bin/xcodebuild -version 68 | - name: Run tests 69 | run: cd ios && xcodebuild -quiet -workspace LaunchdarklyReactNativeClient.xcworkspace -scheme LaunchdarklyReactNativeClient -sdk iphonesimulator -destination "${destination}" test | xcpretty && exit ${PIPESTATUS[0]} 70 | env: 71 | destination: ${{ matrix.destination }} 72 | -------------------------------------------------------------------------------- /__mocks__/native.js: -------------------------------------------------------------------------------- 1 | const mockNativeModule = { 2 | FLAG_PREFIX: 'test-flag-prefix', 3 | ALL_FLAGS_PREFIX: 'test-all-flags-prefix', 4 | CONNECTION_MODE_PREFIX: 'test-connection-mode-prefix', 5 | 6 | configure: jest.fn(), 7 | configureWithTimeout: jest.fn(), 8 | 9 | boolVariation: jest.fn(), 10 | boolVariationDefaultValue: jest.fn(), 11 | numberVariation: jest.fn(), 12 | numberVariationDefaultValue: jest.fn(), 13 | stringVariation: jest.fn(), 14 | stringVariationDefaultValue: jest.fn(), 15 | jsonVariationNone: jest.fn(), 16 | jsonVariationNumber: jest.fn(), 17 | jsonVariationBool: jest.fn(), 18 | jsonVariationString: jest.fn(), 19 | jsonVariationArray: jest.fn(), 20 | jsonVariationObject: jest.fn(), 21 | 22 | boolVariationDetail: jest.fn(), 23 | boolVariationDetailDefaultValue: jest.fn(), 24 | numberVariationDetail: jest.fn(), 25 | numberVariationDetailDefaultValue: jest.fn(), 26 | stringVariationDetail: jest.fn(), 27 | stringVariationDetailDefaultValue: jest.fn(), 28 | jsonVariationDetailNone: jest.fn(), 29 | jsonVariationDetailNumber: jest.fn(), 30 | jsonVariationDetailBool: jest.fn(), 31 | jsonVariationDetailString: jest.fn(), 32 | jsonVariationDetailArray: jest.fn(), 33 | jsonVariationDetailObject: jest.fn(), 34 | 35 | allFlags: jest.fn(), 36 | 37 | trackNumber: jest.fn(), 38 | trackBool: jest.fn(), 39 | trackString: jest.fn(), 40 | trackArray: jest.fn(), 41 | trackObject: jest.fn(), 42 | track: jest.fn(), 43 | 44 | trackNumberMetricValue: jest.fn(), 45 | trackBoolMetricValue: jest.fn(), 46 | trackStringMetricValue: jest.fn(), 47 | trackArrayMetricValue: jest.fn(), 48 | trackObjectMetricValue: jest.fn(), 49 | trackMetricValue: jest.fn(), 50 | 51 | setOffline: jest.fn(), 52 | isOffline: jest.fn(), 53 | setOnline: jest.fn(), 54 | isInitialized: jest.fn(), 55 | flush: jest.fn(), 56 | close: jest.fn(), 57 | identify: jest.fn(), 58 | getConnectionMode: jest.fn(), 59 | getLastSuccessfulConnection: jest.fn(), 60 | getLastFailedConnection: jest.fn(), 61 | getLastFailure: jest.fn(), 62 | 63 | registerFeatureFlagListener: jest.fn(), 64 | unregisterFeatureFlagListener: jest.fn(), 65 | registerCurrentConnectionModeListener: jest.fn(), 66 | unregisterCurrentConnectionModeListener: jest.fn(), 67 | registerAllFlagsListener: jest.fn(), 68 | unregisterAllFlagsListener: jest.fn(), 69 | }; 70 | 71 | jest.mock( 72 | 'react-native', 73 | () => { 74 | return { 75 | NativeModules: { 76 | LaunchdarklyReactNativeClient: mockNativeModule, 77 | }, 78 | NativeEventEmitter: jest.fn().mockImplementation(() => { 79 | return { addListener: jest.fn() }; 80 | }), 81 | }; 82 | }, 83 | { virtual: true }, 84 | ); 85 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "launchdarkly-react-native-client-sdk", 3 | "version": "9.4.2", 4 | "description": "LaunchDarkly Client-side SDK for React Native", 5 | "main": "index.js", 6 | "types": "index.d.ts", 7 | "files": [ 8 | "android/src/main", 9 | "android/build.gradle", 10 | "android/consumer-proguard-rules.pro", 11 | "android/gradle.properties", 12 | "ios/LaunchdarklyReactNativeClient.xcodeproj", 13 | "ios/LaunchdarklyReactNativeClient.swift", 14 | "ios/LaunchdarklyReactNativeClient-Bridging-Header.h", 15 | "ios/LaunchdarklyReactNativeClientBridge.m", 16 | "src", 17 | "babel.config.js", 18 | "index.d.ts", 19 | "index.js", 20 | "launchdarkly-react-native-client-sdk.podspec", 21 | "test-types.ts", 22 | "tsconfig.json" 23 | ], 24 | "scripts": { 25 | "check-typescript": "node_modules/typescript/bin/tsc", 26 | "test": "jest", 27 | "test:junit": "jest --testResultsProcessor jest-junit", 28 | "link-dev": "./link-dev.sh", 29 | "prettier": "prettier --write '**/*.@(js|ts|tsx|json|css)'", 30 | "doctor": "react-native doctor", 31 | "yarn-all": "yarn && yarn --cwd ManualTestApp", 32 | "modd-ios": "modd -f modd-ios.conf", 33 | "modd-android": "modd -f modd-android.conf", 34 | "modd": "modd", 35 | "dev-ios": "yarn yarn-all && yarn modd-ios", 36 | "dev-android": "yarn yarn-all && yarn modd-android", 37 | "clean-all": "./clean-all.sh" 38 | }, 39 | "repository": { 40 | "type": "git", 41 | "url": "https://github.com/launchdarkly/react-native-client-sdk.git" 42 | }, 43 | "keywords": [ 44 | "react-native" 45 | ], 46 | "author": "LaunchDarkly", 47 | "license": "Apache-2.0", 48 | "bugs": { 49 | "url": "https://github.com/launchdarkly/react-native-client-sdk/issues" 50 | }, 51 | "homepage": "https://docs.launchdarkly.com/sdk/client-side/react-native", 52 | "dependencies": { 53 | "launchdarkly-js-sdk-common": "5.0.3" 54 | }, 55 | "peerDependencies": { 56 | "react-native": ">=0.69.0 <0.74.0" 57 | }, 58 | "devDependencies": { 59 | "@react-native/babel-preset": "^0.73.18", 60 | "@tsconfig/react-native": "2.0.3", 61 | "@types/jest": "29.4.0", 62 | "@types/react": "18.2.6", 63 | "@types/react-test-renderer": "18.0.0", 64 | "jest": "^29.4.2", 65 | "jest-junit": "^15.0.0", 66 | "prettier": "^2.8.8", 67 | "react-native": "^0.73.2", 68 | "typedoc-plugin-rename-defaults": "^0.6.4", 69 | "typescript": ">=4.5.0" 70 | }, 71 | "jest": { 72 | "preset": "react-native", 73 | "setupFiles": [ 74 | "./__mocks__/native.js" 75 | ], 76 | "modulePathIgnorePatterns": [ 77 | "/example/node_modules", 78 | "/lib/" 79 | ] 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /ManualTestApp/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /ManualTestApp/ios/ManualTestApp.xcodeproj/xcshareddata/xcschemes/ManualTestApp.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 55 | 61 | 62 | 63 | 64 | 70 | 72 | 78 | 79 | 80 | 81 | 83 | 84 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /ios/LaunchdarklyReactNativeClient.xcodeproj/xcshareddata/xcschemes/LaunchdarklyReactNativeClient.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 67 | 68 | 74 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ManualTestApp/ios/ManualTestApp/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 24 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /ios/LaunchdarklyReactNativeClientBridge.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "React/RCTEventEmitter.h" 3 | 4 | @interface RCT_EXTERN_MODULE(LaunchdarklyReactNativeClient, RCTEventEmitter) 5 | 6 | RCT_EXTERN_METHOD(configure:(NSDictionary *)config context:(NSDictionary *)context resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) 7 | 8 | RCT_EXTERN_METHOD(configureWithTimeout:(NSDictionary *)config context:(NSDictionary *)context timeout:(NSInteger *)timeout resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) 9 | 10 | RCT_EXTERN_METHOD(boolVariation:(NSString *)flagKey defaultValue:(BOOL *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) 11 | 12 | RCT_EXTERN_METHOD(numberVariation:(NSString *)flagKey defaultValue:(double)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) 13 | 14 | RCT_EXTERN_METHOD(stringVariation:(NSString *)flagKey defaultValue:(NSString *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) 15 | 16 | RCT_EXTERN_METHOD(jsonVariation:(NSString *)flagKey defaultValue:(id *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) 17 | 18 | RCT_EXTERN_METHOD(boolVariationDetail:(NSString *)flagKey defaultValue:(BOOL *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) 19 | 20 | RCT_EXTERN_METHOD(numberVariationDetail:(NSString *)flagKey defaultValue:(double)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) 21 | 22 | RCT_EXTERN_METHOD(stringVariationDetail:(NSString *)flagKey defaultValue:(NSString *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) 23 | 24 | RCT_EXTERN_METHOD(jsonVariationDetail:(NSString *)flagKey defaultValue:(id *)defaultValue environment:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) 25 | 26 | RCT_EXTERN_METHOD(trackData:(NSString *)eventName data:(id *)data environment:(NSString *)environment) 27 | 28 | RCT_EXTERN_METHOD(trackMetricValue:(NSString *)eventName data:(id *)data metricValue:(NSNumber * _Nonnull)metricValue environment:(NSString *)environment) 29 | 30 | RCT_EXTERN_METHOD(setOffline:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) 31 | 32 | RCT_EXTERN_METHOD(isOffline:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) 33 | 34 | RCT_EXTERN_METHOD(setOnline:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) 35 | 36 | RCT_EXTERN_METHOD(flush) 37 | 38 | RCT_EXTERN_METHOD(close:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) 39 | 40 | RCT_EXTERN_METHOD(identify:(NSDictionary *)context resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) 41 | 42 | RCT_EXTERN_METHOD(allFlags:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) 43 | 44 | RCT_EXTERN_METHOD(registerFeatureFlagListener:(NSString *)flagKey environment:(NSString *)environment) 45 | 46 | RCT_EXTERN_METHOD(unregisterFeatureFlagListener:(NSString *)flagKey environment:(NSString *)environment) 47 | 48 | RCT_EXTERN_METHOD(registerCurrentConnectionModeListener:(NSString *)listenerId environment:(NSString *)environment) 49 | 50 | RCT_EXTERN_METHOD(unregisterCurrentConnectionModeListener:(NSString *)listenerId environment:(NSString *)environment) 51 | 52 | RCT_EXTERN_METHOD(registerAllFlagsListener:(NSString *)listenerId environment:(NSString *)environment) 53 | 54 | RCT_EXTERN_METHOD(unregisterAllFlagsListener:(NSString *)listenerId environment:(NSString *)environment) 55 | 56 | RCT_EXTERN_METHOD(isInitialized:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) 57 | 58 | RCT_EXTERN_METHOD(getConnectionMode:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) 59 | 60 | RCT_EXTERN_METHOD(getLastSuccessfulConnection:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) 61 | 62 | RCT_EXTERN_METHOD(getLastFailedConnection:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) 63 | 64 | RCT_EXTERN_METHOD(getLastFailure:(NSString *)environment resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | jobs: 4 | # This job simulates integrating the SDK into a freshly created React Native project template and 5 | # then builds Android and iOS applications using the template. 6 | build-applications-using-template: 7 | parameters: 8 | rn-version: 9 | description: The React Native project template version 10 | type: string 11 | xcode-version: 12 | description: The Xcode version to build with 13 | type: string 14 | 15 | macos: 16 | xcode: <> 17 | 18 | environment: 19 | ANDROID_SDK_ROOT: '/tmp/Android' 20 | 21 | steps: 22 | - checkout 23 | 24 | - run: 25 | name: Download Android command line tools 26 | command: | 27 | mkdir -p $ANDROID_SDK_ROOT/cmdline-tools/latest 28 | curl https://dl.google.com/android/repository/commandlinetools-mac-8092744_latest.zip -o cmdline-tools.zip 29 | unzip cmdline-tools.zip 30 | mv cmdline-tools/* $ANDROID_SDK_ROOT/cmdline-tools/latest/ 31 | yes | $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager --licenses > /dev/null || true 32 | 33 | - run: 34 | name: Setup Android debug keystore 35 | command: | 36 | keytool -genkey -v -keystore debug.keystore -storepass android -alias androiddebugkey -keypass android -keyalg RSA -keysize 2048 -validity 10000 -dname "cn=Unknown, ou=Unknown, o=Unknown, c=Unknown" 37 | mkdir -p ~/.android 38 | cp debug.keystore ~/.android/ 39 | 40 | - restore_cache: 41 | name: Restore RN project template from cache 42 | key: v1-rn-template-cache-<> 43 | 44 | - run: 45 | name: Create CI test application for RN <> 46 | command: | 47 | cd .. 48 | mkdir -p test 49 | cd test 50 | [ -d "CITest" ] || npx react-native@<> init CITest --version <> --skip-install 51 | 52 | # use macos default ruby version 53 | cd CITest 54 | rm -rf .ruby-version 55 | 56 | - save_cache: 57 | name: Save RN project template to cache 58 | key: v1-rn-template-cache-<> 59 | paths: 60 | - ../test/CITest 61 | 62 | - run: 63 | name: Add LaunchDarkly dependency 64 | command: | 65 | cd ../test/CITest && npx yarn add file:../../project 66 | cd node_modules/launchdarkly-react-native-client-sdk/ios 67 | rm -rf LaunchdarklyReactNativeClient.xcworkspace 68 | rm -rf build 69 | rm -rf Pods 70 | rm -rf Tests 71 | rm -rf Podfile 72 | rm -rf Podfile.lock 73 | 74 | - restore_cache: 75 | name: Restore gem cache 76 | key: v1-gem-cache-<>- 77 | 78 | # Newer cocoapods fixes Swift library auto-linking errors 79 | - run: 80 | name: Update CocoaPods 81 | command: | 82 | sudo gem install cocoapods 83 | sudo gem cleanup 84 | # Used as cache key to prevent storing redundant caches 85 | gem list > /tmp/cache-key.txt 86 | 87 | - save_cache: 88 | name: Save gem cache 89 | key: v1-gem-cache-<>-{{ checksum "/tmp/cache-key.txt" }} 90 | paths: 91 | - ~/.gem 92 | 93 | - run: 94 | name: Install iOS Pods 95 | command: cd ../test/CITest/ios && pod install 96 | 97 | - run: 98 | name: Build application for iOS (Release) 99 | command: | 100 | mkdir -p artifacts 101 | cd ../test/CITest/ios 102 | xcodebuild -workspace CITest.xcworkspace -scheme CITest build -configuration Release -destination "generic/platform=iOS" CODE_SIGNING_ALLOWED=NO GCC_WARN_INHIBIT_ALL_WARNINGS=YES | tee '../../../project/artifacts/xcb-<>.txt' | xcpretty 103 | 104 | - when: 105 | # only build android once 106 | condition: 107 | and: 108 | - equal: [14.3.1, << parameters.xcode-version >>] 109 | - equal: [0.73.2, << parameters.rn-version >>] 110 | steps: 111 | - run: 112 | name: Build application for Android 113 | command: cd ../test/CITest/android && ./gradlew packageRelease 114 | 115 | - store_artifacts: 116 | path: artifacts 117 | 118 | workflows: 119 | version: 2 120 | install-sdk-build-app: 121 | jobs: 122 | - build-applications-using-template: 123 | name: rn<>-xc<>-build-apps-using-template 124 | matrix: 125 | parameters: 126 | rn-version: ['0.73.2'] 127 | xcode-version: ['14.3.1', '15.1'] 128 | -------------------------------------------------------------------------------- /ManualTestApp/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | apply plugin: "org.jetbrains.kotlin.android" 3 | apply plugin: "com.facebook.react" 4 | 5 | /** 6 | * This is the configuration block to customize your React Native Android app. 7 | * By default you don't need to apply any configuration, just uncomment the lines you need. 8 | */ 9 | react { 10 | /* Folders */ 11 | // The root of your project, i.e. where "package.json" lives. Default is '..' 12 | // root = file("../") 13 | // The folder where the react-native NPM package is. Default is ../node_modules/react-native 14 | // reactNativeDir = file("../node_modules/react-native") 15 | // The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen 16 | // codegenDir = file("../node_modules/@react-native/codegen") 17 | // The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js 18 | // cliFile = file("../node_modules/react-native/cli.js") 19 | 20 | /* Variants */ 21 | // The list of variants to that are debuggable. For those we're going to 22 | // skip the bundling of the JS bundle and the assets. By default is just 'debug'. 23 | // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. 24 | // debuggableVariants = ["liteDebug", "prodDebug"] 25 | 26 | /* Bundling */ 27 | // A list containing the node command and its flags. Default is just 'node'. 28 | // nodeExecutableAndArgs = ["node"] 29 | // 30 | // The command to run when bundling. By default is 'bundle' 31 | // bundleCommand = "ram-bundle" 32 | // 33 | // The path to the CLI configuration file. Default is empty. 34 | // bundleConfig = file(../rn-cli.config.js) 35 | // 36 | // The name of the generated asset file containing your JS bundle 37 | // bundleAssetName = "MyApplication.android.bundle" 38 | // 39 | // The entry file for bundle generation. Default is 'index.android.js' or 'index.js' 40 | // entryFile = file("../js/MyApplication.android.js") 41 | // 42 | // A list of extra flags to pass to the 'bundle' commands. 43 | // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle 44 | // extraPackagerArgs = [] 45 | 46 | /* Hermes Commands */ 47 | // The hermes compiler command to run. By default it is 'hermesc' 48 | // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc" 49 | // 50 | // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" 51 | // hermesFlags = ["-O", "-output-source-map"] 52 | } 53 | 54 | /** 55 | * Set this to true to Run Proguard on Release builds to minify the Java bytecode. 56 | */ 57 | def enableProguardInReleaseBuilds = false 58 | 59 | /** 60 | * The preferred build flavor of JavaScriptCore (JSC) 61 | * 62 | * For example, to use the international variant, you can use: 63 | * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` 64 | * 65 | * The international variant includes ICU i18n library and necessary data 66 | * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that 67 | * give correct results when using with locales other than en-US. Note that 68 | * this variant is about 6MiB larger per architecture than default. 69 | */ 70 | def jscFlavor = 'org.webkit:android-jsc:+' 71 | 72 | android { 73 | ndkVersion rootProject.ext.ndkVersion 74 | buildToolsVersion rootProject.ext.buildToolsVersion 75 | compileSdk rootProject.ext.compileSdkVersion 76 | 77 | namespace "com.manualtestapp" 78 | defaultConfig { 79 | applicationId "com.manualtestapp" 80 | minSdkVersion rootProject.ext.minSdkVersion 81 | targetSdkVersion rootProject.ext.targetSdkVersion 82 | versionCode 1 83 | versionName "1.0" 84 | } 85 | signingConfigs { 86 | debug { 87 | storeFile file('debug.keystore') 88 | storePassword 'android' 89 | keyAlias 'androiddebugkey' 90 | keyPassword 'android' 91 | } 92 | } 93 | buildTypes { 94 | debug { 95 | signingConfig signingConfigs.debug 96 | } 97 | release { 98 | // Caution! In production, you need to generate your own keystore file. 99 | // see https://reactnative.dev/docs/signed-apk-android. 100 | signingConfig signingConfigs.debug 101 | minifyEnabled enableProguardInReleaseBuilds 102 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 103 | } 104 | } 105 | } 106 | 107 | dependencies { 108 | // The version of react-native is set by the React Native Gradle Plugin 109 | implementation("com.facebook.react:react-android") 110 | implementation("com.facebook.react:flipper-integration") 111 | 112 | if (hermesEnabled.toBoolean()) { 113 | implementation("com.facebook.react:hermes-android") 114 | } else { 115 | implementation jscFlavor 116 | } 117 | } 118 | 119 | apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) 120 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LaunchDarkly Client-Side SDK for React Native 2 | 3 | > [!IMPORTANT] 4 | > As mentioned in the [repository changelog](https://github.com/launchdarkly/react-native-client-sdk/blob/main/CHANGELOG.md), the `launchdarkly-react-native-client-sdk` project has been renamed to `@launchdarkly/react-native-client-sdk`. All future releases will be made from the [new repository](https://github.com/launchdarkly/js-core/tree/main/packages/sdk/react-native). Please consider upgrading and filing potential requests in that repository's [issue tracker](https://github.com/launchdarkly/js-core/issues?q=is%3Aissue+is%3Aopen+label%3A%22package%3A+sdk%2Freact-native%22+sort%3Aupdated-desc). 5 | 6 | ## v9.x readme 7 | 8 | [![NPM](https://img.shields.io/npm/v/launchdarkly-react-native-client-sdk.svg)](https://www.npmjs.com/package/launchdarkly-react-native-client-sdk) 9 | [![CircleCI](https://circleci.com/gh/launchdarkly/react-native-client-sdk.svg?style=shield)](https://circleci.com/gh/launchdarkly/react-native-client-sdk) 10 | [![Documentation](https://img.shields.io/static/v1?label=GitHub+Pages&message=API+reference&color=00add8)](https://launchdarkly.github.io/react-native-client-sdk) 11 | 12 | ## LaunchDarkly overview 13 | 14 | [LaunchDarkly](https://www.launchdarkly.com) is a feature management platform that serves trillions of feature flags daily to help teams build better software, faster. [Get started](https://docs.launchdarkly.com/home/getting-started) using LaunchDarkly today! 15 | 16 | [![Twitter Follow](https://img.shields.io/twitter/follow/launchdarkly.svg?style=social&label=Follow&maxAge=2592000)](https://twitter.com/intent/follow?screen_name=launchdarkly) 17 | 18 | ## Supported versions 19 | [React Native SDK version 10](https://github.com/launchdarkly/js-core/tree/main/packages/sdk/react-native) is now released. 20 | 21 | The LaunchDarkly React Native SDK version 10 is written in pure JavaScript and is compatible with Expo. It supports hot reloading. For customers using React Native 0.72.x - 0.73.x, use the latest 10.x release. If you cannot upgrade to the 10.x yet, use RN 8.0.x and 9.0.x respectively. 22 | 23 | For React Native 0.71.x, use the latest 8.0.x release. 24 | 25 | For React Native 0.69.x - 0.70.x support, use the latest 7.1.x release. 26 | 27 | ## Getting started 28 | 29 | 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. 30 | 31 | ## Learn more 32 | 33 | Read 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). 34 | 35 | ## Testing 36 | 37 | We run integration tests for all our SDKs using a centralized test harness. This approach gives us the ability to test for consistency across SDKs, as well as test networking behavior in a long-running application. These tests cover each method in the SDK, and verify that event sending, flag evaluation, stream reconnection, and other aspects of the SDK all behave correctly. 38 | 39 | ## Contributing 40 | 41 | We encourage pull requests and other contributions from the community. Check out our [contributing guidelines](CONTRIBUTING.md) for instructions on how to contribute to this SDK. 42 | 43 | ## About LaunchDarkly 44 | 45 | - LaunchDarkly is a continuous delivery platform that provides feature flags as a service and allows developers to iterate quickly and safely. We allow you to easily flag your features and manage them from the LaunchDarkly dashboard. With LaunchDarkly, you can: 46 | - Roll out a new feature to a subset of your users (like a group of users who opt-in to a beta tester group), gathering feedback and bug reports from real-world use cases. 47 | - 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?). 48 | - 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. 49 | - 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. 50 | - 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. 51 | - Explore LaunchDarkly 52 | - [launchdarkly.com](https://www.launchdarkly.com/ 'LaunchDarkly Main Website') for more information 53 | - [docs.launchdarkly.com](https://docs.launchdarkly.com/ 'LaunchDarkly Documentation') for our documentation and SDK reference guides 54 | - [apidocs.launchdarkly.com](https://apidocs.launchdarkly.com/ 'LaunchDarkly API Documentation') for our API documentation 55 | - [blog.launchdarkly.com](https://blog.launchdarkly.com/ 'LaunchDarkly Blog Documentation') for the latest product updates 56 | 57 | ## Developing this SDK 58 | 59 | - Run `yarn doctor` in both the root and ManualTestApp directories and make sure everything is green 60 | - If watchman fails, you can try installing it manually `brew reinstall watchman` 61 | - Make sure you have [modd](https://github.com/cortesi/modd#install) installed so native code changes are hot reloaded 62 | 63 | * For ios run `yarn dev-ios` 64 | * For android run `yarn dev-android` 65 | -------------------------------------------------------------------------------- /android/src/test/java/LDUtilTest.java: -------------------------------------------------------------------------------- 1 | import static com.launchdarkly.reactnative.utils.LDUtil.configureContext; 2 | import static com.launchdarkly.reactnative.utils.LDUtil.createSingleContext; 3 | import static com.launchdarkly.reactnative.utils.LDUtil.validateConfig; 4 | import static org.junit.Assert.assertEquals; 5 | import static org.junit.Assert.assertFalse; 6 | import static org.junit.Assert.assertTrue; 7 | 8 | import com.facebook.react.bridge.JavaOnlyArray; 9 | import com.facebook.react.bridge.JavaOnlyMap; 10 | import com.facebook.react.bridge.ReadableType; 11 | import com.launchdarkly.sdk.LDContext; 12 | 13 | import org.junit.Test; 14 | 15 | public class LDUtilTest { 16 | private JavaOnlyMap createAnonymousNoKey() { 17 | JavaOnlyMap configMap = new JavaOnlyMap(); 18 | configMap.putString("kind", "employee"); 19 | configMap.putBoolean("anonymous", true); 20 | configMap.putString("name", "Yus"); 21 | 22 | return configMap; 23 | } 24 | 25 | private JavaOnlyMap createEmployeeContextMap() { 26 | JavaOnlyMap configMap = new JavaOnlyMap(); 27 | configMap.putString("kind", "employee"); 28 | configMap.putString("key", "blr123"); 29 | configMap.putBoolean("anonymous", true); 30 | configMap.putString("name", "Yus"); 31 | configMap.putInt("employeeNumber", 55); 32 | configMap.putBoolean("isActive", true); 33 | 34 | JavaOnlyMap addressMap = new JavaOnlyMap(); 35 | addressMap.putString("street", "Sunset Blvd"); 36 | addressMap.putInt("number", 321); 37 | configMap.putMap("address", addressMap); 38 | 39 | return configMap; 40 | } 41 | 42 | private JavaOnlyMap createOrgContextMap() { 43 | JavaOnlyMap configMap = new JavaOnlyMap(); 44 | configMap.putString("kind", "org"); 45 | configMap.putString("key", "qf32"); 46 | configMap.putString("name", "Qantas"); 47 | configMap.putInt("employeeCount", 10000); 48 | configMap.putBoolean("isInternational", true); 49 | 50 | JavaOnlyMap addressMap = new JavaOnlyMap(); 51 | addressMap.putString("street", "Bourke St"); 52 | addressMap.putInt("number", 22); 53 | addressMap.putString("country", "Australia"); 54 | configMap.putMap("address", addressMap); 55 | 56 | return configMap; 57 | } 58 | 59 | private JavaOnlyMap createMultiContextMap() { 60 | JavaOnlyMap multi = new JavaOnlyMap(); 61 | multi.putString("kind", "multi"); 62 | multi.putMap("employee", createEmployeeContextMap()); 63 | multi.putMap("org", createOrgContextMap()); 64 | return multi; 65 | } 66 | 67 | @Test 68 | public void testValidateConfig() { 69 | JavaOnlyMap configMap = createEmployeeContextMap(); 70 | 71 | assertFalse(validateConfig("stream", configMap, ReadableType.Boolean)); 72 | assertTrue(validateConfig("kind", configMap, ReadableType.String)); 73 | assertTrue(validateConfig("key", configMap, ReadableType.String)); 74 | } 75 | 76 | @Test 77 | public void testCreateSingleContextSuccess() { 78 | JavaOnlyMap configMap = createEmployeeContextMap(); 79 | LDContext c = createSingleContext(configMap, "employee"); 80 | 81 | assertTrue(c.isValid()); 82 | assertEquals("employee", c.getKind().toString()); 83 | assertEquals("blr123", c.getKey()); 84 | assertTrue(c.isAnonymous()); 85 | assertEquals("Yus", c.getName()); 86 | assertEquals(55, c.getValue("employeeNumber").intValue()); 87 | assertTrue(c.getValue("isActive").booleanValue()); 88 | assertEquals("{\"kind\":\"employee\",\"key\":\"blr123\",\"name\":\"Yus\",\"anonymous\":true,\"address\":{\"number\":321,\"street\":\"Sunset Blvd\"},\"isActive\":true,\"employeeNumber\":55}", c.toString()); 89 | } 90 | 91 | @Test 92 | public void testCreateSingleContextNoKey() { 93 | JavaOnlyMap configMap = new JavaOnlyMap(); 94 | LDContext c = createSingleContext(configMap, "employee"); 95 | 96 | assertFalse(c.isValid()); 97 | } 98 | 99 | @Test 100 | public void testAnonymousNoKey() { 101 | LDContext c = createSingleContext(createAnonymousNoKey(), "employee"); 102 | 103 | assertTrue(c.isValid()); 104 | assertTrue(c.isAnonymous()); 105 | assertEquals("__LD_PLACEHOLDER_KEY__", c.getKey()); 106 | } 107 | 108 | @Test 109 | public void testCreateSingleContextWithMeta() { 110 | JavaOnlyMap configMap = createEmployeeContextMap(); 111 | 112 | JavaOnlyMap metaMap = new JavaOnlyMap(); 113 | JavaOnlyArray arr = new JavaOnlyArray(); 114 | arr.pushString("employeeNumber"); 115 | arr.pushString("address"); 116 | metaMap.putArray("privateAttributes", arr); 117 | configMap.putMap("_meta", metaMap); 118 | 119 | LDContext c = createSingleContext(configMap, "employee"); 120 | 121 | assertTrue(c.isValid()); 122 | assertEquals(2, c.getPrivateAttributeCount()); 123 | assertEquals("{\"kind\":\"employee\",\"key\":\"blr123\",\"name\":\"Yus\",\"anonymous\":true,\"address\":{\"number\":321,\"street\":\"Sunset Blvd\"},\"isActive\":true,\"employeeNumber\":55,\"_meta\":{\"privateAttributes\":[\"employeeNumber\",\"address\"]}}", c.toString()); 124 | } 125 | 126 | @Test 127 | public void testMultiContextSuccess() { 128 | LDContext c = configureContext(createMultiContextMap()); 129 | 130 | assertTrue(c.isValid()); 131 | assertEquals("{\"kind\":\"multi\",\"employee\":{\"key\":\"blr123\",\"name\":\"Yus\",\"anonymous\":true,\"address\":{\"number\":321,\"street\":\"Sunset Blvd\"},\"isActive\":true,\"employeeNumber\":55},\"org\":{\"key\":\"qf32\",\"name\":\"Qantas\",\"address\":{\"number\":22,\"country\":\"Australia\",\"street\":\"Bourke St\"},\"isInternational\":true,\"employeeCount\":10000}}", c.toString()); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /test-types.ts: -------------------------------------------------------------------------------- 1 | // This file exists only so that we can run the TypeScript compiler in the CI build 2 | // to validate our index.d.ts file. The code will not actually be run. 3 | 4 | import LDClient, { 5 | LDConnectionMode, 6 | LDConfig, 7 | LDEvaluationDetail, 8 | LDEvaluationReason, 9 | LDFailureReason, 10 | LDFlagSet, 11 | LDContext, 12 | } from 'launchdarkly-react-native-client-sdk'; 13 | 14 | async function tests() { 15 | const jsonObj: Record = { 16 | a: 's', 17 | b: true, 18 | c: 3, 19 | d: ['x', 'y'], 20 | e: [true, false], 21 | f: [1, 2], 22 | }; 23 | 24 | const jsonArr: any[] = ['a', 1, null, false, { a: 3 }, []]; 25 | 26 | const configWithKeyOnly: LDConfig = { 27 | mobileKey: '', 28 | enableAutoEnvAttributes: true, 29 | }; 30 | const configWithAllOptions: LDConfig = { 31 | application: { 32 | id: 'rn-unit-test', 33 | version: '0.0.1', 34 | name: 'RN app with LD', 35 | versionName: 'Beta release 0.0.1', 36 | }, 37 | mobileKey: '', 38 | enableAutoEnvAttributes: true, 39 | pollUrl: '', 40 | streamUrl: '', 41 | eventsUrl: '', 42 | eventCapacity: 1, 43 | flushInterval: 1, 44 | connectionTimeout: 1, 45 | pollingInterval: 1, 46 | backgroundPollingInterval: 1, 47 | useReport: true, 48 | stream: true, 49 | disableBackgroundUpdating: true, 50 | offline: true, 51 | debugMode: true, 52 | evaluationReasons: true, 53 | secondaryMobileKeys: { test: 'fake_key' }, 54 | maxCachedContexts: 6, 55 | diagnosticOptOut: true, 56 | diagnosticRecordingInterval: 100000, 57 | allAttributesPrivate: true, 58 | privateAttributes: ['abc', 'def'], 59 | }; 60 | const userEmpty: LDContext = {}; 61 | const userWithKeyOnly: LDContext = { kind: 'user', key: 'test-user-1' }; 62 | const user: LDContext = { 63 | kind: 'user', 64 | key: 'test-user-2', 65 | name: 'name', 66 | firstName: 'first', 67 | lastName: 'last', 68 | email: 'test@example.com', 69 | anonymous: true, 70 | country: 'us', 71 | privateAttributeNames: ['name', 'email'], 72 | custom: jsonObj, 73 | avatar: 'avatar', 74 | ip: '192.0.2.1', 75 | }; 76 | const client: LDClient = new LDClient(); 77 | const timeoutClient: LDClient = new LDClient(); 78 | 79 | const configure: null = await client.configure(configWithAllOptions, user); 80 | const configureWithTimeout: null = await timeoutClient.configure(configWithAllOptions, userWithKeyOnly, 10); 81 | const identify: null = await client.identify(user); 82 | 83 | const boolFlagValue: boolean = await client.boolVariation('key', false); 84 | const floatFlagValue: number = await client.numberVariation('key', 2.3); 85 | const stringFlagValue: string = await client.stringVariation('key', 'default'); 86 | const jsonObjFlagValue: any = await client.jsonVariation('key', jsonObj); 87 | const jsonArrFlagValue: any = await client.jsonVariation('key', jsonArr); 88 | const jsonSimpleFlagValue: any = await client.jsonVariation('key', 3); 89 | 90 | const boolDetail: LDEvaluationDetail = await client.boolVariationDetail('key', false); 91 | const floatDetail: LDEvaluationDetail = await client.numberVariationDetail('key', 2.3); 92 | const stringDetail: LDEvaluationDetail = await client.stringVariationDetail('key', 'default'); 93 | const jsonDetail: LDEvaluationDetail = await client.jsonVariationDetail('key', jsonObj); 94 | 95 | const boolDetailMulti: LDEvaluationDetail = await client.boolVariationDetail('key', false, 'test'); 96 | const floatDetailMulti: LDEvaluationDetail = await client.numberVariationDetail('key', 2.3, 'test'); 97 | const stringDetailMulti: LDEvaluationDetail = await client.stringVariationDetail('key', 'default', 'test'); 98 | const jsonDetailMulti: LDEvaluationDetail = await client.jsonVariationDetail('key', jsonObj, 'test'); 99 | 100 | const detailIndex: number | undefined = boolDetail.variationIndex; 101 | const detailReason: LDEvaluationReason = boolDetail.reason; 102 | const detailBoolValue: boolean = boolDetail.value; 103 | const detailFloatValue: number = floatDetail.value; 104 | const detailStringValue: string = stringDetail.value; 105 | const detailJsonValue: Record = jsonDetail.value; 106 | 107 | const flagSet: LDFlagSet = await client.allFlags(); 108 | const flagSetValue: any = flagSet['key']; 109 | 110 | const track1: void = await client.track('eventname'); 111 | const track2: void = await client.track('eventname', undefined); 112 | const track3: void = await client.track('eventname', true); 113 | const track4: void = await client.track('eventname', 2); 114 | const track5: void = await client.track('eventname', 2.3); 115 | const track6: void = await client.track('eventname', 'something'); 116 | const track7: void = await client.track('eventname', [2, 3]); 117 | const track8: void = await client.track('eventname', { foo: 2 }); 118 | const track9: void = await client.track('eventname', { foo: 2 }, 4); 119 | 120 | const setOffline: boolean = await client.setOffline(); 121 | const setOnline: boolean = await client.setOnline(); 122 | const isOffline: boolean = await client.isOffline(); 123 | const isInitialized: boolean = await client.isInitialized(); 124 | 125 | const callback = function (_: string): void {}; 126 | const registerFeatureFlagListener: void = client.registerFeatureFlagListener('key', callback); 127 | const unregisterFeatureFlagListener: void = client.unregisterFeatureFlagListener('key', callback); 128 | const registerAllFlagsListener: void = client.registerAllFlagsListener('id', (flags) => flags); 129 | const unregisterAllFlagsListener: void = client.unregisterAllFlagsListener('id'); 130 | const registerCurrentConnectionModeListener: void = client.registerCurrentConnectionModeListener('id', callback); 131 | const unregisterCurrentConnectionModeListener: void = client.unregisterCurrentConnectionModeListener('id'); 132 | 133 | const getConnectionMode: LDConnectionMode = await client.getConnectionMode(); 134 | const getSuccessfulConnection: number | null = await client.getLastSuccessfulConnection(); 135 | const getFailedConnection: number | null = await client.getLastFailedConnection(); 136 | const getFailureReason: LDFailureReason | null = await client.getLastFailure(); 137 | 138 | const flush: void = await client.flush(); 139 | const close: void = await client.close(); 140 | 141 | const version: String = client.getVersion(); 142 | } 143 | 144 | tests(); 145 | -------------------------------------------------------------------------------- /ios/Tests/Tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Tests.swift 3 | // Tests 4 | // 5 | // Created by Yusinto Ngadiman on 2/15/23. 6 | // Copyright © 2023 Facebook. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import LaunchDarkly 11 | @testable import LaunchdarklyReactNativeClient 12 | 13 | final class Tests: XCTestCase { 14 | let anonymousNoKey = #""" 15 | { 16 | "kind": "employee", 17 | "anonymous": true, 18 | "name": "Yus" 19 | } 20 | """# 21 | 22 | let employeeJson = #""" 23 | { 24 | "kind": "employee", 25 | "key": "blr123", 26 | "anonymous": true, 27 | "name": "Yus", 28 | "employeeNumber": 55, 29 | "isActive": true, 30 | "address": { 31 | "street": "Sunset Blvd", 32 | "number": 321 33 | }, 34 | "_meta": { 35 | "privateAttributes": ["address", "employeeNumber"] 36 | } 37 | } 38 | """# 39 | 40 | let multiJson = #""" 41 | { 42 | "kind": "multi", 43 | "employee": { 44 | "key": "blr123", 45 | "anonymous": true, 46 | "name": "Yus", 47 | "employeeNumber": 55, 48 | "isActive": true, 49 | "address": { 50 | "street": "Sunset Blvd", 51 | "number": 321 52 | }, 53 | "_meta": { 54 | "privateAttributes": ["address", "employeeNumber"] 55 | } 56 | }, 57 | "org": { 58 | "key": "qf32", 59 | "name": "Qantas", 60 | "employeeCount": 10000, 61 | "isInternational": true, 62 | "address": { 63 | "street": "Bourke St", 64 | "number": 22, 65 | "country": "Australia" 66 | } 67 | } 68 | } 69 | """# 70 | 71 | let config = #""" 72 | { 73 | "mobileKey": "mob-abc", 74 | "enableAutoEnvAttributes": true, 75 | "debugMode": true, 76 | "application": { 77 | "id": "rn-unit-test", 78 | "version": "x.y.z" 79 | }, 80 | "pollUrl": "https://poll-url", 81 | "eventsUrl": "https://events-url", 82 | "streamUrl": "https://stream-url", 83 | "eventCapacity": 11, 84 | "flushInterval": 22, 85 | "connectionTimeout": 33, 86 | "pollingInterval": 44, 87 | "backgroundPollingInterval": 55, 88 | "maxCachedContexts": 66, 89 | "diagnosticRecordingInterval": 1200000, 90 | "allAttributesPrivate": true, 91 | "privateAttributes": ["address", "email", "username"] 92 | } 93 | """# 94 | private func jsonToDict(_ json: String) -> NSDictionary { 95 | let data = try json.data(using: .utf8) 96 | return try! (JSONSerialization.jsonObject(with: data!, options: []) as? [String : Any])! as NSDictionary 97 | } 98 | 99 | private func createEmployeeContext() -> NSDictionary { 100 | return jsonToDict(employeeJson) 101 | } 102 | 103 | private func createMultiContext() -> NSDictionary { 104 | return jsonToDict(multiJson) 105 | } 106 | 107 | func testCreateSingleContext() throws { 108 | let dict = createEmployeeContext() 109 | let context = try LaunchdarklyReactNativeClient().createSingleContext(dict as NSDictionary, dict["kind"] as! String) 110 | let anonymous = context.getValue(Reference("anonymous"))! 111 | let name = context.getValue(Reference("name"))! 112 | 113 | XCTAssertEqual(context.kind, Kind.custom("employee")) 114 | XCTAssertEqual(context.contextKeys(), ["employee": "blr123"]) 115 | XCTAssertEqual(true, anonymous) 116 | XCTAssertEqual(name, "Yus") 117 | XCTAssertEqual(context.attributes, ["employeeNumber": LDValue.number(55.0), "address": LDValue.object(["number": LDValue.number(321.0), "street": LDValue.string("Sunset Blvd")]), "isActive": LDValue.bool(true)]) 118 | XCTAssertEqual(context.privateAttributes, [Reference("address"), Reference("employeeNumber")]) 119 | XCTAssertFalse(context.isMulti()) 120 | 121 | let expected = try JSONDecoder().decode(LDContext.self, from: Data(employeeJson.utf8)) 122 | XCTAssertEqual(context, expected) 123 | } 124 | 125 | func testAnonymousNoKey() throws { 126 | let dict = jsonToDict(anonymousNoKey) 127 | let context = try LaunchdarklyReactNativeClient().createSingleContext(dict as NSDictionary, dict["kind"] as! String) 128 | let anonymous = context.getValue(Reference("anonymous"))! 129 | let uuid = context.getValue(Reference("key"))! 130 | XCTAssertEqual(true, anonymous) 131 | XCTAssertNotEqual(LDValue.null, uuid) 132 | } 133 | 134 | func testCreateMultiContext() throws { 135 | let dict = createMultiContext() 136 | let context = try LaunchdarklyReactNativeClient().contextBuild(dict as NSDictionary) 137 | 138 | XCTAssertTrue(context.isMulti()) 139 | XCTAssertEqual(context.contextKeys(), ["employee": "blr123", "org": "qf32"]) 140 | 141 | let expected = try JSONDecoder().decode(LDContext.self, from: Data(multiJson.utf8)) 142 | XCTAssertEqual(context, expected) 143 | } 144 | 145 | func testConfigBuild() throws { 146 | let configDict = jsonToDict(config) 147 | let config = try LaunchdarklyReactNativeClient().configBuild(config: configDict)! 148 | 149 | XCTAssertEqual(config.applicationInfo?.buildTag(), "application-id/rn-unit-test application-version/x.y.z") 150 | XCTAssertEqual(config.baseUrl.absoluteString, configDict["pollUrl"] as! String) 151 | XCTAssertEqual(config.eventsUrl.absoluteString, configDict["eventsUrl"] as! String) 152 | XCTAssertEqual(config.streamUrl.absoluteString, configDict["streamUrl"] as! String) 153 | XCTAssertEqual(config.eventCapacity, configDict["eventCapacity"] as! Int) 154 | XCTAssertEqual(config.eventFlushInterval as Double * 1000, configDict["flushInterval"] as! Double) 155 | XCTAssertEqual(config.connectionTimeout as Double * 1000, configDict["connectionTimeout"] as! Double) 156 | XCTAssertEqual(config.flagPollingInterval as Double * 1000, configDict["pollingInterval"] as! Double) 157 | XCTAssertEqual(config.backgroundFlagPollingInterval as Double * 1000, configDict["backgroundPollingInterval"] as! Double) 158 | XCTAssertEqual(config.maxCachedContexts, configDict["maxCachedContexts"] as! Int) 159 | XCTAssertEqual(config.diagnosticRecordingInterval as Double * 1000, configDict["diagnosticRecordingInterval"] as! Double) 160 | XCTAssertTrue(config.allContextAttributesPrivate) 161 | XCTAssertEqual(config.privateContextAttributes, [Reference("address"), Reference("email"), Reference("username")]) 162 | XCTAssertEqual(config.autoEnvAttributes, configDict["enableAutoEnvAttributes"] as! Bool) 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /ManualTestApp/App.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-bitwise,react-native/no-inline-styles */ 2 | import React, {useState, useEffect, ReactNode} from 'react'; 3 | import { 4 | SafeAreaView, 5 | ScrollView, 6 | StyleSheet, 7 | Text, 8 | View, 9 | Button, 10 | TextInput, 11 | Alert, 12 | Switch, 13 | } from 'react-native'; 14 | import {MOBILE_KEY} from '@env'; 15 | import {Picker} from '@react-native-picker/picker'; 16 | import LDClient, { 17 | LDConfig, 18 | LDMultiKindContext, 19 | } from 'launchdarkly-react-native-client-sdk'; 20 | import MessageQueue from 'react-native/Libraries/BatchedBridge/MessageQueue'; 21 | 22 | const Wrapper = ({children}: {children: ReactNode}) => { 23 | const styles = { 24 | scroll: {backgroundColor: '#fff', padding: 10}, 25 | area: {backgroundColor: '#fff', flex: 1}, 26 | }; 27 | return ( 28 | 29 | {children} 30 | 31 | ); 32 | }; 33 | 34 | const Body = () => { 35 | const [client, setClient] = useState(null); 36 | const [flagKey, setFlagKey] = useState('dev-test-flag'); 37 | const [flagType, setFlagType] = useState('bool'); 38 | const [isOffline, setIsOffline] = useState(false); 39 | const [contextKey, setContextKey] = useState('context-key'); 40 | const [listenerKey, setListenerKey] = useState(''); 41 | const [listeners, setListeners] = useState({}); 42 | 43 | useEffect(() => { 44 | async function initializeClient() { 45 | let ldClient = new LDClient(); 46 | let config: LDConfig = { 47 | mobileKey: MOBILE_KEY, 48 | enableAutoEnvAttributes: true, 49 | debugMode: true, 50 | application: { 51 | id: 'rn-manual-test-app', 52 | version: '0.0.1', 53 | }, 54 | }; 55 | const userContext = { 56 | kind: 'user', 57 | key: 'test-key', 58 | }; 59 | const multiContext: LDMultiKindContext = { 60 | kind: 'multi', 61 | user: userContext, 62 | org: { 63 | key: 'org-key', 64 | name: 'Example organization name', 65 | _meta: { 66 | privateAttributes: ['address', 'phone'], 67 | }, 68 | address: { 69 | street: 'sunset blvd', 70 | postcode: 94105, 71 | }, 72 | phone: 5551234, 73 | }, 74 | }; 75 | 76 | try { 77 | await ldClient.configure(config, multiContext); 78 | } catch (err) { 79 | console.error(err); 80 | } 81 | setClient(ldClient); 82 | } 83 | 84 | if (client == null) { 85 | initializeClient().then(() => 86 | console.log('ld client initialized successfully'), 87 | ); 88 | } 89 | }); 90 | 91 | const evalFlag = async () => { 92 | let res; 93 | if (flagType === 'bool') { 94 | res = await client?.boolVariation(flagKey, false); 95 | } else if (flagType === 'string') { 96 | res = await client?.stringVariation(flagKey, ''); 97 | } else if (flagType === 'number') { 98 | res = await client?.numberVariationDetail(flagKey, 33); 99 | } else if (flagType === 'json') { 100 | res = await client?.jsonVariation(flagKey, null); 101 | } 102 | 103 | Alert.alert('LD Server Response', JSON.stringify(res)); 104 | }; 105 | 106 | const track = () => { 107 | client?.track(flagKey, false); 108 | }; 109 | 110 | const identify = () => { 111 | client?.identify({kind: 'user', key: contextKey}); 112 | }; 113 | 114 | const listen = () => { 115 | if (listeners.hasOwnProperty(listenerKey)) { 116 | return; 117 | } 118 | let listener = (value: string | undefined) => 119 | Alert.alert('Listener Callback', value); 120 | client?.registerFeatureFlagListener(listenerKey, listener); 121 | setListeners({...listeners, ...{[listenerKey]: listener}}); 122 | }; 123 | 124 | const removeListener = () => { 125 | // @ts-ignore 126 | client?.unregisterFeatureFlagListener(listenerKey, listeners[listenerKey]); 127 | // @ts-ignore 128 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 129 | let {[listenerKey]: omit, ...newListeners} = listeners; 130 | setListeners(newListeners); 131 | }; 132 | 133 | const flush = () => { 134 | client?.flush(); 135 | }; 136 | 137 | const setOffline = (offline: boolean) => { 138 | if (offline) { 139 | client?.setOffline(); 140 | } else { 141 | client?.setOnline(); 142 | } 143 | 144 | setIsOffline(offline); 145 | }; 146 | 147 | return ( 148 | <> 149 | Feature Key: 150 | 156 | 157 |