Skip to content
This repository has been archived by the owner on Jun 16, 2022. It is now read-only.

LIVE-545 Add USB Firmware updates on Android #2432

Open
wants to merge 76 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
1e70f3e
LIVE-545 Merge first poc on fw update to current develop
grsoares21 Mar 28, 2022
71f73ea
LIVE-545 Fix syntax error on appstate
grsoares21 Mar 28, 2022
8cb120f
LIVE-545 Integrate current firmware update modal into V3 manager
grsoares21 Mar 29, 2022
06e55b3
LIVE-545 Migrate current version of the FW update to TypeScript
grsoares21 Mar 30, 2022
05bfaef
LIVE-545 Redesign update banner
grsoares21 Mar 31, 2022
29bf40e
LIVE-545 Add experimental + feature flag for usb firmware update
grsoares21 Mar 31, 2022
77b2908
LIVE-545 Use FF and experimental setting to decide which FW update mo…
grsoares21 Mar 31, 2022
ecb69e6
LIVE-545 Started refactoring of the state management for FW update
grsoares21 Apr 1, 2022
6118d51
LIVE-545 Some progress and investigation on FW update state management
grsoares21 Apr 3, 2022
0b09b6d
LIVE-545 Finish up state management and start redesign of the update …
grsoares21 Apr 4, 2022
fd20c31
LIVE-545 remove useless console.log
grsoares21 Apr 4, 2022
b9b7ebf
LIVE-545 Implement update step screens + extra mcu flash step
grsoares21 Apr 6, 2022
50e6178
LIVE-545 Fix minor bugs on modal closing behaviour
grsoares21 Apr 7, 2022
d72f499
LIVE-545 Started implementantion of the reinstall apps button
grsoares21 Apr 7, 2022
2bc7f03
LIVE-545 Fix edge cases for re-install apps button
grsoares21 Apr 11, 2022
9088692
LIVE-545 Use correct markdown component and text for firmware update
grsoares21 Apr 12, 2022
15556c2
LIVE-545 Split firmware update steps into multiple components
grsoares21 Apr 12, 2022
903e209
LIVE-545 Add tracking events for firmware update steps
grsoares21 Apr 13, 2022
63b5784
LIVE-545 Add FW version and wired connection guard-checks
grsoares21 Apr 13, 2022
4747920
LIVE-545 Add unwired connection error and firmware identifier
grsoares21 Apr 13, 2022
d75a9ee
LIVE-545 Add LLC with feature flag configuration to the dependencies
grsoares21 Apr 14, 2022
59606e5
LIVE-545 Remove the progress notification when update is over
grsoares21 Apr 14, 2022
57c2adf
LIVE-545 Remove libcore to fix build
grsoares21 Apr 14, 2022
bd1751f
LIVE-545 Redesign Bluetooth error + standardize FW update track events
grsoares21 Apr 15, 2022
31d97dc
LIVE-545 Fix small typescript error
grsoares21 Apr 15, 2022
2048939
LIVE-545 Remove some useless left-over changes
grsoares21 Apr 15, 2022
efaec29
LIVE-545 Remove useless localization change
grsoares21 Apr 15, 2022
79aafa9
LIVE-545 Move forgotten change from JS to TS
grsoares21 Apr 15, 2022
5374f01
LIVE-545 Add device check and fix small bug
grsoares21 Apr 15, 2022
6f8f2e5
LIVE-545 Run prettier
grsoares21 Apr 15, 2022
cff96e7
LIVE-545 Update yarn.lock after deleting usestatemachine
grsoares21 Apr 15, 2022
4d0069f
LIVE-545 Small improvements on BackgroundRunner.kt file
grsoares21 Apr 15, 2022
47fac14
LIVE-545 Remove network security config changes
grsoares21 Apr 15, 2022
d6ef20a
LIVE-545 Use Track component for tracking FW update events
grsoares21 Apr 15, 2022
7405d96
LIVE-545 Implement internationalisation of FW update notifications
grsoares21 Apr 19, 2022
462ab14
LIVE-545 Remove close button from modal when close is blocked
grsoares21 Apr 20, 2022
d810e8e
LIVE-545 Fix reinstall button with device authorization
grsoares21 Apr 20, 2022
6ed35df
LIVE-545 Use the same fw update banner for both the portfolio and man…
grsoares21 Apr 26, 2022
7f1892a
LIVE-545 Fixed firmware version name value
grsoares21 Apr 26, 2022
1fbdd17
LIVE-545 Fix ghost update screen in up to date devices
grsoares21 Apr 26, 2022
f45b168
LIVE-545 Add possibility of closing the update during the confirm pin…
grsoares21 Apr 26, 2022
c365b58
LIVE-545 Fixed version checking for allowing fw update
grsoares21 Apr 27, 2022
a5879eb
LIVE-545 Use ranges for version check and add version check to manager
grsoares21 Apr 27, 2022
215350a
LIVE-545 Remove experimental feature for fw update
grsoares21 Apr 27, 2022
1181f34
Merge branch 'develop' into feat/LIVE-545-llm-fw-update
grsoares21 Apr 27, 2022
0df24bd
LIVE-545 Update live common reference
grsoares21 Apr 27, 2022
29c1db0
LIVE-545 Remove intermediate screen between mcu and bootloader flashes
grsoares21 Apr 27, 2022
90a22a8
LIVE-545 Design improvements to the changelog screen
grsoares21 Apr 27, 2022
4672ed3
LIVE-545 Use alert component for firmware update banner
grsoares21 Apr 27, 2022
5eb6d38
LIVE-545 Fixed untranslated text
grsoares21 Apr 27, 2022
39a49e3
LIVE-545 Fix linting + TS migration
grsoares21 Apr 27, 2022
b629512
LIVE-545 Move Bluetooth not supported error to live-common
grsoares21 Apr 28, 2022
3d54b94
LIVE-545 Catch invalid channel error to replace for a more meaningful…
grsoares21 Apr 28, 2022
f114676
LIVE-545 Implement wording feedback
grsoares21 Apr 28, 2022
1f635a5
LIVE-545 Add todo comment about uncaught exception mapping
grsoares21 Apr 28, 2022
4870a42
Merge branch 'develop' into feat/LIVE-545-llm-fw-update
grsoares21 Apr 28, 2022
4ae1c7b
Merge branch 'develop' into feat/LIVE-545-llm-fw-update
grsoares21 Apr 28, 2022
83a0e1e
LIVE-545 Fix flow config for ts files
grsoares21 Apr 29, 2022
7164976
LIVE-545 TRemporarily activate the feature regardless of the feature …
grsoares21 Apr 29, 2022
2e64cf8
LIVE-545 Remove reinstall button when error is due to network
grsoares21 May 2, 2022
83991ed
LIVE-545 Remove reinstall apps button when there are no apps to reins…
grsoares21 May 2, 2022
c76c437
LIVE-545 Add repair flow for when device is stuck in bootloader
grsoares21 May 4, 2022
53c1e7d
LIVE-545 Replace nbsp for normal space in device name
grsoares21 May 9, 2022
be518b2
LIVE-545 Map invalid channel error to disconnected device in device a…
grsoares21 May 9, 2022
b1a5a6e
LIVE-545 Use feature flag for activating fw update once again
grsoares21 May 9, 2022
de7c4f2
LIVE-545 Fix wording for device disconnected errors
grsoares21 May 10, 2022
9c01764
LIVE-545 Update live-common dependency commit hash
grsoares21 May 10, 2022
d487ee4
LIVE-545 fix linting
grsoares21 May 10, 2022
5d67b22
LIVE-545 Add tracking for device action errors
grsoares21 May 10, 2022
c759a18
LIVE-545 fix linting
grsoares21 May 10, 2022
b74c9f8
LIVE-545 update yarn.lock
grsoares21 May 10, 2022
093a3c9
Merge branch 'develop' into feat/LIVE-545-llm-fw-update
grsoares21 May 10, 2022
699b530
LIVE-545 Fix yarn.lock
grsoares21 May 11, 2022
c498715
LIVE-545 Bump live-common and ledgerjs libraries
grsoares21 May 11, 2022
9514ac5
LIVE-545 Temporarily enable feature for alpha test
grsoares21 May 12, 2022
a416703
LIVE-545 Update the wording for device confirmation for update
grsoares21 May 16, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ project.ext.envConfigFiles = [
apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"

import com.android.build.OutputFile

/**
* The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
* and bundleReleaseJsAndAssets).
Expand Down Expand Up @@ -256,3 +254,5 @@ task copyDownloadableDepsToLibs(type: Copy) {
}

apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
5 changes: 3 additions & 2 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission-sdk-23 android:name="android.permission.ACCESS_FINE_LOCATION"/>
Expand All @@ -30,11 +31,11 @@
android:allowBackup="false"
android:theme="@style/AppTheme"
android:networkSecurityConfig="@xml/network_security_config">
<service android:name=".BackgroundService" />
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:launchMode="singleTask"
android:taskAffinity=""
android:launchMode="singleInstance"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
android:windowSoftInputMode="adjustResize"
android:screenOrientation="portrait"
Expand Down
103 changes: 103 additions & 0 deletions android/app/src/main/java/com/ledger/live/BackgroundRunner.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package com.ledger.live;

import android.app.PendingIntent
import android.content.Intent
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.facebook.react.HeadlessJsTaskService
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod


/**
* This class is in charge of receiving the call from react-native side and in turn calling a
grsoares21 marked this conversation as resolved.
Show resolved Hide resolved
* headlessJS service that will run our code in the background. We could potentially launch some
* UI helpers such as notifications from here, and have the headlessJS talk back to us with more
* exposed methods.
*/
class BackgroundRunner(var context: ReactApplicationContext) : ReactContextBaseJavaModule(context) {
override fun getName(): String {
return "BackgroundRunner"
}

/**
* Refactor this eye-sore into something cleaner someday.
grsoares21 marked this conversation as resolved.
Show resolved Hide resolved
*/
private fun createOrUpdateNotification(progress: Int, message: String) {
val intent = Intent(context, MainActivity::class.java)
intent.addCategory(Intent.CATEGORY_LAUNCHER)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, 0)

val requiresUserInput = message !== "";
var builder = NotificationCompat.Builder(context, if (requiresUserInput) {
MainApplication.HI_NOTIFICATION_CHANNEL
} else {
MainApplication.LO_NOTIFICATION_CHANNEL
})
.setPriority(if (requiresUserInput) {
NotificationCompat.PRIORITY_HIGH
} else {
NotificationCompat.PRIORITY_DEFAULT
})
.setContentText(when {
requiresUserInput -> {
"We require your confirmation on the device"
grsoares21 marked this conversation as resolved.
Show resolved Hide resolved
}
progress == 0 -> {
"Transferring update, we will notify you when we're done"
}
else -> {
"Installing $progress%"
grsoares21 marked this conversation as resolved.
Show resolved Hide resolved
}
})
.setOnlyAlertOnce(!requiresUserInput)
.setSmallIcon(R.drawable.ic_stat_group)
.setContentIntent(pendingIntent)
.setOngoing(true)
.setAutoCancel(false)

if (!requiresUserInput) {
// Conditionally make it a progress thing
builder = builder.setProgress(100, progress, progress == 0 || requiresUserInput)
grsoares21 marked this conversation as resolved.
Show resolved Hide resolved
}

with(NotificationManagerCompat.from(context)) {
// notificationId is a unique int for each notification that you must define
notify(if (requiresUserInput) {
MainApplication.FW_UPDATE_NOTIFICATION_USER
} else {
MainApplication.FW_UPDATE_NOTIFICATION_PROGRESS
}, builder.build())
}
}

@ReactMethod
fun start(deviceId: String?, firmwareSerializedJson: String?) {
createOrUpdateNotification(0, "");
HeadlessJsTaskService.acquireWakeLockNow(context)
val service = Intent(context, BackgroundService::class.java)
service.putExtra("deviceId", deviceId)
service.putExtra("firmwareSerializedJson", firmwareSerializedJson)

context.startService(service)
}

@ReactMethod
fun update(progress: Int) {
createOrUpdateNotification(progress, "");
}

@ReactMethod
fun requireUserAction(message: String) {
createOrUpdateNotification(0, message);
}

@ReactMethod
fun stop() {
with(NotificationManagerCompat.from(context)) {
cancelAll();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.ledger.live;

import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager
import java.util.*

class BackgroundRunnerPackager : ReactPackage {
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
return emptyList()
}

override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
val modules: MutableList<NativeModule> = ArrayList()
modules.add(BackgroundRunner(reactContext))
return modules
}
}
grsoares21 marked this conversation as resolved.
Show resolved Hide resolved
22 changes: 22 additions & 0 deletions android/app/src/main/java/com/ledger/live/BackgroundService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.ledger.live;

import android.content.Intent
import com.facebook.react.HeadlessJsTaskService
import com.facebook.react.bridge.Arguments
import com.facebook.react.jstasks.HeadlessJsTaskConfig

class BackgroundService : HeadlessJsTaskService() {
override fun getTaskConfig(intent: Intent): HeadlessJsTaskConfig? {
val extras = intent.extras
return if (extras != null) {
HeadlessJsTaskConfig(
"BackgroundRunnerService",
Arguments.fromBundle(extras),
600000, // timeout for the task
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might have been overkill, but what is the worst case... the service keeps on running in the background for a while 🤔

true // optional: defines whether or not the task is allowed in foreground. Default is false
)
} else {
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package com.ledger.live;
import expo.modules.ReactActivityDelegateWrapper;

import android.app.PendingIntent;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.view.View;
import android.view.WindowManager;

import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;

import com.facebook.react.ReactActivity;

import org.devio.rn.splashscreen.SplashScreen;
Expand Down Expand Up @@ -45,7 +50,6 @@ protected void onCreate(Bundle savedInstanceState) {
}
}
super.onCreate(null);

/**
* Addresses an inconvenient side-effect of using `password-visible`, that
* allowed styled texts to be pasted (receiver's address for instance) retaining
Expand Down
25 changes: 25 additions & 0 deletions android/app/src/main/java/com/ledger/live/MainApplication.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package com.ledger.live;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.res.Configuration;
import expo.modules.ApplicationLifecycleDispatcher;
import expo.modules.ReactNativeHostWrapper;

import android.app.Application;
import android.content.Context;
import android.os.Build;

import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import co.airbitz.fastcrypto.RNFastCryptoPackage;
Expand All @@ -22,6 +26,10 @@
import java.util.List;

public class MainApplication extends Application implements ReactApplication {
public static String LO_NOTIFICATION_CHANNEL = "lo-llm";
public static String HI_NOTIFICATION_CHANNEL = "hi-llm";
public static int FW_UPDATE_NOTIFICATION_PROGRESS = 1;
public static int FW_UPDATE_NOTIFICATION_USER = 2;

static {
try {
Expand All @@ -32,6 +40,21 @@ public class MainApplication extends Application implements ReactApplication {
}
}

private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
String description = "Notification channel for background running tasks";
NotificationChannel loChannel = new NotificationChannel(LO_NOTIFICATION_CHANNEL, LO_NOTIFICATION_CHANNEL, NotificationManager.IMPORTANCE_DEFAULT);
loChannel.setDescription(description);
NotificationChannel hiChannel = new NotificationChannel(HI_NOTIFICATION_CHANNEL, HI_NOTIFICATION_CHANNEL, NotificationManager.IMPORTANCE_HIGH);
hiChannel.setDescription(description);

NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(loChannel);
notificationManager.createNotificationChannel(hiChannel);
}
}


private final ReactNativeHost mReactNativeHost =
new ReactNativeHostWrapper(this, new ReactNativeHost(this) {
@Override
Expand All @@ -45,6 +68,7 @@ protected List<ReactPackage> getPackages() {
List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new BluetoothHelperPackage());
packages.add(new ReactVideoPackage());
packages.add(new BackgroundRunnerPackager());
return packages;
}

Expand All @@ -70,6 +94,7 @@ public void onCreate() {
SoLoader.init(this, /* native exopackage */ false);
initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
ApplicationLifecycleDispatcher.onApplicationCreate(this);
createNotificationChannel();
}

/**
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added android/app/src/main/res/mipmap-ldpi/smallicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions android/app/src/main/res/xml/network_security_config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">10.0.2.2</domain>
<domain includeSubdomains="true">192.168.0.168</domain>
grsoares21 marked this conversation as resolved.
Show resolved Hide resolved
<domain includeSubdomains="true">localhost</domain>
</domain-config>
</network-security-config>
1 change: 1 addition & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ buildscript {
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlinVersion"
classpath 'com.google.gms:google-services:4.3.10'
}
gradle.projectsEvaluated {
Expand Down
5 changes: 5 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { AppRegistry } from "react-native";
import * as Sentry from "@sentry/react-native";
import Config from "react-native-config";

import BackgroundRunnerService from "./services/BackgroundRunnerService";
import App from "./src";
import { getEnabled } from "./src/components/HookSentry";
import logReport from "./src/log-report";
Expand Down Expand Up @@ -71,3 +72,7 @@ if (Config.DISABLE_YELLOW_BOX) {
logReport.logReportInit();

AppRegistry.registerComponent("ledgerlivemobile", () => App);
AppRegistry.registerHeadlessTask(
"BackgroundRunnerService",
() => BackgroundRunnerService,
);
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
},
"dependencies": {
"@babel/plugin-transform-named-capturing-groups-regex": "^7.16.8",
"@cassiozen/usestatemachine": "^1.0.1",
grsoares21 marked this conversation as resolved.
Show resolved Hide resolved
"@formatjs/intl-datetimeformat": "^5.0.0",
"@formatjs/intl-getcanonicallocales": "^1.9.1",
"@formatjs/intl-locale": "^2.4.46",
Expand All @@ -74,7 +75,7 @@
"@ledgerhq/errors": "6.10.0",
"@ledgerhq/hw-transport": "6.24.1",
"@ledgerhq/hw-transport-http": "6.27.0",
"@ledgerhq/live-common": "21.36.0",
"@ledgerhq/live-common": "https://github.com/LedgerHQ/ledger-live-common.git#149561a0c598c1ec550b3a8c91ef30e16fc79650",
"@ledgerhq/logs": "6.10.0",
"@ledgerhq/native-ui": "^0.7.8",
"@ledgerhq/react-native-hid": "6.24.1",
Expand Down Expand Up @@ -190,6 +191,7 @@
"rn-snoopy": "^2.0.2",
"rxjs": "^6.6.6",
"rxjs-compat": "^6.6.6",
"semver": "^7.3.7",
"stream-browserify": "^3.0.0",
"string_decoder": "~1.3.0",
"styled-components": "^5.3.3",
Expand All @@ -210,6 +212,8 @@
"@types/react": "^17.0.30",
"@types/react-native": "^0.65.21",
"@types/react-test-renderer": "^17.0.1",
"@types/redux-actions": "^2.6.2",
"@types/semver": "^7.3.9",
"babel-jest": "^26.6.3",
"babel-plugin-module-resolver": "^4.1.0",
"detox": "^18.2.1",
Expand Down
Loading