From b8177b03f787bfde052fd6122ccb78639a958e7c Mon Sep 17 00:00:00 2001 From: Brian Beckerle <49686530+brainbicycle@users.noreply.github.com> Date: Fri, 31 Jan 2025 16:22:37 -0500 Subject: [PATCH] feat(deps): migrate to more secure library for secret management (#11446) * feat: replace react-native-config with react-native-keys * build(deps): add patch for react-native-keys * docs: add hacks.md entry for rn-keys * refactor: android to use reactnativekeys * refactor: android * refactor: js related code * refactor: ios related stuff - not working * attempt to fix missing node in pre-action * remove placeholder in echo file * undo config changes interfering with cocoapods * uncomment scheme change * update scripts to download the new file * fix: tests * refactor: oss stops complaining Co-authored-by: brainbicycle * refactor: gitignore + script update * update setup script to update the new file * switch oss flag to string to fix android build Co-authored-by: George * add back env plugin to fix mapbox token in gradle file Co-authored-by: George * remove now unused .env file * attempt to handle nvm as well as asdf in preaction script * update preaction again to fix duplicate node stuff * update docs and minor cleanup Co-authored-by: George * update doctor script Co-authored-by: George * build(deps): remove most of the patch and bump react-native-keys Co-authored-by: brainbicycle * workaround for timeout in ci --------- Co-authored-by: George Kartalis --- .circleci/config.yml | 2 +- .env.example | 44 ------------- .gitignore | 4 ++ .secrets.baseline | 18 ++++- HACKS.md | 11 ++++ android/app/build.gradle | 8 +-- .../java/net/artsy/app/MainApplication.kt | 10 ++- .../app/utils/ReactNativeConfigUtils.java | 22 ------- android/build.gradle | 3 +- android/gradle.properties | 3 +- docs/adding_new_keys.md | 36 +++------- fastlane/Fastfile | 2 + ios/Artsy.xcodeproj/project.pbxproj | 19 ------ .../xcshareddata/xcschemes/Artsy.xcscheme | 20 +++++- ios/Artsy/App/ARAppDelegate+Emission.m | 2 +- ios/Artsy/App/ARAppDelegate.mm | 13 ++-- ios/Artsy/App/ARAppNotificationsDelegate.m | 10 +-- ios/Artsy/App/ArtsyEcho.m | 4 +- ios/Artsy/Networking/ARRouter.m | 6 +- ios/Podfile.lock | 37 ++++++++--- keys.example.json | 45 +++++++++++++ package.json | 2 +- patches/react-native-keys+0.7.11.patch | 12 ++++ scripts/setup/setup-env-for-artsy | 3 +- scripts/setup/setup-env-for-ci | 2 +- scripts/setup/setup-env-for-oss | 11 ++-- scripts/setup/update-env-for-artsy | 1 + scripts/utils/doctor.js | 29 ++++++++- src/app/App.tsx | 5 +- .../Components/LocationMap/LocationMap.tsx | 4 +- .../Components/Recaptcha/RecaptchaWebView.tsx | 6 +- src/app/Components/ShareSheet/ShareSheet.tsx | 4 +- .../ImageCarousel/ImageCarouselVimeoVideo.tsx | 4 +- src/app/Scenes/Map/GlobalMap.tsx | 4 +- .../Scenes/Partner/Components/PartnerMap.tsx | 4 +- src/app/store/AuthModel.ts | 10 +-- src/app/store/config/features.ts | 8 ++- src/app/system/codepush.ts | 14 ++-- .../devTools/DevMenu/Components/DevTools.tsx | 10 +-- src/app/system/errorReporting/setupSentry.ts | 7 +- .../Websockets/GravityWebsocketContext.tsx | 6 +- src/app/utils/experiments/unleashClient.ts | 13 ++-- src/app/utils/googleMaps.ts | 4 +- .../utils/track/SegmentTrackingProvider.ts | 10 +-- src/app/utils/useSiftConfig.ts | 10 ++- src/setupJest.tsx | 37 +++++++++-- typings/react-native-config.d.ts | 21 ------ yarn.lock | 65 +++++++++++++++++-- 48 files changed, 376 insertions(+), 249 deletions(-) delete mode 100644 .env.example delete mode 100644 android/app/src/main/java/net/artsy/app/utils/ReactNativeConfigUtils.java create mode 100644 keys.example.json create mode 100644 patches/react-native-keys+0.7.11.patch delete mode 100644 typings/react-native-config.d.ts diff --git a/.circleci/config.yml b/.circleci/config.yml index 0ade7498761..6ba42b1b283 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -38,7 +38,7 @@ commands: steps: - run: name: "Set up .env file" - command: touch .env.shared && touch .env && scripts/setup/setup-env-for-ci + command: touch .env.shared && touch keys.shared.json && scripts/setup/setup-env-for-ci setup-awscli: steps: - run: diff --git a/.env.example b/.env.example deleted file mode 100644 index c337aef71a9..00000000000 --- a/.env.example +++ /dev/null @@ -1,44 +0,0 @@ -# The ArtsyAPIClientKey and ArtsyAPIClientSecret are provided for open source -# contributors to learn from the app. Please don't abuse the keys or we'll -# need to rotate them and you'll make it harder for everyone to learn. -# -# As these are publicly available, they are not eligable for the Artsy Bug -# Bounty program. -ARTSY_API_CLIENT_SECRET=3a33d2085cbd1176153f99781bbce7c6 -ARTSY_API_CLIENT_KEY=e750db60ac506978fc70 -ARTSY_DEV_API_CLIENT_KEY=e750db60ac506978fc70 -ARTSY_DEV_API_CLIENT_SECRET=3a33d2085cbd1176153f99781bbce7c6 -ARTSY_FACEBOOK_APP_ID=- -ARTSY_PROD_API_CLIENT_KEY=- -ARTSY_PROD_API_CLIENT_SECRET=- -BRAZE_PRODUCTION_APP_KEY_IOS=- -BRAZE_STAGING_APP_KEY_IOS=- -CODE_PUSH_IOS_CANARY_DEPLOYMENT_KEY=- -CODE_PUSH_IOS_STAGING_DEPLOYMENT_KEY=- -CODE_PUSH_IOS_PRODUCTION_DEPLOYMENT_KEY=- -CODE_PUSH_ANDROID_CANARY_DEPLOYMENT_KEY=- -CODE_PUSH_ANDROID_STAGING_DEPLOYMENT_KEY=- -CODE_PUSH_ANDROID_PRODUCTION_DEPLOYMENT_KEY=- -GOOGLE_MAPS_API_KEY=- -GRAVITY_API_KEY=- -GRAVITY_STAGING_WEBSOCKET_URL=- -GRAVITY_WEBSOCKET_URL=- -MAPBOX_API_CLIENT_KEY=- -SEGMENT_PRODUCTION_WRITE_KEY_ANDROID=- -SEGMENT_PRODUCTION_WRITE_KEY_IOS=- -SEGMENT_STAGING_WRITE_KEY_ANDROID=- -SEGMENT_STAGING_WRITE_KEY_IOS=- -SENTRY_DSN=- -UNLEASH_PROXY_CLIENT_KEY_PRODUCTION=- -UNLEASH_PROXY_CLIENT_KEY_STAGING=- -UNLEASH_PROXY_URL_PRODUCTION=- -UNLEASH_PROXY_URL_STAGING=- -VIMEO_PUBLIC_TOKEN=- - -# For Pods -MAPBOX_DOWNLOAD_TOKEN=- - -# Open Source Contributors -OSS=- - -# Deprecated diff --git a/.gitignore b/.gitignore index 2e15a28e4bc..a03d2e73b68 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ net.artsy.artsy.dev.pem ios/Artsy/App/Echo.json ios/Artsy/App/EchoNew.json **/.xcode.env.local +ios/tmp.xcconfig # Fastlane Preview.html @@ -103,6 +104,9 @@ native-hash.txt **/.env* !.env.example +# Ignore key env files +**/keys.shared.json + # Android/IntelliJ .gradle .idea diff --git a/.secrets.baseline b/.secrets.baseline index ea6bb112ed8..91ff5c3b767 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -177,6 +177,22 @@ "line_number": 86 } ], + "keys.example.json": [ + { + "type": "Hex High Entropy String", + "filename": "keys.example.json", + "hashed_secret": "49d94093659ddfdbd054f2f7d6cf6086ec83cfd2", + "is_verified": false, + "line_number": 4 + }, + { + "type": "Secret Keyword", + "filename": "keys.example.json", + "hashed_secret": "49d94093659ddfdbd054f2f7d6cf6086ec83cfd2", + "is_verified": false, + "line_number": 4 + } + ], "scripts/utils/required_reason_api_usage.sh": [ { "type": "Base64 High Entropy String", @@ -1134,5 +1150,5 @@ } ] }, - "generated_at": "2025-01-10T17:13:48Z" + "generated_at": "2025-01-28T13:31:59Z" } diff --git a/HACKS.md b/HACKS.md index bfe0063aeef..24e57505d94 100644 --- a/HACKS.md +++ b/HACKS.md @@ -296,3 +296,14 @@ https://github.com/software-mansion/react-native-reanimated/pull/6573 #### Explanation/Context: In the HomeView Tasks, we want to update the FlatList's `CellRendererComponent` to update the `zIndex` of the rendered elements so they can be on top of each other, and to animate them we need to use Reanimated's FlatList, but it doesn't support updating the `CellRendererComponent` prop since they have their own implementation, so we added this patch to update the style of the component in Reanimated's FlatList. + +## patch-pacakge for react-native-keys + +#### When can we remove this: + +When react-native-keys fixes and releases the this issue: +https://github.com/numandev1/react-native-keys/issues/86#issuecomment-2546610160 + +#### Explanation/Context: + +Android was unable to build correctly on react-native 76 without excluding `libreactnative.so` diff --git a/android/app/build.gradle b/android/app/build.gradle index 6bab06738ac..6baddc768f2 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -4,12 +4,12 @@ apply plugin: "com.google.gms.google-services" apply plugin: "com.facebook.react" apply plugin: "org.jetbrains.kotlin.android" -project.ext.envConfigFiles = [ - debug: ".env.shared", - release: ".env.shared", +project.ext.keyFiles = [ + debug: "keys.shared.json", + release: "keys.shared.json", ] -apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle" +apply from: project(':react-native-keys').projectDir.getPath() + "/RNKeys.gradle" apply from: "../../node_modules/react-native-code-push/android/codepush.gradle" /** diff --git a/android/app/src/main/java/net/artsy/app/MainApplication.kt b/android/app/src/main/java/net/artsy/app/MainApplication.kt index b9733cd2ee4..5927bcd44f8 100644 --- a/android/app/src/main/java/net/artsy/app/MainApplication.kt +++ b/android/app/src/main/java/net/artsy/app/MainApplication.kt @@ -19,8 +19,8 @@ import com.facebook.react.soloader.OpenSourceMergedSoMapping import com.google.firebase.messaging.FirebaseMessaging import com.microsoft.codepush.react.CodePush import com.segment.analytics.Analytics -import net.artsy.app.utils.ReactNativeConfigUtils import io.sentry.react.RNSentryPackage +import com.reactnativekeysjsi.KeysModule.getSecureFor class MainApplication : Application(), ReactApplication { @@ -55,14 +55,12 @@ class MainApplication : Application(), ReactApplication { ArtsyNativeModule.didLaunch(this.getSharedPreferences("launchConfig", MODE_PRIVATE)) - var segmentWriteKey = BuildConfig.SEGMENT_PRODUCTION_WRITE_KEY_ANDROID + var segmentWriteKey: String = getSecureFor("SEGMENT_PRODUCTION_WRITE_KEY_ANDROID") if (BuildConfig.DEBUG) { - segmentWriteKey = BuildConfig.SEGMENT_STAGING_WRITE_KEY_ANDROID + segmentWriteKey = getSecureFor("SEGMENT_STAGING_WRITE_KEY_ANDROID") } - val analytics = Analytics.Builder(this, - ReactNativeConfigUtils.decode(segmentWriteKey, BuildConfig.GRAVITY_API_KEY) - ).build() + val analytics = Analytics.Builder(this, segmentWriteKey).build() Analytics.setSingletonInstance(analytics) diff --git a/android/app/src/main/java/net/artsy/app/utils/ReactNativeConfigUtils.java b/android/app/src/main/java/net/artsy/app/utils/ReactNativeConfigUtils.java deleted file mode 100644 index 451b17e88f3..00000000000 --- a/android/app/src/main/java/net/artsy/app/utils/ReactNativeConfigUtils.java +++ /dev/null @@ -1,22 +0,0 @@ -package net.artsy.app.utils; - -import android.util.Base64; - -public class ReactNativeConfigUtils { - public static String decode(String encodedString, String key) { - byte[] decodedBytes = Base64.decode(encodedString, Base64.DEFAULT); - byte[] keyBytes = key.getBytes(); - - int len = decodedBytes.length; - int keyBytesLen = keyBytes.length; - - byte[] resultBytes = new byte[len]; - - for (int i = 0; i < decodedBytes.length; i++) { - resultBytes[i] = (byte) (decodedBytes[i] ^ keyBytes[i % keyBytesLen]); - } - - return new String(resultBytes); - } - -} diff --git a/android/build.gradle b/android/build.gradle index 8a44c2d6bbf..d4f69efe81f 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -25,10 +25,9 @@ buildscript { } plugins { - id("co.uzzu.dotenv.gradle") version "4.0.0" + id("co.uzzu.dotenv.gradle") version "4.0.0" } - allprojects { repositories { maven { url "https://appboy.github.io/appboy-android-sdk/sdk" } diff --git a/android/gradle.properties b/android/gradle.properties index 1605808686e..24266a0fa96 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -45,4 +45,5 @@ newArchEnabled=false # If set to false, you will be using JSC instead. hermesEnabled=true -dotenv.filename=../.env.shared +# Specify dotenv file name for access in build.gradle +dotenv.filename=../.env.shared \ No newline at end of file diff --git a/docs/adding_new_keys.md b/docs/adding_new_keys.md index 05237582b7a..29a5bd3ffbf 100644 --- a/docs/adding_new_keys.md +++ b/docs/adding_new_keys.md @@ -1,38 +1,18 @@ # Adding a New Key -Keys are stored in `.env.shared` file, accessed through [`react-native-config`](https://github.com/luggit/react-native-config). You need to rebuild in Xcode for changes to the file to take effect. +Keys accessed within the app are stored in `keys.shared.json` file, accessed through [`react-native-keys`](https://github.com/numandev1/react-native-keys). You need to rebuild in Xcode for changes to the file to take effect. -.env.shared is for artsy people to be able to work and compile. +keys.shared.json is for artsy people to be able to work and compile. Is in .gitignore, and is downloaded by developers using the yarn setup:artsy script. It is also the main file that the app gets all the real env vars, keys etc. -.env.example is for open source people to be able to work and compile and see what env vars need/use. -It is committed in git, and we try to keep the exact layout copied over from .env.shared, but without any actual keys, we replace them with "-" or similar. +keys.example.json is for open source people to be able to work and compile and see what env vars need/use. +It is committed in git, and we try to keep the exact layout copied over from keys.shared.json, but without any actual keys, we replace them with "-" or similar. -On the React Native side: +On the React Native side follow these [docs](https://github.com/numandev1/react-native-keys?tab=readme-ov-file#javascript) -```ts -import Config from "react-native-config" +On the native iOS side follow these [docs](https://github.com/numandev1/react-native-keys?tab=readme-ov-file#ios-1) -// ... +On the native android side follow these [docs](https://github.com/numandev1/react-native-keys?tab=readme-ov-file#android-) -Config.KEY_NAME -``` - -On the native iOS side: - -```objc -#import - -// ... - -[ReactNativeConfig envFor:@"KEY_NAME"] -``` - -On the native android side: - -```java -BuildConfig.KEY_NAME; -``` - -You'll need to update the keys in [`emission.d.ts`](https://github.com/artsy/eigen/blob/869d35e0d83d4afae2cb62ebeab924f420944b0f/typings/emission.d.ts#L58-L72) and [`setupJest.ts`](https://github.com/artsy/eigen/blob/4654bacbcdc8624fb2799e9f86ad7717c5ab604b/src/setupJest.ts#L319-L331). +You'll need to update the keys in setupJest.ts, look for the mock for react-native-keys. diff --git a/fastlane/Fastfile b/fastlane/Fastfile index a2019625be2..5fe9339e0aa 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -82,6 +82,8 @@ lane :ship_beta_ios do sentry_release_name = "ios-#{latest_version}-#{bundle_version}" sh('yarn bundle:ios') + # Workaround for timeout in ci + ENV["FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT"] = "120" build_ios_app( workspace: 'ios/Artsy.xcworkspace', configuration: 'Store', diff --git a/ios/Artsy.xcodeproj/project.pbxproj b/ios/Artsy.xcodeproj/project.pbxproj index df0cc200659..5ce97ec2f6a 100644 --- a/ios/Artsy.xcodeproj/project.pbxproj +++ b/ios/Artsy.xcodeproj/project.pbxproj @@ -3336,7 +3336,6 @@ buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Artsy" */; buildPhases = ( 400905DA34751CB03A2B22D1 /* [CP] Check Pods Manifest.lock */, - 1A08BBFB291E6B7000062A70 /* Setup .env from react-native-config */, FD10A7F022414F080027D42C /* Start Packager */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, @@ -3644,24 +3643,6 @@ shellPath = /bin/sh; shellScript = "export SOURCEMAP_FILE=\"$PROJECT_DIR/../main.jsbundle.map\"\n\nset -e\n\nWITH_ENVIRONMENT=\"../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../node_modules/react-native/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n"; }; - 1A08BBFB291E6B7000062A70 /* Setup .env from react-native-config */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Setup .env from react-native-config"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "cp \"${PROJECT_DIR}/../.env.shared\" \"${PROJECT_DIR}/../.env\"\n"; - }; 21D9FBA2AAC48EBB99241E8F /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/ios/Artsy.xcodeproj/xcshareddata/xcschemes/Artsy.xcscheme b/ios/Artsy.xcodeproj/xcshareddata/xcschemes/Artsy.xcscheme index 0402d3cd522..3762a0be029 100644 --- a/ios/Artsy.xcodeproj/xcshareddata/xcschemes/Artsy.xcscheme +++ b/ios/Artsy.xcodeproj/xcshareddata/xcschemes/Artsy.xcscheme @@ -1,10 +1,28 @@ + version = "1.7"> + + + + + + + + + + +#import "Keys.h" #import "AREmission.h" #import "ARTemporaryAPIModule.h" #import "AREventsModule.h" diff --git a/ios/Artsy/App/ARAppDelegate.mm b/ios/Artsy/App/ARAppDelegate.mm index de04a862f73..0a55e17f409 100644 --- a/ios/Artsy/App/ARAppDelegate.mm +++ b/ios/Artsy/App/ARAppDelegate.mm @@ -30,7 +30,7 @@ #import "ARPHPhotoPickerModule.h" #import "ARCocoaConstantsModule.h" -#import +#import "Keys.h" #import #import #import "AREmission.h" @@ -138,7 +138,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( didFinishLaunchingWithOptions:launchOptions]; - BOOL ossUser = [[ReactNativeConfig envFor:@"OSS"] isEqualToString:@"true"]; + BOOL ossUser = [[Keys publicFor:@"OSS"] isEqualToString:@"true"]; if ([FIRApp defaultApp] == nil && !ossUser) { [FIRApp configure]; } @@ -163,9 +163,9 @@ - (UIView *)createRootViewWithBridge:(RCTBridge *)bridge - (void)setupAnalytics:(UIApplication *)application withLaunchOptions:(NSDictionary *)launchOptions { - NSString *brazeAppKey = [ReactNativeConfig envFor:@"BRAZE_STAGING_APP_KEY_IOS"]; + NSString *brazeAppKey = [Keys secureFor:@"BRAZE_STAGING_APP_KEY_IOS"]; if (![ARAppStatus isDev]) { - brazeAppKey = [ReactNativeConfig envFor:@"BRAZE_PRODUCTION_APP_KEY_IOS"]; + brazeAppKey = [Keys secureFor:@"BRAZE_STAGING_APP_KEY_IOS"]; } NSString *brazeSDKEndPoint = @"sdk.iad-06.braze.com"; @@ -178,9 +178,10 @@ - (void)setupAnalytics:(UIApplication *)application withLaunchOptions:(NSDiction BrazeInAppMessageUI *inAppMessageUI = [[BrazeInAppMessageUI alloc] init]; braze.inAppMessagePresenter = inAppMessageUI; - NSString *segmentWriteKey = [ReactNativeConfig envFor:@"SEGMENT_STAGING_WRITE_KEY_IOS"]; + NSString *segmentWriteKey = [Keys secureFor:@"SEGMENT_STAGING_WRITE_KEY_IOS"]; + if (![ARAppStatus isDev]) { - segmentWriteKey = [ReactNativeConfig envFor:@"SEGMENT_PRODUCTION_WRITE_KEY_IOS"]; + segmentWriteKey = [Keys secureFor:@"SEGMENT_PRODUCTION_WRITE_KEY_IOS"]; } SEGAnalyticsConfiguration *configuration = [SEGAnalyticsConfiguration configurationWithWriteKey:segmentWriteKey]; diff --git a/ios/Artsy/App/ARAppNotificationsDelegate.m b/ios/Artsy/App/ARAppNotificationsDelegate.m index 9fb5a76e5f1..f5a80a28956 100644 --- a/ios/Artsy/App/ARAppNotificationsDelegate.m +++ b/ios/Artsy/App/ARAppNotificationsDelegate.m @@ -206,12 +206,12 @@ - (BOOL)tokensAreTheSame:(NSString *)newToken previousToken:(NSString * _Nullabl // Handle the notification view on when the app is in the foreground -(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler{ - + if (ARAppDelegate.braze != nil) { // Forward notification payload to Braze for processing. [ARAppDelegate.braze.notifications handleForegroundNotificationWithNotification:notification]; } - + NSDictionary *userInfo = notification.request.content.userInfo; NSMutableDictionary *notificationInfo = [[NSMutableDictionary alloc] initWithDictionary:userInfo]; @@ -223,16 +223,16 @@ -(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNoti -(void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler{ BOOL processedByBraze = ARAppDelegate.braze != nil && [ARAppDelegate.braze.notifications handleUserNotificationWithResponse:response withCompletionHandler:completionHandler]; - + NSDictionary *userInfo = response.notification.request.content.userInfo; NSMutableDictionary *notificationInfo = [[NSMutableDictionary alloc] initWithDictionary:userInfo]; - + if (processedByBraze) { NSString *url = userInfo[@"ab_uri"]; [self tappedNotification:userInfo url:url]; return; } - + [self tappedNotification:notificationInfo url:userInfo[@"url"]]; completionHandler(); diff --git a/ios/Artsy/App/ArtsyEcho.m b/ios/Artsy/App/ArtsyEcho.m index c66ade773f0..26425d797bd 100644 --- a/ios/Artsy/App/ArtsyEcho.m +++ b/ios/Artsy/App/ArtsyEcho.m @@ -1,7 +1,7 @@ #import "ArtsyEcho.h" #import -#import +#import "Keys.h" #import "ARAppStatus.h" @@ -28,7 +28,7 @@ - (void)checkForUpdates:(void (^)(BOOL updatedDataOnServer))updateCheckCompleted return; } - if (![[ReactNativeConfig envFor:@"_OSS_"] isEqualToString:@"true"]) { + if (![[Keys publicFor:@"_OSS_"] isEqualToString:@"true"]) { [super checkForUpdates:updateCheckCompleted]; } } diff --git a/ios/Artsy/Networking/ARRouter.m b/ios/Artsy/Networking/ARRouter.m index 5836dfcdb3d..d250862a1ee 100644 --- a/ios/Artsy/Networking/ARRouter.m +++ b/ios/Artsy/Networking/ARRouter.m @@ -20,7 +20,7 @@ #import "UIDevice-Hardware.h" #import -#import +#import "Keys.h" #import #import #import @@ -273,8 +273,8 @@ + (void)setXappToken:(NSString *)token + (NSURLRequest *)newXAppTokenRequest { - NSString *clientId = [ARAppStatus isDev] ? [ReactNativeConfig envFor:@"ARTSY_DEV_API_CLIENT_KEY"] : [ReactNativeConfig envFor:@"ARTSY_PROD_API_CLIENT_KEY"]; - NSString *clientSecret = [ARAppStatus isDev] ? [ReactNativeConfig envFor:@"ARTSY_DEV_API_CLIENT_SECRET"] : [ReactNativeConfig envFor:@"ARTSY_PROD_API_CLIENT_SECRET"]; + NSString *clientId = [ARAppStatus isDev] ? [Keys secureFor:@"ARTSY_DEV_API_CLIENT_KEY"] : [Keys secureFor:@"ARTSY_PROD_API_CLIENT_KEY"]; + NSString *clientSecret = [ARAppStatus isDev] ? [Keys secureFor:@"ARTSY_DEV_API_CLIENT_SECRET"] : [Keys secureFor:@"ARTSY_PROD_API_CLIENT_SECRET"]; NSDictionary *params = @{ @"client_id" : clientId, @"client_secret" : clientSecret, diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 13875540662..87ce98279ff 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -248,6 +248,7 @@ PODS: - ObjectiveSugar (1.1.0) - OCMock (3.9.1) - OHHTTPStubs (3.1.2) + - OpenSSL-Universal (3.3.2000) - ORStackView (2.0.3): - FLKAutoLayout - PromisesObjC (2.4.0) @@ -1492,10 +1493,6 @@ PODS: - React-Core - react-native-blurhash (1.1.11): - React-Core - - react-native-config (1.4.11): - - react-native-config/App (= 1.4.11) - - react-native-config/App (1.4.11): - - React-Core - react-native-context-menu-view (1.10.0): - React - react-native-cookies (6.0.11): @@ -1523,6 +1520,28 @@ PODS: - React-Core - react-native-in-app-review (4.3.3): - React-Core + - react-native-keys (0.7.11): + - DoubleConversion + - glog + - hermes-engine + - OpenSSL-Universal + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - react-native-netinfo (11.3.2): - React-Core - react-native-pager-view (6.5.0): @@ -2271,7 +2290,6 @@ DEPENDENCIES: - React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`) - react-native-blob-util (from `../node_modules/react-native-blob-util`) - react-native-blurhash (from `../node_modules/react-native-blurhash`) - - react-native-config (from `../node_modules/react-native-config`) - react-native-context-menu-view (from `../node_modules/react-native-context-menu-view`) - "react-native-cookies (from `../node_modules/@react-native-cookies/cookies`)" - react-native-document-picker (from `../node_modules/react-native-document-picker`) @@ -2279,6 +2297,7 @@ DEPENDENCIES: - "react-native-geolocation (from `../node_modules/@react-native-community/geolocation`)" - react-native-get-random-values (from `../node_modules/react-native-get-random-values`) - react-native-in-app-review (from `../node_modules/react-native-in-app-review`) + - react-native-keys (from `../node_modules/react-native-keys`) - "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)" - react-native-pager-view (from `../node_modules/react-native-pager-view`) - react-native-render-html (from `../node_modules/react-native-render-html`) @@ -2409,6 +2428,7 @@ SPEC REPOS: - ObjectiveSugar - OCMock - OHHTTPStubs + - OpenSSL-Universal - ORStackView - PromisesObjC - Quick @@ -2531,8 +2551,6 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-blob-util" react-native-blurhash: :path: "../node_modules/react-native-blurhash" - react-native-config: - :path: "../node_modules/react-native-config" react-native-context-menu-view: :path: "../node_modules/react-native-context-menu-view" react-native-cookies: @@ -2547,6 +2565,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-get-random-values" react-native-in-app-review: :path: "../node_modules/react-native-in-app-review" + react-native-keys: + :path: "../node_modules/react-native-keys" react-native-netinfo: :path: "../node_modules/@react-native-community/netinfo" react-native-pager-view: @@ -2759,6 +2779,7 @@ SPEC CHECKSUMS: ObjectiveSugar: a6a25f23d657c19df0a0b972466d5b5ca9f5295c OCMock: 9491e4bec59e0b267d52a9184ff5605995e74be8 OHHTTPStubs: c93230597bc5c8b74bcfdb403cc8773515b8b08c + OpenSSL-Universal: b60a3702c9fea8b3145549d421fdb018e53ab7b4 ORStackView: b9507271cb41fb9e0b3eecc6414d831201e7cf7c PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 Pulley: edc993fb57f7eb20541c8453d0fce10559f21dac @@ -2794,7 +2815,6 @@ SPEC CHECKSUMS: React-microtasksnativemodule: 16e31e4f503a5f46adafd035096130418c3bce70 react-native-blob-util: f7234c91ad0e3faeee51b3edee80b61553f74993 react-native-blurhash: 275104c6fb85e754c448854024bf7a8d4571ba8a - react-native-config: 1baea138c9641f8460d7730568daf2ff3738c4db react-native-context-menu-view: 74fbcf8a5f842c4802d0121addda2f8ffca43604 react-native-cookies: b90327af903c8a7652100201d890c50a98697799 react-native-document-picker: 6b08d834d4e4252bb8aad13d852e27666294b5b9 @@ -2802,6 +2822,7 @@ SPEC CHECKSUMS: react-native-geolocation: def10765a5144e7a76837093d43303d70bb6addf react-native-get-random-values: d16467cf726c618e9c7a8c3c39c31faa2244bbba react-native-in-app-review: b3d1eed3d1596ebf6539804778272c4c65e4a400 + react-native-keys: ac4011e7f7b63f9c0557a0c120a9f7e78ee9ab7e react-native-netinfo: ce102083db558237dac20cf64172ef569ebe2dd9 react-native-pager-view: a58a82591f421322e2bdb4fb69b82f6486dd4b22 react-native-render-html: 5afc4751f1a98621b3009432ef84c47019dcb2bd diff --git a/keys.example.json b/keys.example.json new file mode 100644 index 00000000000..6a7445963d7 --- /dev/null +++ b/keys.example.json @@ -0,0 +1,45 @@ + +{ + "secure": { + "ARTSY_API_CLIENT_KEY": "e750db60ac506978fc70", + "ARTSY_API_CLIENT_SECRET": "3a33d2085cbd1176153f99781bbce7c6", + "ARTSY_DEV_API_CLIENT_KEY": "e750db60ac506978fc70", + "ARTSY_DEV_API_CLIENT_SECRET": "3a33d2085cbd1176153f99781bbce7c6", + "ARTSY_FACEBOOK_APP_ID": "-", + "ARTSY_PROD_API_CLIENT_KEY": "-", + "ARTSY_PROD_API_CLIENT_SECRET": "-", + "BRAZE_PRODUCTION_APP_KEY_IOS": "-", + "BRAZE_STAGING_APP_KEY_IOS": "-", + "CODE_PUSH_IOS_CANARY_DEPLOYMENT_KEY": "-", + "CODE_PUSH_IOS_STAGING_DEPLOYMENT_KEY": "-", + "CODE_PUSH_IOS_PRODUCTION_DEPLOYMENT_KEY": "-", + "CODE_PUSH_ANDROID_CANARY_DEPLOYMENT_KEY": "-", + "CODE_PUSH_ANDROID_STAGING_DEPLOYMENT_KEY": "-", + "CODE_PUSH_ANDROID_PRODUCTION_DEPLOYMENT_KEY": "-", + "GOOGLE_MAPS_API_KEY": "-", + "GRAVITY_STAGING_WEBSOCKET_URL": "-", + "GRAVITY_WEBSOCKET_URL": "-", + "MAPBOX_API_CLIENT_KEY": "-", + "RECAPTCHA_KEY_STAGING": "-", + "RECAPTCHA_KEY_PRODUCTION": "-", + "SEGMENT_PRODUCTION_WRITE_KEY_ANDROID": "-", + "SEGMENT_PRODUCTION_WRITE_KEY_IOS": "-", + "SEGMENT_STAGING_WRITE_KEY_ANDROID": "-", + "SEGMENT_STAGING_WRITE_KEY_IOS": "-", + "SENTRY_DSN": "-", + "SIFT_PRODUCTION_ACCOUNT_ID": "-", + "SIFT_PRODUCTION_BEACON_KEY": "-", + "SIFT_STAGING_ACCOUNT_ID": "-", + "SIFT_STAGING_BEACON_KEY": "-", + "SPLIT_IO_PRODUCTION_API_KEY": "-", + "SPLIT_IO_STAGING_API_KEY": "-", + "UNLEASH_PROXY_CLIENT_KEY_PRODUCTION": "-", + "UNLEASH_PROXY_CLIENT_KEY_STAGING": "-", + "UNLEASH_PROXY_URL_PRODUCTION": "-", + "UNLEASH_PROXY_URL_STAGING": "-", + "VIMEO_PUBLIC_TOKEN": "-" + }, + "public": { + "OSS": true + } +} diff --git a/package.json b/package.json index e04c61e883a..ed8216f4b3d 100644 --- a/package.json +++ b/package.json @@ -158,7 +158,6 @@ "react-native-blurhash": "1.1.11", "react-native-bootsplash": "6.1.3", "react-native-code-push": "8.3.1", - "react-native-config": "https://github.com/artsy/react-native-config.git#v1.4.12-artsy", "react-native-context-menu-view": "git+https://github.com/artsy/react-native-context-menu-view.git#v1.10.10-artsy", "react-native-device-info": "14.0.0", "react-native-document-picker": "9.3.0", @@ -171,6 +170,7 @@ "react-native-image-crop-picker": "0.41.6", "react-native-in-app-review": "4.3.3", "react-native-keychain": "9.2.2", + "react-native-keys": "0.7.11", "react-native-linear-gradient": "2.8.3", "react-native-localize": "2.1.3", "react-native-pager-view": "6.5.0", diff --git a/patches/react-native-keys+0.7.11.patch b/patches/react-native-keys+0.7.11.patch new file mode 100644 index 00000000000..e79873e22bc --- /dev/null +++ b/patches/react-native-keys+0.7.11.patch @@ -0,0 +1,12 @@ +diff --git a/node_modules/react-native-keys/android/build.gradle b/node_modules/react-native-keys/android/build.gradle +index 7d07aae..56f223a 100644 +--- a/node_modules/react-native-keys/android/build.gradle ++++ b/node_modules/react-native-keys/android/build.gradle +@@ -156,6 +156,7 @@ android { + "**/libc++_shared.so", + "**/libfbjni.so", + "**/libreactnativejni.so", ++ "**/libreactnative.so", + "**/libjsi.so", + "**/libreact_nativemodule_core.so", + "**/libturbomodulejsijni.so", diff --git a/scripts/setup/setup-env-for-artsy b/scripts/setup/setup-env-for-artsy index 1ad16504702..a506b1f1e55 100755 --- a/scripts/setup/setup-env-for-artsy +++ b/scripts/setup/setup-env-for-artsy @@ -2,7 +2,8 @@ set -euxo pipefail -aws s3 cp s3://artsy-citadel/eigen/.env.shared ./ +aws s3 cp s3://artsy-citadel/eigen/.env.shared .env.shared +aws s3 cp s3://artsy-citadel/eigen/keys.shared.json keys.shared.json cp metaflags.example.json metaflags.json diff --git a/scripts/setup/setup-env-for-ci b/scripts/setup/setup-env-for-ci index bfa5c3a7a16..426815c2050 100755 --- a/scripts/setup/setup-env-for-ci +++ b/scripts/setup/setup-env-for-ci @@ -3,7 +3,7 @@ set -euxo pipefail aws s3 cp s3://artsy-citadel/eigen/.env.shared .env.shared -aws s3 cp s3://artsy-citadel/eigen/.env.shared .env aws s3 cp s3://artsy-citadel/eigen/.env.releases .env.releases +aws s3 cp s3://artsy-citadel/eigen/keys.shared.json keys.shared.json cp metaflags.example.json metaflags.json diff --git a/scripts/setup/setup-env-for-oss b/scripts/setup/setup-env-for-oss index fe6993c2a4f..e29ff2766fb 100755 --- a/scripts/setup/setup-env-for-oss +++ b/scripts/setup/setup-env-for-oss @@ -1,16 +1,13 @@ #!/usr/bin/env bash set -euxo pipefail -if [ -f ".env.shared" ]; then - echo "You already have a .env.shared file, skipping .env.shared creation" +if [ -f "keys.shared.json" ]; then + echo "You already have a keys.shared.json file, skipping keys.shared.json creation" else - echo "Creating .env.shared file" - cp .env.example .env.shared + echo "Creating keys.shared.json file" + cp keys.example.json keys.shared.json fi -# Set the OSS flag to true -sed -i'old' 's/OSS=.*/OSS=true/g' .env.shared - cp metaflags.example.json metaflags.json # Need a phony Google plist to compile the project but it won't be used diff --git a/scripts/setup/update-env-for-artsy b/scripts/setup/update-env-for-artsy index ae4c895336a..8d8fb62e477 100755 --- a/scripts/setup/update-env-for-artsy +++ b/scripts/setup/update-env-for-artsy @@ -7,6 +7,7 @@ read -p "Are you sure you want to update the env vars in S3? " -n 1 -r if [[ $REPLY =~ ^[Yy]$ ]] then aws s3 cp .env.shared s3://artsy-citadel/eigen/ + aws s3 cp keys.shared.json s3://artsy-citadel/eigen/ RED='\033[0;31m' RST='\033[0m' diff --git a/scripts/utils/doctor.js b/scripts/utils/doctor.js index dddf36ec2a0..c38cdbe62f5 100755 --- a/scripts/utils/doctor.js +++ b/scripts/utils/doctor.js @@ -41,7 +41,7 @@ const WARN = (message) => console.log(`🟡 ${message}`) const g = (text) => chalk.bold.green(text) const r = (text) => chalk.bold.red(text) -const checkEnvVariablesAreUpToDate = () => { +const checkDotEnvVariablesAreUpToDate = () => { exec("touch .env.temp") exec("aws s3 cp s3://artsy-citadel/eigen/.env.shared .env.temp") @@ -62,6 +62,30 @@ const checkEnvVariablesAreUpToDate = () => { exec("rm .env.temp") } +const checkEnvJSONVariablesAreUpToDate = () => { + exec("touch keys.shared.json.temp") + exec("aws s3 cp s3://artsy-citadel/eigen/keys.shared.json keys.shared.json.temp") + + const updatedEnv = fs.readFileSync("./keys.shared.json.temp", "utf8").toString() + let localEnv = "" + try { + localEnv = fs.readFileSync("./keys.shared.json", "utf8").toString() + } catch (e) { + // in that case, we don't even have a `keys.shared.json`. + localEnv = "nope" + } + + if (updatedEnv !== localEnv) { + NO( + `Your ${r`keys.shared.json file`} does not match the one in AWS.`, + `Run ${g`yarn setup:artsy`}.` + ) + } else { + YES(`Your ${g`keys.shared.json file`} seems to be valid.`) + } + exec("rm keys.shared.json.temp") +} + const checkNodeExists = () => { try { exec("node --version") @@ -247,7 +271,8 @@ const checkAndroidStudioVersion = () => { } const main = async () => { - checkEnvVariablesAreUpToDate() + checkEnvJSONVariablesAreUpToDate() + checkDotEnvVariablesAreUpToDate() checkNodeExists() checkYarnExists() diff --git a/src/app/App.tsx b/src/app/App.tsx index c9ae3e9baa0..490c4990ce3 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -21,9 +21,9 @@ import { useStripeConfig } from "app/utils/useStripeConfig" import { useEffect } from "react" import { NativeModules, UIManager, View } from "react-native" import codePush from "react-native-code-push" -import Config from "react-native-config" import { Settings } from "react-native-fbsdk-next" import "react-native-get-random-values" +import Keys from "react-native-keys" import { useWebViewCookies } from "./Components/ArtsyWebView" import { Providers } from "./Providers" import { ForceUpdate } from "./Scenes/ForceUpdate/ForceUpdate" @@ -67,7 +67,8 @@ if (UIManager.setLayoutAnimationEnabledExperimental) { const Main = () => { useRageShakeDevMenu() useEffect(() => { - if (Config.OSS === "true") { + const oss = Keys.OSS + if (oss === "true") { return } GoogleSignin.configure({ diff --git a/src/app/Components/LocationMap/LocationMap.tsx b/src/app/Components/LocationMap/LocationMap.tsx index 878444e13ab..a38a3fc972e 100644 --- a/src/app/Components/LocationMap/LocationMap.tsx +++ b/src/app/Components/LocationMap/LocationMap.tsx @@ -7,11 +7,11 @@ import { LocationMap_location$data } from "__generated__/LocationMap_location.gr import { Pin } from "app/Components/Icons/Pin" import { ArtsyMapStyleURL } from "app/Scenes/Map/GlobalMap" import { Linking, TouchableOpacity } from "react-native" -import Config from "react-native-config" +import Keys from "react-native-keys" import { createFragmentContainer, graphql } from "react-relay" import styled from "styled-components/native" -MapboxGL.setAccessToken(Config.MAPBOX_API_CLIENT_KEY) +MapboxGL.setAccessToken(Keys.secureFor("MAPBOX_API_CLIENT_KEY")) const MapWrapper = styled(Flex)` border-width: 1px; diff --git a/src/app/Components/Recaptcha/RecaptchaWebView.tsx b/src/app/Components/Recaptcha/RecaptchaWebView.tsx index b6caa30c2c7..67854499ba0 100644 --- a/src/app/Components/Recaptcha/RecaptchaWebView.tsx +++ b/src/app/Components/Recaptcha/RecaptchaWebView.tsx @@ -1,6 +1,6 @@ import { useIsStaging } from "app/utils/hooks/useIsStaging" import { FC, useRef } from "react" -import Config from "react-native-config" +import Keys from "react-native-keys" import WebView, { WebViewMessageEvent } from "react-native-webview" import { ShouldStartLoadRequest } from "react-native-webview/lib/WebViewTypes" @@ -20,7 +20,9 @@ export const RecaptchaWebView: FC = ({ const ref = useRef(null) const isStaging = useIsStaging() - const recaptchaKey = !isStaging ? Config.RECAPTCHA_KEY_PRODUCTION : Config.RECAPTCHA_KEY_STAGING + const recaptchaKey = !isStaging + ? Keys.secureFor("RECAPTCHA_KEY_PRODUCTION") + : Keys.secureFor("RECAPTCHA_KEY_STAGING") const baseUrl = !isStaging ? "https://artsy.net/" : "https://staging.artsy.net/" const handleOnMessage = (e: WebViewMessageEvent) => { diff --git a/src/app/Components/ShareSheet/ShareSheet.tsx b/src/app/Components/ShareSheet/ShareSheet.tsx index 80d7af7d7b1..26b761fe3a6 100644 --- a/src/app/Components/ShareSheet/ShareSheet.tsx +++ b/src/app/Components/ShareSheet/ShareSheet.tsx @@ -27,7 +27,7 @@ import { SNAP_POINTS } from "app/Scenes/MyCollection/Components/MyCollectionBott import { GlobalStore } from "app/store/GlobalStore" import { useCanOpenURL } from "app/utils/useCanOpenURL" import { useRef } from "react" -import Config from "react-native-config" +import Keys from "react-native-keys" import Share, { Social } from "react-native-share" import ViewShot from "react-native-view-shot" import { useTracking } from "react-tracking" @@ -86,7 +86,7 @@ export const ShareSheet = () => { try { await Share.shareSingle({ - appId: Config.ARTSY_FACEBOOK_APP_ID, + appId: Keys.secureFor("ARTSY_FACEBOOK_APP_ID"), social: Social.InstagramStories, backgroundImage: `data:image/png;base64,${processedBase64Image}`, }) diff --git a/src/app/Scenes/Artwork/Components/ImageCarousel/ImageCarouselVimeoVideo.tsx b/src/app/Scenes/Artwork/Components/ImageCarousel/ImageCarouselVimeoVideo.tsx index d7dfa18fe47..69d97903bfb 100644 --- a/src/app/Scenes/Artwork/Components/ImageCarousel/ImageCarouselVimeoVideo.tsx +++ b/src/app/Scenes/Artwork/Components/ImageCarousel/ImageCarouselVimeoVideo.tsx @@ -2,7 +2,7 @@ import { Flex, Touchable } from "@artsy/palette-mobile" import { parse } from "query-string" import { useEffect, useState } from "react" import { DimensionValue, Image } from "react-native" -import { Config } from "react-native-config" +import Keys from "react-native-keys" import { Vimeo } from "react-native-vimeo-iframe" interface ImageCarouselVimeoVideoProps { @@ -77,7 +77,7 @@ const useVimeoVideoMetadata = (videoId: string) => { const response = await fetch(`https://api.vimeo.com/videos/${videoId}`, { headers: { Accept: "application/vnd.vimeo.*+json;version=3.4", - Authorization: `Bearer ${Config.VIMEO_PUBLIC_TOKEN}`, + Authorization: `Bearer ${Keys.secureFor("VIMEO_PUBLIC_TOKEN")}`, }, }) const data = await response.json() diff --git a/src/app/Scenes/Map/GlobalMap.tsx b/src/app/Scenes/Map/GlobalMap.tsx index c6fb224e574..c454a1d0d5c 100644 --- a/src/app/Scenes/Map/GlobalMap.tsx +++ b/src/app/Scenes/Map/GlobalMap.tsx @@ -18,7 +18,7 @@ import { Schema, screenTrack, track } from "app/utils/track" import { get, isEqual, uniq } from "lodash" import React from "react" import { Animated, Dimensions, Image } from "react-native" -import Config from "react-native-config" +import Keys from "react-native-keys" import { createFragmentContainer, graphql, RelayProp } from "react-relay" import styled from "styled-components/native" import Supercluster, { AnyProps, ClusterProperties, PointFeature } from "supercluster" @@ -35,7 +35,7 @@ import { } from "./bucketCityResults" import { Fair, FilterData, RelayErrorState, Show } from "./types" -MapboxGL.setAccessToken(Config.MAPBOX_API_CLIENT_KEY) +MapboxGL.setAccessToken(Keys.secureFor("MAPBOX_API_CLIENT_KEY")) const ShowCardContainer = styled(Box)` position: absolute; diff --git a/src/app/Scenes/Partner/Components/PartnerMap.tsx b/src/app/Scenes/Partner/Components/PartnerMap.tsx index 7fd15b8f14e..e881a96b744 100644 --- a/src/app/Scenes/Partner/Components/PartnerMap.tsx +++ b/src/app/Scenes/Partner/Components/PartnerMap.tsx @@ -7,11 +7,11 @@ import { Pin } from "app/Components/Icons/Pin" import { cityAndPostalCode, tappedOnMap } from "app/Components/LocationMap/LocationMap" import { ArtsyMapStyleURL } from "app/Scenes/Map/GlobalMap" import { TouchableOpacity } from "react-native" -import Config from "react-native-config" +import Keys from "react-native-keys" import { createFragmentContainer, graphql } from "react-relay" import styled from "styled-components/native" -MapboxGL.setAccessToken(Config.MAPBOX_API_CLIENT_KEY) +MapboxGL.setAccessToken(Keys.secureFor("MAPBOX_API_CLIENT_KEY")) const PartnerMap: React.FC<{ location: PartnerMap_location$data diff --git a/src/app/store/AuthModel.ts b/src/app/store/AuthModel.ts index caf5ac25990..49f38855690 100644 --- a/src/app/store/AuthModel.ts +++ b/src/app/store/AuthModel.ts @@ -24,9 +24,9 @@ import { postEventToProviders } from "app/utils/track/providers" import { Action, Computed, StateMapper, Thunk, action, computed, thunk } from "easy-peasy" import { stringify } from "qs" import { Platform } from "react-native" -import Config from "react-native-config" import { LoginManager, LoginTracking } from "react-native-fbsdk-next" import Keychain from "react-native-keychain" +import Keys from "react-native-keys" import SiftReactNative from "sift-react-native" import { AuthError } from "./AuthError" import { GlobalStore, getCurrentEmissionState } from "./GlobalStore" @@ -193,10 +193,12 @@ export interface AuthModel { > } -const clientKey = __DEV__ ? Config.ARTSY_DEV_API_CLIENT_KEY : Config.ARTSY_PROD_API_CLIENT_KEY +const clientKey = __DEV__ + ? Keys.secureFor("ARTSY_DEV_API_CLIENT_KEY") + : Keys.secureFor("ARTSY_PROD_API_CLIENT_KEY") const clientSecret = __DEV__ - ? Config.ARTSY_DEV_API_CLIENT_SECRET - : Config.ARTSY_PROD_API_CLIENT_SECRET + ? Keys.secureFor("ARTSY_DEV_API_CLIENT_SECRET") + : Keys.secureFor("ARTSY_PROD_API_CLIENT_SECRET") export const getAuthModel = (): AuthModel => ({ sessionState: { diff --git a/src/app/store/config/features.ts b/src/app/store/config/features.ts index c11de001cd9..9a62ad92d5b 100644 --- a/src/app/store/config/features.ts +++ b/src/app/store/config/features.ts @@ -2,7 +2,7 @@ import { useToast } from "app/Components/Toast/toastHook" import { GlobalStore, unsafe__getEnvironment } from "app/store/GlobalStore" import { setupSentry } from "app/system/errorReporting/setupSentry" import { echoLaunchJson } from "app/utils/jsonFiles" -import Config from "react-native-config" +import Keys from "react-native-keys" interface FeatureDescriptorCommonTypes { /** Provide a short description for the Dev Menu. */ @@ -328,9 +328,11 @@ export const devToggles: { [key: string]: DevToggleDescriptor } = { DTDebugSentry: { description: "Enable sentry debug mode and send exceptions to sentry", onChange: (value, { toast }) => { - if (!Config.SENTRY_DSN) { + if (!Keys.secureFor("SENTRY_DSN")) { toast.show( - `No Sentry DSN available ${__DEV__ ? "Set it in .env.shared and re-build the app." : ""}`, + `No Sentry DSN available ${ + __DEV__ ? "Set it in keys.shared.json and re-build the app." : "" + }`, "middle" ) return diff --git a/src/app/system/codepush.ts b/src/app/system/codepush.ts index 270f8e0e2d1..39681ab7509 100644 --- a/src/app/system/codepush.ts +++ b/src/app/system/codepush.ts @@ -1,20 +1,20 @@ import { ArtsyNativeModule } from "app/NativeModules/ArtsyNativeModule" import { Platform } from "react-native" import codePush from "react-native-code-push" -import Config from "react-native-config" +import Keys from "react-native-keys" const codePushCanaryKey = Platform.OS == "ios" - ? Config.CODE_PUSH_IOS_CANARY_DEPLOYMENT_KEY - : Config.CODE_PUSH_ANDROID_CANARY_DEPLOYMENT_KEY + ? Keys.secureFor("CODE_PUSH_IOS_CANARY_DEPLOYMENT_KEY") + : Keys.secureFor("CODE_PUSH_ANDROID_CANARY_DEPLOYMENT_KEY") const codePushStagingKey = Platform.OS == "ios" - ? Config.CODE_PUSH_IOS_STAGING_DEPLOYMENT_KEY - : Config.CODE_PUSH_ANDROID_STAGING_DEPLOYMENT_KEY + ? Keys.secureFor("CODE_PUSH_IOS_STAGING_DEPLOYMENT_KEY") + : Keys.secureFor("CODE_PUSH_ANDROID_STAGING_DEPLOYMENT_KEY") const codePushProdKey = Platform.OS == "ios" - ? Config.CODE_PUSH_IOS_PRODUCTION_DEPLOYMENT_KEY - : Config.CODE_PUSH_ANDROID_PRODUCTION_DEPLOYMENT_KEY + ? Keys.secureFor("CODE_PUSH_IOS_PRODUCTION_DEPLOYMENT_KEY") + : Keys.secureFor("CODE_PUSH_ANDROID_PRODUCTION_DEPLOYMENT_KEY") export const stagingKey = codePushStagingKey ?? "Staging_Key" export const productionKey = codePushProdKey ?? "Production_Key" diff --git a/src/app/system/devTools/DevMenu/Components/DevTools.tsx b/src/app/system/devTools/DevMenu/Components/DevTools.tsx index 8ff0dca109a..948a1abce47 100644 --- a/src/app/system/devTools/DevMenu/Components/DevTools.tsx +++ b/src/app/system/devTools/DevMenu/Components/DevTools.tsx @@ -19,10 +19,10 @@ import { requestSystemPermissions } from "app/utils/requestPushNotificationsPerm import { capitalize, sortBy } from "lodash" import { useState } from "react" import { Alert, Button, Platform } from "react-native" -import Config from "react-native-config" import DeviceInfo from "react-native-device-info" import FastImage from "react-native-fast-image" import Keychain from "react-native-keychain" +import Keys from "react-native-keys" const configurableDevToggleKeys = sortBy( Object.entries(devToggles), @@ -129,10 +129,10 @@ export const DevTools: React.FC<{}> = () => { { - if (!Config.SENTRY_DSN) { + if (!Keys.secureFor("SENTRY_DSN")) { Alert.alert( "No Sentry DSN available", - __DEV__ ? "Set it in .env.shared and re-build the app." : undefined + __DEV__ ? "Set it in keys.shared.json and re-build the app." : undefined ) return } @@ -142,10 +142,10 @@ export const DevTools: React.FC<{}> = () => { { - if (!Config.SENTRY_DSN) { + if (!Keys.secureFor("SENTRY_DSN")) { Alert.alert( "No Sentry DSN available", - __DEV__ ? "Set it in .env.shared and re-build the app." : undefined + __DEV__ ? "Set it in keys.shared.json and re-build the app." : undefined ) return } diff --git a/src/app/system/errorReporting/setupSentry.ts b/src/app/system/errorReporting/setupSentry.ts index 7c123f1cd26..a2851cdb4d6 100644 --- a/src/app/system/errorReporting/setupSentry.ts +++ b/src/app/system/errorReporting/setupSentry.ts @@ -1,8 +1,8 @@ import * as Sentry from "@sentry/react-native" import { appJson } from "app/utils/jsonFiles" import { Platform } from "react-native" -import Config from "react-native-config" import DeviceInfo from "react-native-device-info" +import Keys from "react-native-keys" export const routingInstrumentation = Sentry.reactNavigationIntegration({ enableTimeToInitialDisplay: true, @@ -36,8 +36,9 @@ interface SetupSentryProps extends Partial { } export function setupSentry(props: SetupSentryProps = { debug: false }) { - const sentryDSN = Config.SENTRY_DSN - const ossUser = Config.OSS === "true" + const sentryDSN = Keys.secureFor("SENTRY_DSN") + const oss = Keys.OSS + const ossUser = oss === "true" // In DEV, enabling this will clober stack traces in errors and logs, obscuring // the source of the error. So we disable it in dev mode. diff --git a/src/app/utils/Websockets/GravityWebsocketContext.tsx b/src/app/utils/Websockets/GravityWebsocketContext.tsx index c58c16b4606..a94f9c0399c 100644 --- a/src/app/utils/Websockets/GravityWebsocketContext.tsx +++ b/src/app/utils/Websockets/GravityWebsocketContext.tsx @@ -2,7 +2,7 @@ import { ActionCable, Cable } from "@kesha-antonov/react-native-action-cable" import { useIsStaging } from "app/utils/hooks/useIsStaging" import React, { createContext, useContext, useEffect, useState } from "react" -import Config from "react-native-config" +import Keys from "react-native-keys" interface GravityWebsocketContextValueType { cable: any | null @@ -21,7 +21,9 @@ export const GravityWebsocketContextProvider: React.FC = ({ children }) => { const [channelsHolder, setChannelsHolder] = useState(null) const isStaging = useIsStaging() - const wssUrl = isStaging ? Config.GRAVITY_STAGING_WEBSOCKET_URL : Config.GRAVITY_WEBSOCKET_URL + const wssUrl = isStaging + ? Keys.secureFor("GRAVITY_STAGING_WEBSOCKET_URL") + : Keys.secureFor("GRAVITY_WEBSOCKET_URL") useEffect(() => { if (!actionCable) { diff --git a/src/app/utils/experiments/unleashClient.ts b/src/app/utils/experiments/unleashClient.ts index 43c1f8e5bf1..5f22d0150ff 100644 --- a/src/app/utils/experiments/unleashClient.ts +++ b/src/app/utils/experiments/unleashClient.ts @@ -5,7 +5,7 @@ import { experiments, } from "app/utils/experiments/experiments" import { nullToUndef } from "app/utils/nullAndUndef" -import { Config } from "react-native-config" +import Keys from "react-native-keys" import { UnleashClient } from "unleash-proxy-client" // We want to return a phony unleash client for oss builds @@ -29,7 +29,8 @@ export function getUnleashClient(props?: { env?: "production" | "staging" userId?: string | null }): PublicUnleashClient { - if (Config.OSS === "true") { + const oss = Keys.OSS + if (oss === "true") { return fakeUnleashClient } @@ -52,12 +53,12 @@ const createUnleashClient = (userId: string | undefined) => { return new UnleashClient({ url: envBeingUsed === "production" - ? Config.UNLEASH_PROXY_URL_PRODUCTION - : Config.UNLEASH_PROXY_URL_STAGING, + ? Keys.secureFor("UNLEASH_PROXY_URL_PRODUCTION") + : Keys.secureFor("UNLEASH_PROXY_URL_STAGING"), clientKey: envBeingUsed === "production" - ? Config.UNLEASH_PROXY_CLIENT_KEY_PRODUCTION - : Config.UNLEASH_PROXY_CLIENT_KEY_STAGING, + ? Keys.secureFor("UNLEASH_PROXY_CLIENT_KEY_PRODUCTION") + : Keys.secureFor("UNLEASH_PROXY_CLIENT_KEY_STAGING"), appName: "eigen", context: { userId, diff --git a/src/app/utils/googleMaps.ts b/src/app/utils/googleMaps.ts index 1d1e1aff714..5446f324366 100644 --- a/src/app/utils/googleMaps.ts +++ b/src/app/utils/googleMaps.ts @@ -1,8 +1,8 @@ import { captureMessage } from "@sentry/react-native" import { stringify } from "query-string" -import Config from "react-native-config" +import Keys from "react-native-keys" -const API_KEY = Config.GOOGLE_MAPS_API_KEY +const API_KEY = Keys.secureFor("GOOGLE_MAPS_API_KEY") export interface SimpleLocation { id: string diff --git a/src/app/utils/track/SegmentTrackingProvider.ts b/src/app/utils/track/SegmentTrackingProvider.ts index 152b6f517e6..9988b054ba3 100644 --- a/src/app/utils/track/SegmentTrackingProvider.ts +++ b/src/app/utils/track/SegmentTrackingProvider.ts @@ -3,7 +3,7 @@ import { BrazePlugin } from "@segment/analytics-react-native-plugin-braze" import { addBreadcrumb } from "@sentry/react-native" import { visualize } from "app/utils/visualizer" import { Platform } from "react-native" -import Config from "react-native-config" +import Keys from "react-native-keys" import { isCohesionScreen, TrackingProvider } from "./providers" export const SEGMENT_TRACKING_PROVIDER = "SEGMENT_TRACKING_PROVIDER" @@ -16,11 +16,11 @@ export const SegmentTrackingProvider: TrackingProvider = { // prettier-ignore const writeKey = Platform.select({ ios: __DEV__ - ? Config.SEGMENT_STAGING_WRITE_KEY_IOS - : Config.SEGMENT_PRODUCTION_WRITE_KEY_IOS, + ? Keys.secureFor("SEGMENT_STAGING_WRITE_KEY_IOS") + : Keys.secureFor("SEGMENT_PRODUCTION_WRITE_KEY_IOS"), android: __DEV__ - ? Config.SEGMENT_STAGING_WRITE_KEY_ANDROID - : Config.SEGMENT_PRODUCTION_WRITE_KEY_ANDROID, + ? Keys.secureFor("SEGMENT_STAGING_WRITE_KEY_ANDROID") + : Keys.secureFor("SEGMENT_PRODUCTION_WRITE_KEY_ANDROID"), default: "", }) diff --git a/src/app/utils/useSiftConfig.ts b/src/app/utils/useSiftConfig.ts index 7c3e79a064e..057ce032ec3 100644 --- a/src/app/utils/useSiftConfig.ts +++ b/src/app/utils/useSiftConfig.ts @@ -1,15 +1,19 @@ import { useIsStaging } from "app/utils/hooks/useIsStaging" import { useEffect } from "react" -import Config from "react-native-config" +import Keys from "react-native-keys" import SiftReactNative from "sift-react-native" export function useSiftConfig() { const isStaging = useIsStaging() const accountId = - __DEV__ || isStaging ? Config.SIFT_STAGING_ACCOUNT_ID : Config.SIFT_PRODUCTION_ACCOUNT_ID + __DEV__ || isStaging + ? Keys.secureFor("SIFT_STAGING_ACCOUNT_ID") + : Keys.secureFor("SIFT_PRODUCTION_ACCOUNT_ID") const beaconKey = - __DEV__ || isStaging ? Config.SIFT_STAGING_BEACON_KEY : Config.SIFT_PRODUCTION_BEACON_KEY + __DEV__ || isStaging + ? Keys.secureFor("SIFT_STAGING_BEACON_KEY") + : Keys.secureFor("SIFT_PRODUCTION_BEACON_KEY") useEffect(() => { if (!!accountId && !!beaconKey) { diff --git a/src/setupJest.tsx b/src/setupJest.tsx index c4651d94df1..63f59d630db 100644 --- a/src/setupJest.tsx +++ b/src/setupJest.tsx @@ -283,8 +283,30 @@ jest.mock("react-native-image-crop-picker", () => ({ clean: jest.fn(), })) -jest.mock("react-native-config", () => { - const mockConfig = { +jest.mock("react-native-keys", () => { + interface MockConfig { + secureFor: (key: keyof typeof secrets) => string + secrets: { + ARTSY_DEV_API_CLIENT_SECRET: string + ARTSY_DEV_API_CLIENT_KEY: string + ARTSY_PROD_API_CLIENT_SECRET: string + ARTSY_PROD_API_CLIENT_KEY: string + ARTSY_FACEBOOK_APP_ID: string + SEGMENT_PRODUCTION_WRITE_KEY_IOS: string + SEGMENT_PRODUCTION_WRITE_KEY_ANDROID: string + SEGMENT_STAGING_WRITE_KEY_IOS: string + SEGMENT_STAGING_WRITE_KEY_ANDROID: string + SENTRY_DSN: string + GOOGLE_MAPS_API_KEY: string + MAPBOX_API_CLIENT_KEY: string + UNLEASH_PROXY_CLIENT_KEY_PRODUCTION: string + UNLEASH_PROXY_CLIENT_KEY_STAGING: string + UNLEASH_PROXY_URL_PRODUCTION: string + UNLEASH_PROXY_URL_STAGING: string + } + } + + const secrets = { ARTSY_DEV_API_CLIENT_SECRET: "artsy_api_client_secret", // pragma: allowlist secret ARTSY_DEV_API_CLIENT_KEY: "artsy_api_client_key", // pragma: allowlist secret ARTSY_PROD_API_CLIENT_SECRET: "artsy_api_client_secret", // pragma: allowlist secret @@ -302,8 +324,15 @@ jest.mock("react-native-config", () => { UNLEASH_PROXY_URL_PRODUCTION: "https://unleash_proxy_url_production", // pragma: allowlist secret UNLEASH_PROXY_URL_STAGING: "https://unleash_proxy_url_staging", // pragma: allowlist secret } - // support both default and named export - return { ...mockConfig, Config: mockConfig } + + const mockConfig: MockConfig = { + secureFor: (key) => { + return secrets[key] + }, + secrets, + } + + return mockConfig }) jest.mock("react-native-view-shot", () => ({})) diff --git a/typings/react-native-config.d.ts b/typings/react-native-config.d.ts deleted file mode 100644 index e9e912ee7e2..00000000000 --- a/typings/react-native-config.d.ts +++ /dev/null @@ -1,21 +0,0 @@ -import "react-native-config" - -declare module "react-native-config" { - interface NativeConfig { - ARTSY_API_CLIENT_KEY: string - ARTSY_API_CLIENT_SECRET: string - ARTSY_FACEBOOK_APP_ID: string - GOOGLE_MAPS_API_KEY: string - MAPBOX_API_CLIENT_KEY: string - SEGMENT_PRODUCTION_WRITE_KEY_ANDROID: string - SEGMENT_PRODUCTION_WRITE_KEY_IOS: string - SEGMENT_STAGING_WRITE_KEY_ANDROID: string - SEGMENT_STAGING_WRITE_KEY_IOS: string - SENTRY_DSN: string - UNLEASH_PROXY_CLIENT_KEY_PRODUCTION: string - UNLEASH_PROXY_CLIENT_KEY_STAGING: string - UNLEASH_PROXY_URL_PRODUCTION: string - UNLEASH_PROXY_URL_STAGING: string - VIMEO_PUBLIC_TOKEN: string - } -} diff --git a/yarn.lock b/yarn.lock index 41b55461d1a..5adb004decf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5126,6 +5126,11 @@ resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-2.0.9.tgz#782a0edfa6d699191292c13168bd496cd66b87c6" integrity sha512-ZuzIc7aN+i2ZDMWIiSmMdubR9EMMSTdEzF6R+FckP4p6xdnOYKqknTo/k+xXQvciSXlNGIwA4OPU5X7JIFzYdA== +"@types/minimatch@^3.0.3", "@types/minimatch@^3.0.4": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" + integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== + "@types/moment-timezone@0.5.30": version "0.5.30" resolved "https://registry.yarnpkg.com/@types/moment-timezone/-/moment-timezone-0.5.30.tgz#340ed45fe3e715f4a011f5cfceb7cb52aad46fc7" @@ -7080,6 +7085,11 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" +crypto-js@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631" + integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q== + css-color-keywords@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05" @@ -7800,6 +7810,11 @@ enhanced-resolve@^5.10.0: graceful-fs "^4.2.4" tapable "^2.2.0" +ensure-posix-path@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ensure-posix-path/-/ensure-posix-path-1.1.1.tgz#3c62bdb19fa4681544289edb2b382adc029179ce" + integrity sha512-VWU0/zXzVbeJNXvME/5EmLuEj2TauvoaTz6aFYK1Z92JCBlDlZ3Gu0tuGR42kpW1754ywTs+QB0g5TP0oj9Zaw== + entities@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4" @@ -8904,6 +8919,15 @@ fs-exists-sync@^0.1.0: resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add" integrity sha512-cR/vflFyPZtrN6b38ZyWxpWdhlXrzZEBawlpBQMq7033xVY7/kg0GDMBK5jg8lDYQckdJ5x/YC88lM3C7VMsLg== +fs-extra@^11.1.1: + version "11.3.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.0.tgz#0daced136bbaf65a555a326719af931adc7a314d" + integrity sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs-extra@^11.2.0: version "11.2.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.2.0.tgz#e70e17dfad64232287d01929399e0ea7c86b0e5b" @@ -11469,6 +11493,14 @@ marky@^1.2.2: resolved "https://registry.yarnpkg.com/marky/-/marky-1.2.5.tgz#55796b688cbd72390d2d399eaaf1832c9413e3c0" integrity sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q== +matcher-collection@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/matcher-collection/-/matcher-collection-2.0.1.tgz#90be1a4cf58d6f2949864f65bb3b0f3e41303b29" + integrity sha512-daE62nS2ZQsDg9raM0IlZzLmI2u+7ZapXBwdoeBUKAYERPDDIc0qNqA8E0Rp2D+gspKR7BgIFP52GeujaGXWeQ== + dependencies: + "@types/minimatch" "^3.0.3" + minimatch "^3.0.2" + mdn-data@2.0.14: version "2.0.14" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" @@ -13164,10 +13196,6 @@ react-native-collapsible-tab-view@^8.0.0: dependencies: use-deep-compare "^1.1.0" -"react-native-config@https://github.com/artsy/react-native-config.git#v1.4.12-artsy": - version "1.4.11" - resolved "https://github.com/artsy/react-native-config.git#278cbdc5f71d792f30c7474b0a0a5caab5c95551" - "react-native-context-menu-view@git+https://github.com/artsy/react-native-context-menu-view.git#v1.10.10-artsy": version "1.10.0" resolved "git+https://github.com/artsy/react-native-context-menu-view.git#e5a6bf3abba774936cac9ae2988704f0d7443c14" @@ -13244,6 +13272,17 @@ react-native-keychain@9.2.2: resolved "https://registry.yarnpkg.com/react-native-keychain/-/react-native-keychain-9.2.2.tgz#45449fadfbe96b35a2e1470f8a9c40a0c22edbe5" integrity sha512-sATwVC76fEl4fsdBsgzG6lwhUhKduK0yqPBKHQRrJlv/2/HFBLXc4yLk9SKnZdgVHCpBxIEX4obMyMcu3HBh9w== +react-native-keys@0.7.11: + version "0.7.11" + resolved "https://registry.yarnpkg.com/react-native-keys/-/react-native-keys-0.7.11.tgz#264b1929a614dd0f9e64537261dbe7fc2d8a3d68" + integrity sha512-QnB7O3IswIMgosFWbZeD7ZAQFNLYNyaQRC/P9hJ3HZC3VUSPBFEjYzfs9nVixYCTqhgz+GmTLZvSrIPhpO/Reg== + dependencies: + crypto-js "^4.2.0" + fs-extra "^11.1.1" + normalize-path "^3.0.0" + walk-sync "^3.0.0" + xml2js "^0.6.2" + react-native-linear-gradient@2.8.3: version "2.8.3" resolved "https://registry.yarnpkg.com/react-native-linear-gradient/-/react-native-linear-gradient-2.8.3.tgz#9a116649f86d74747304ee13db325e20b21e564f" @@ -16075,6 +16114,16 @@ w3c-xmlserializer@^4.0.0: dependencies: xml-name-validator "^4.0.0" +walk-sync@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/walk-sync/-/walk-sync-3.0.0.tgz#67f882925021e20569a1edd560b8da31da8d171c" + integrity sha512-41TvKmDGVpm2iuH7o+DAOt06yyu/cSHpX3uzAwetzASvlNtVddgIjXIb2DfB/Wa20B1Jo86+1Dv1CraSU7hWdw== + dependencies: + "@types/minimatch" "^3.0.4" + ensure-posix-path "^1.1.0" + matcher-collection "^2.0.1" + minimatch "^3.0.4" + walker@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" @@ -16384,6 +16433,14 @@ xml2js@0.6.0: sax ">=0.6.0" xmlbuilder "~11.0.0" +xml2js@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.6.2.tgz#dd0b630083aa09c161e25a4d0901e2b2a929b499" + integrity sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA== + dependencies: + sax ">=0.6.0" + xmlbuilder "~11.0.0" + xml@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5"