From a343c8345ffff044ef8db478ddce92e0e68d3cfb Mon Sep 17 00:00:00 2001 From: Yusinto Ngadiman Date: Thu, 30 Nov 2023 15:21:22 -0800 Subject: [PATCH] chore: RN provider and hooks attempt 2 (#321) This is a second attempt of #306 using the newly added event source. - use StreamingProcessor and event source to fetch flags - added LDProvider - added hooks - added typed variation[Detail] functions Please read the [example README](https://github.com/launchdarkly/js-core/blob/9d458f28625c12169df614c8d2289f9c0952cc45/packages/sdk/react-native/example/README.md#L1) and try running the example app. --------- Co-authored-by: LaunchDarklyReleaseBot Co-authored-by: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> --- .eslintrc.js | 1 + packages/sdk/react-native/README.md | 12 +- packages/sdk/react-native/example/.gitignore | 3 + packages/sdk/react-native/example/App.tsx | 48 +-- packages/sdk/react-native/example/README.md | 22 +- packages/sdk/react-native/example/app.json | 10 +- .../sdk/react-native/example/package.json | 14 +- .../sdk/react-native/example/src/welcome.tsx | 41 ++ .../sdk/react-native/example/types/env.d.ts | 2 +- packages/sdk/react-native/link-dev.sh | 16 +- packages/sdk/react-native/package.json | 4 +- .../src/ReactNativeLDClient.test.ts | 23 ++ .../react-native/src/ReactNativeLDClient.ts | 18 + packages/sdk/react-native/src/hooks/index.ts | 6 + .../sdk/react-native/src/hooks/useLDClient.ts | 10 + .../src/hooks/useLDDataSourceStatus.ts | 10 + .../react-native/src/hooks/variation/index.ts | 2 + .../src/hooks/variation/useTypedVariation.ts | 82 ++++ .../src/hooks/variation/useVariation.ts | 147 +++++++ packages/sdk/react-native/src/index.test.tsx | 1 - packages/sdk/react-native/src/index.ts | 8 +- packages/sdk/react-native/src/init.ts | 11 - .../react-native/src/provider/LDProvider.tsx | 32 ++ .../sdk/react-native/src/provider/index.ts | 4 + .../react-native/src/provider/reactContext.ts | 21 + .../src/provider/setupListeners.ts | 25 ++ .../src/react-native-sse/EventSource.ts | 4 +- packages/shared/common/src/utils/http.ts | 14 +- packages/shared/common/src/utils/index.ts | 3 +- .../shared/mocks/src/streamingProcessor.ts | 10 +- .../sdk-client/src/LDClientImpl.test.ts | 73 ++-- .../shared/sdk-client/src/LDClientImpl.ts | 197 ++++++++-- .../shared/sdk-client/src/api/LDClient.ts | 362 +++++++++++++----- .../sdk-client/src/api/LDEmitter.test.ts | 1 + .../shared/sdk-client/src/api/LDEmitter.ts | 2 +- .../sdk-client/src/evaluation/fetchUtils.ts | 21 +- 36 files changed, 997 insertions(+), 263 deletions(-) create mode 100644 packages/sdk/react-native/example/src/welcome.tsx create mode 100644 packages/sdk/react-native/src/ReactNativeLDClient.test.ts create mode 100644 packages/sdk/react-native/src/ReactNativeLDClient.ts create mode 100644 packages/sdk/react-native/src/hooks/index.ts create mode 100644 packages/sdk/react-native/src/hooks/useLDClient.ts create mode 100644 packages/sdk/react-native/src/hooks/useLDDataSourceStatus.ts create mode 100644 packages/sdk/react-native/src/hooks/variation/index.ts create mode 100644 packages/sdk/react-native/src/hooks/variation/useTypedVariation.ts create mode 100644 packages/sdk/react-native/src/hooks/variation/useVariation.ts delete mode 100644 packages/sdk/react-native/src/index.test.tsx delete mode 100644 packages/sdk/react-native/src/init.ts create mode 100644 packages/sdk/react-native/src/provider/LDProvider.tsx create mode 100644 packages/sdk/react-native/src/provider/index.ts create mode 100644 packages/sdk/react-native/src/provider/reactContext.ts create mode 100644 packages/sdk/react-native/src/provider/setupListeners.ts diff --git a/.eslintrc.js b/.eslintrc.js index 98be7e8ec..4a0af77b6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -25,6 +25,7 @@ module.exports = { ], 'import/default': 'error', 'import/export': 'error', + 'import/extensions': ['error', 'never', { json: 'always' }], 'import/no-self-import': 'error', 'import/no-cycle': 'error', 'import/no-useless-path-segments': 'error', diff --git a/packages/sdk/react-native/README.md b/packages/sdk/react-native/README.md index 08f5848de..da3d52acb 100644 --- a/packages/sdk/react-native/README.md +++ b/packages/sdk/react-native/README.md @@ -25,8 +25,6 @@ yarn add @launchdarkly/react-native-client-sdk TODO ```typescript -import { init } from '@launchdarkly/react-native-client-sdk'; - // TODO ``` @@ -34,15 +32,7 @@ See the full [example app](https://github.com/launchdarkly/js-core/tree/main/pac ## Developing this SDK -:information_source: You will need to setup your sdk key in the example dir. See the example [README](https://github.com/launchdarkly/js-core/blob/main/packages/sdk/react-native/example/README.md#L1). - -```shell -# at js-core repo root -yarn && yarn build - -# at sdk/react-native repo -yarn android | ios -``` +:information_source: See the example [README](https://github.com/launchdarkly/js-core/blob/main/packages/sdk/react-native/example/README.md#L1). ## About LaunchDarkly diff --git a/packages/sdk/react-native/example/.gitignore b/packages/sdk/react-native/example/.gitignore index 88aca5470..b252443a3 100644 --- a/packages/sdk/react-native/example/.gitignore +++ b/packages/sdk/react-native/example/.gitignore @@ -34,3 +34,6 @@ yarn-error.* # typescript *.tsbuildinfo + +ios +android diff --git a/packages/sdk/react-native/example/App.tsx b/packages/sdk/react-native/example/App.tsx index 93675937b..56634e16e 100644 --- a/packages/sdk/react-native/example/App.tsx +++ b/packages/sdk/react-native/example/App.tsx @@ -1,44 +1,18 @@ -import { CLIENT_SIDE_SDK_KEY } from '@env'; -import React, { useEffect, useState } from 'react'; -import { StyleSheet, Text, View } from 'react-native'; +import { MOBILE_KEY } from '@env'; -import { init, type LDClientImpl } from '@launchdarkly/react-native-client-sdk'; +import { LDProvider, ReactNativeLDClient } from '@launchdarkly/react-native-client-sdk'; -const context = { kind: 'user', key: 'test-user-1' }; - -export default function App() { - const [ldc, setLdc] = useState(); - const [flag, setFlag] = useState(false); +import Welcome from './src/welcome'; - useEffect(() => { - init(CLIENT_SIDE_SDK_KEY, context) - .then((c) => { - setLdc(c); - }) - .catch((e) => console.log(e)); - }, []); - - useEffect(() => { - const f = ldc?.boolVariation('dev-test-flag', false); - setFlag(f ?? false); - }, [ldc]); +const featureClient = new ReactNativeLDClient(MOBILE_KEY); +const context = { kind: 'user', key: 'test-user-1' }; +const App = () => { return ( - - {flag ? <>devTestFlag: {`${flag}`} : <>loading...} - + + + ); -} +}; -const styles = StyleSheet.create({ - container: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - }, - box: { - width: 60, - height: 60, - marginVertical: 20, - }, -}); +export default App; diff --git a/packages/sdk/react-native/example/README.md b/packages/sdk/react-native/example/README.md index bbd8c9b0a..2307d41b9 100644 --- a/packages/sdk/react-native/example/README.md +++ b/packages/sdk/react-native/example/README.md @@ -2,19 +2,29 @@ To run the example app: -1. Create a `.env` file at the same level as this README -2. Add your client-side sdk key to that `.env` file: +1. At the js-core repo root: ```shell -CLIENT_SIDE_SDK_KEY=abcdef12456 +yarn && yarn build ``` -3. Finally +2. Create an `.env` file at the same level as this README and add your mobile key to that `.env` file: ```shell +MOBILE_KEY=abcdef12456 +``` + +3. Replace `dev-test-flag` with your flag key in `src/welcome.tsx`. + +4. Run the app: + +```shell +# Note for android, there's an issue with Flipper interfering with streaming connections +# so please run the release build. There's no such issue with ios. + # android -yarn && yarn android +yarn && yarn android-release # ios -yarn && yarn ios +yarn && yarn ios-go ``` diff --git a/packages/sdk/react-native/example/app.json b/packages/sdk/react-native/example/app.json index c728b74ef..b797a3a7e 100644 --- a/packages/sdk/react-native/example/app.json +++ b/packages/sdk/react-native/example/app.json @@ -11,15 +11,19 @@ "resizeMode": "contain", "backgroundColor": "#ffffff" }, - "assetBundlePatterns": ["**/*"], + "assetBundlePatterns": [ + "**/*" + ], "ios": { - "supportsTablet": true + "supportsTablet": true, + "bundleIdentifier": "com.anonymous.reactnativeexample" }, "android": { "adaptiveIcon": { "foregroundImage": "./assets/adaptive-icon.png", "backgroundColor": "#ffffff" - } + }, + "package": "com.anonymous.reactnativeexample" }, "web": { "favicon": "./assets/favicon.png" diff --git a/packages/sdk/react-native/example/package.json b/packages/sdk/react-native/example/package.json index d6ca0e3e7..113904380 100644 --- a/packages/sdk/react-native/example/package.json +++ b/packages/sdk/react-native/example/package.json @@ -4,13 +4,21 @@ "main": "node_modules/expo/AppEntry.js", "scripts": { "start": "expo start", - "android": "expo start --android --clear", - "ios": "expo start --ios --clear", + "expo-clean": "expo prebuild --clean", + "android": "expo run:android", + "android-release": "expo run:android --variant release", + "android-go": "expo start --android --clear", + "android-log": "react-native log-android", + "ios": "expo run:ios", + "ios-release": "expo run:ios --configuration Release", + "ios-go": "expo start --ios --clear", + "ios-log": "react-native log-ios", "web": "expo start --web --clear", - "clean": "yarn cache clean && rm -rf node_modules" + "clean": "expo prebuild --clean && yarn cache clean && rm -rf node_modules && rm -rf .expo" }, "dependencies": { "expo": "~49.0.16", + "expo-splash-screen": "~0.20.5", "expo-status-bar": "~1.7.1", "react": "18.2.0", "react-native": "0.72.6" diff --git a/packages/sdk/react-native/example/src/welcome.tsx b/packages/sdk/react-native/example/src/welcome.tsx new file mode 100644 index 000000000..dd42ebbf6 --- /dev/null +++ b/packages/sdk/react-native/example/src/welcome.tsx @@ -0,0 +1,41 @@ +import { Button, StyleSheet, Text, View } from 'react-native'; + +import { + useBoolVariation, + useLDClient, + useLDDataSourceStatus, +} from '@launchdarkly/react-native-client-sdk'; + +export default function Welcome() { + const { error, status } = useLDDataSourceStatus(); + const flag = useBoolVariation('dev-test-flag', false); + const ldc = useLDClient(); + + const login = () => { + ldc.identify({ kind: 'user', key: 'test-user-2' }); + }; + + return ( + + Welcome to LaunchDarkly + status: {status ?? 'not connected'} + {error ? error: {error.message} : null} + devTestFlag: {`${flag}`} + context: {JSON.stringify(ldc.getContext())} +