From c93ff064a73e1b80ab412817a3d7b890d39dfd52 Mon Sep 17 00:00:00 2001 From: Yusinto Ngadiman Date: Thu, 23 Feb 2023 16:28:41 -0800 Subject: [PATCH] [sc-190014] Merge 7.0 for u2c release (#143) * Update swift and android sdk dependencies * Initial commit * TODO: comment out private attributes config and figure out later * Remove erroneous type annotation * TODO: fix deprecated config settings * Added todos to map deprecated android config * Initial refactor to move deprecated android config items * Start implementing deprecated config options * Implement android sdk major changes * Removed deprecated config options * Remove pollUri to use streaming and default endpoints * Implemented typescript changes * Renamed xxxUri to xxxUrl * Implemented swift config option changes * Fixed Reference creation for private context attributes * Update modd.conf * Removed jcenter * Reinstate user.privateAttributeNames because that should stay frozen * PR feedback * Update test-types.ts * Remove unnecessary stream: true config default * [sc-188224] Adios old rn versions * Set example app min ios target to 12.4 as mandated by rn 0.70. Improve local dev modd experience. * Added yarn-all scripts * Fixed broken tests by adding babel config and removing redundant transform.js * Remove old rn versions from CI * Force npm i to get around rn 6.3.0 still supporting old rn versions * Increase circleci macos resource to large * [sc-188335] Add u2c support (#140) * Added js common sdk context helper files. First attempt to implement glue code from js to android sdk. * Validate context in js before initializing ldclient * Added multi context support. Dry'd context manipulation logic. * Copy src folder to node_modules so its part of the npm package * Added arbitrary attributes mapping logic * Replaced src/common sdk files with actual js common sdk package * Refactored init code to be more robust with errors. Dry'd init logic. * Implemented identify logic for context. Changed example app to use multi context. * Autogenerate keys for anonymous context * Removed console logs and dry'd logic * Added contextUtils. Refactored isContext to js. * Initial attempt at ios context implementation * First cut ios implementation * Refactored helper functions to its own utils class. Added android unit tests. * Added multicontext tests * Added ios Podfile and fix dev build. Added swift unit tests. * Added swift unit test scaffold * Update Tests.swift * Improve ios unit tests * Speed up hot reload by replacing cp with rsync. Dry'd dir paths eliminating repetitions. * Update Tests.swift * Update .gitignore * Added more context and config tests * Add unit test for config build * Update modd-android.conf * Added contextUtils tests * Added typescript support for example app to test types. Replaced cp with rsync to speed up local dev. Improved tsconfig to use out of the box rn config. * Use common types for LDUser, LDContext and LDMultiKindContext * Update LaunchdarklyReactNativeClient.xcscheme * Update App.tsx * Prettified changelog * Updated common sdk version * Replaced user references with context * Use trySetValue for key, name and anonymous * Update config.yml * Added files property to specify only necessary files for npm publish * Exclude android test files from npm publish * Replace print with NSLog * Revert prettier changes. Ignore changelog from prettier. * Corrected misspelling of filename. * Added newline at eof * [sc-189696] Fix auto generation of anonymous keys (#141) * Initial commit * Rollback swift changes * Swift changes. * Updated js tests * Don't generate keys in js and defer context validation to native sdks * Update Tests.swift * Add java anonymous key test * Add github actions for tests * Update main.yml * Remove js tests from cicleci to github action. Prettified circleci config. * Update config.yml * Update config.yml * Update config.yml * Update config.yml * Add ios test job * Update main.yml * Update main.yml * Update main.yml * Update main.yml * Update main.yml * Update main.yml * Update main.yml * Update main.yml * Cache pods and quiet xcodebuild * Update main.yml * Update main.yml * Update main.yml * Update main.yml * Restore other tests --------- Co-authored-by: Yusinto Ngadiman * Fixed formatting of ga yml --------- Co-authored-by: Yusinto Ngadiman * [sc-169352] Add ldClient nil guards (#142) Update LaunchdarklyReactNativeClient.swift * Attempt to fix broken ios tests * Bust Pod cache * Update config.yml --------- Co-authored-by: Yusinto Ngadiman --- .circleci/config.yml | 69 +- .github/workflows/main.yml | 68 + .ldrelease/config.yml | 5 + .prettierignore | 1 + ManualTestApp/{App.js => App.tsx} | 89 +- .../android/app/src/main/AndroidManifest.xml | 34 +- ManualTestApp/android/build.gradle | 4 +- .../ManualTestApp.xcodeproj/project.pbxproj | 77 +- .../xcshareddata/IDEWorkspaceChecks.plist | 8 + ManualTestApp/ios/ManualTestApp/AppDelegate.m | 2 +- ManualTestApp/ios/Podfile | 2 +- ManualTestApp/ios/Podfile.lock | 613 +- ManualTestApp/package.json | 24 +- ManualTestApp/tsconfig.json | 3 + ManualTestApp/yarn.lock | 2171 +-- README.md | 6 +- __mocks__/native.js | 1 - android/build.gradle | 12 +- .../LaunchdarklyReactNativeClientModule.java | 452 +- .../reactnative/utils/LDUtil.java | 230 + android/src/test/java/LDUtilTest.java | 133 + babel.config.js | 3 + index.d.ts | 201 +- index.js | 71 +- index.test.js | 17 +- ios/LaunchdarklyReactNativeClient.swift | 195 +- .../project.pbxproj | 286 +- .../LaunchdarklyReactNativeClient.xcscheme | 91 + .../xcshareddata/xcschemes/Tests.xcscheme | 53 + .../contents.xcworkspacedata | 10 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + ios/LaunchdarklyReactNativeClientBridge.m | 8 +- ios/Podfile | 32 + ios/Podfile.lock | 555 + ios/Tests/Tests.swift | 163 + launchdarkly-react-native-client-sdk.podspec | 2 +- link-dev.sh | 1 + modd-android.conf | 1 + modd-ios.conf | 1 + modd.conf | 1 + package.json | 33 +- src/contextUtils.test.ts | 80 + src/contextUtils.ts | 53 + test-types.ts | 27 +- tsconfig.json | 4 +- yarn.lock | 10866 ++++++++-------- 46 files changed, 9375 insertions(+), 7391 deletions(-) create mode 100644 .github/workflows/main.yml create mode 100644 .prettierignore rename ManualTestApp/{App.js => App.tsx} (63%) create mode 100644 ManualTestApp/ios/ManualTestApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 ManualTestApp/tsconfig.json create mode 100644 android/src/main/java/com/launchdarkly/reactnative/utils/LDUtil.java create mode 100644 android/src/test/java/LDUtilTest.java create mode 100644 babel.config.js create mode 100644 ios/LaunchdarklyReactNativeClient.xcodeproj/xcshareddata/xcschemes/LaunchdarklyReactNativeClient.xcscheme create mode 100644 ios/LaunchdarklyReactNativeClient.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme create mode 100644 ios/LaunchdarklyReactNativeClient.xcworkspace/contents.xcworkspacedata create mode 100644 ios/LaunchdarklyReactNativeClient.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 ios/Podfile create mode 100644 ios/Podfile.lock create mode 100644 ios/Tests/Tests.swift create mode 100644 src/contextUtils.test.ts create mode 100644 src/contextUtils.ts diff --git a/.circleci/config.yml b/.circleci/config.yml index b150201..19b104c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -14,10 +14,10 @@ jobs: macos: xcode: <> - resource_class: macos.x86.medium.gen2 + resource_class: large environment: - ANDROID_SDK_ROOT: "/tmp/Android" + ANDROID_SDK_ROOT: '/tmp/Android' steps: - checkout @@ -49,16 +49,6 @@ jobs: mkdir -p test cd test [ -d "CITest" ] || npx react-native@<> init CITest --version <> --skip-install - - # HACK: rn 0.64.x init the test project targeting ios 10, which is incompatible with the ios SDK which requires - # a minimum of ios 11 - sed -i.bak "s/platform :ios, '10.0'/platform :ios, '11.0'/" CITest/ios/Podfile - sed -i.bak "s/IPHONEOS_DEPLOYMENT_TARGET = 10.0;/IPHONEOS_DEPLOYMENT_TARGET = 11.0;/" CITest/ios/CITest.xcodeproj/project.pbxproj - - #HACK: force rn android 0.64.x init to use minSdkVersion 30 to pass the build - sed -i.bak 's/buildToolsVersion = "29.0.3"/buildToolsVersion = "30.0.2"/' CITest/android/build.gradle - sed -i.bak "s/compileSdkVersion = 29/compileSdkVersion = 31/" CITest/android/build.gradle - sed -i.bak "s/targetSdkVersion = 29/targetSdkVersion = 31/" CITest/android/build.gradle - save_cache: name: Save RN project template to cache @@ -75,7 +65,15 @@ jobs: - run: name: Add LaunchDarkly dependency - command: cd ../test/CITest && npx yarn add file:../../project + command: | + cd ../test/CITest && npx yarn add file:../../project + cd node_modules/launchdarkly-react-native-client-sdk/ios + rm -rf LaunchdarklyReactNativeClient.xcworkspace + rm -rf build + rm -rf Pods + rm -rf Tests + rm -rf Podfile + rm -rf Podfile.lock - restore_cache: name: Restore gem cache @@ -110,7 +108,7 @@ jobs: - when: # We only care to build Android application and debug iOS Build for a single XCode version condition: - equal: [ 13.2.1, << parameters.xcode-version >> ] + equal: [13.2.1, << parameters.xcode-version >>] steps: - run: name: Build application for iOS (Debug) @@ -124,43 +122,22 @@ jobs: - store_artifacts: path: artifacts - test-javascript: - docker: - - image: cimg/node:current - steps: - - checkout - - - run: npm install - - run: mkdir -p reports/jest - - run: - command: npm run test:junit - environment: - JEST_JUNIT_OUTPUT_DIR: "./reports/jest" - - - run: npm run check-typescript - - - store_test_results: - path: reports - workflows: version: 2 - all-tests: + install-sdk-build-app: jobs: - - test-javascript - build-applications-using-template: name: rn<>-xc<>-build-apps-using-template matrix: parameters: - rn-version: [ "0.64.4", "0.65.2", "0.66.4", "0.67.3", "0.68.0","0.69.4", "0.70.1" ] - xcode-version: [ "12.5.1", "13.2.1", "13.4.1", "14.0.1" ] + rn-version: ['0.69.4', '0.70.1'] + xcode-version: ['12.5.1', '13.2.1', '13.4.1', '14.0.1'] exclude: - - rn-version: "0.64.4" - xcode-version: "13.4.1" - - rn-version: "0.64.4" - xcode-version: "14.0.1" - - rn-version: "0.65.2" - xcode-version: "13.4.1" - - rn-version: "0.65.2" - xcode-version: "14.0.1" - requires: - - test-javascript + - rn-version: '0.69.4' + xcode-version: '13.4.1' + - rn-version: '0.69.4' + xcode-version: '14.0.1' + - rn-version: '0.70.1' + xcode-version: '13.4.1' + - rn-version: '0.70.1' + xcode-version: '14.0.1' diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..aaee887 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,68 @@ +name: Tests +on: [push] + +jobs: + tsc: + runs-on: ubuntu-latest + name: Typescript + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: '16.x' + cache: 'yarn' + - run: yarn && yarn tsc + + js-tests: + runs-on: ubuntu-latest + name: JS tests + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: '16.x' + cache: 'yarn' + - run: yarn && yarn test + + android-tests: + runs-on: ubuntu-latest + name: Android tests + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: '16.x' + cache: 'yarn' + - run: yarn && cd android && ./gradlew test + + # ripped from these two places: + # https://vmois.dev/xcode-github-actions/ + # https://gist.github.com/ricardopereira/10198e68f27c14601d77ebc7a8352da1 + ios-tests: + runs-on: macOS-latest + strategy: + matrix: + destination: ['platform=iOS Simulator,name=iPhone 13,OS=16.2'] + name: iOS tests + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: '16.x' + cache: 'yarn' + - uses: actions/cache@v3 + id: cocoapods-cache + with: + path: ios/Pods + key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-pods- + - name: CocoaPods + if: steps.cocoapods-cache.outputs.cache-hit != 'true' + run: cd ios && yarn && pod install + - name: Select Xcode + run: sudo xcode-select -switch /Applications/Xcode_14.2.app && /usr/bin/xcodebuild -version + - name: Run tests + run: cd ios && xcodebuild -quiet -workspace LaunchdarklyReactNativeClient.xcworkspace -scheme LaunchdarklyReactNativeClient -sdk iphonesimulator -destination "${destination}" test | xcpretty && exit ${PIPESTATUS[0]} + env: + destination: ${{ matrix.destination }} diff --git a/.ldrelease/config.yml b/.ldrelease/config.yml index 8ce329b..162f094 100644 --- a/.ldrelease/config.yml +++ b/.ldrelease/config.yml @@ -4,6 +4,11 @@ repo: public: react-native-client-sdk private: react-native-client-sdk-private +branches: + - name: main + description: 7.x + - name: 6.x + publications: - url: https://www.npmjs.com/package/launchdarkly-react-native-client-sdk description: npm diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..1b763b1 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +CHANGELOG.md diff --git a/ManualTestApp/App.js b/ManualTestApp/App.tsx similarity index 63% rename from ManualTestApp/App.js rename to ManualTestApp/App.tsx index 58c7fab..8342230 100644 --- a/ManualTestApp/App.js +++ b/ManualTestApp/App.tsx @@ -1,10 +1,10 @@ -import React, { useState, useEffect } from 'react'; +import { useState, useEffect, ReactNode } from 'react'; import { SafeAreaView, ScrollView, StyleSheet, Text, View, Button, TextInput, Alert, Switch } from 'react-native'; import { Picker } from '@react-native-picker/picker'; -import LDClient from 'launchdarkly-react-native-client-sdk'; -import MessageQueue from 'react-native/Libraries/BatchedBridge/MessageQueue.js'; +import LDClient, { LDMultiKindContext } from 'launchdarkly-react-native-client-sdk'; +import MessageQueue from 'react-native/Libraries/BatchedBridge/MessageQueue'; -const Wrapper = ({ children }): Node => { +const Wrapper = ({ children }: { children: ReactNode }) => { const styles = { scroll: { backgroundColor: '#fff', padding: 10 }, area: { backgroundColor: '#fff', flex: 1 }, @@ -17,11 +17,11 @@ const Wrapper = ({ children }): Node => { }; const Body = () => { - const [client, setClient] = useState(null); + const [client, setClient] = useState(null); const [flagKey, setFlagKey] = useState('dev-test-flag'); const [flagType, setFlagType] = useState('bool'); const [isOffline, setIsOffline] = useState(false); - const [userKey, setUserKey] = useState('user key'); + const [contextKey, setContextKey] = useState('context-key'); const [listenerKey, setListenerKey] = useState(''); const [listeners, setListeners] = useState({}); @@ -36,9 +36,33 @@ const Body = () => { version: '0.0.1', }, }; - let user = { key: userKey }; + const anonymousUserContext = { + kind: 'user', + key: 'user-key-1', + anonymous: true, + }; + + // A multi-context can contain both anonymous and non-anonymous contexts. + // Here, organization is not anonymous. + const multiContext: LDMultiKindContext = { + kind: 'multi', + user: anonymousUserContext, + org: { + key: 'org-key', + name: 'Example organization name', + _meta: { + privateAttributes: ['address', 'phone'], + }, + address: { + street: 'sunset blvd', + postcode: 94105, + }, + phone: 5551234, + }, + }; + try { - await ldClient.configure(config, user); + await ldClient.configure(config, multiContext); } catch (err) { console.error(err); } @@ -46,59 +70,62 @@ const Body = () => { } if (client == null) { - initializeClient(); + initializeClient().then(() => console.log('ld client initialized successfully')); } }); const evalFlag = async () => { let res; if (flagType === 'bool') { - res = await client.boolVariation(flagKey, false); + res = await client?.boolVariation(flagKey, false); } else if (flagType === 'string') { - res = await client.stringVariation(flagKey, ''); + res = await client?.stringVariation(flagKey, ''); } else if (flagType === 'number') { - res = await client.numberVariation(flagKey, 0.0); + res = await client?.numberVariation(flagKey, 0.0); } else if (flagType === 'json') { - res = await client.jsonVariation(flagKey, null); + res = await client?.jsonVariation(flagKey, null); } Alert.alert('LD Server Response', JSON.stringify(res)); }; const track = () => { - client.track(flagKey, false); + client?.track(flagKey, false); }; const identify = () => { - client.identify({ key: userKey }); + client?.identify({ kind: 'user', key: contextKey }); }; const listen = () => { if (listeners.hasOwnProperty(listenerKey)) { return; } - let listener = (value) => Alert.alert('Listener Callback', value); - client.registerFeatureFlagListener(listenerKey, listener); + let listener = (value: string | undefined) => Alert.alert('Listener Callback', value); + client?.registerFeatureFlagListener(listenerKey, listener); setListeners({ ...listeners, ...{ [listenerKey]: listener } }); }; const removeListener = () => { - client.unregisterFeatureFlagListener(listenerKey, listeners[listenerKey]); + // @ts-ignore + client?.unregisterFeatureFlagListener(listenerKey, listeners[listenerKey]); + // @ts-ignore let { [listenerKey]: omit, ...newListeners } = listeners; setListeners(newListeners); }; const flush = () => { - client.flush(); + client?.flush(); }; - const setOffline = (newIsOffline) => { - if (newIsOffline) { - client.setOffline(); + const setOffline = (offline: boolean) => { + if (offline) { + client?.setOffline(); } else { - client.setOnline(); + client?.setOnline(); } - setIsOffline(newIsOffline); + + setIsOffline(offline); }; return ( @@ -116,8 +143,8 @@ const Body = () => { Offline - User Key: - + Context key: +