Compare commits

..
96 changed files with 2598 additions and 1592 deletions
+2
View File
@@ -299,6 +299,8 @@ dependencies {
implementation("com.squareup.okhttp3:okhttp-tls:4.11.0")
implementation("com.google.prefab:cli:2.0.0")
implementation("com.android.tools.build:aapt2:8.1.1-10154469")
implementation "com.github.bumptech.glide:okhttp3-integration:4.12.0"
annotationProcessor "com.github.bumptech.glide:compiler:4.12.0"
}
@@ -0,0 +1,45 @@
package im.status.ethereum
import android.content.Context
import android.os.Handler
import android.os.Looper
import com.bumptech.glide.Glide
import com.bumptech.glide.Registry
import com.bumptech.glide.module.GlideModule
import com.bumptech.glide.GlideBuilder
import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader
import com.bumptech.glide.load.model.GlideUrl
import java.io.InputStream
import java.util.concurrent.Executors
class CustomGlideModule : GlideModule {
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
// Create a single thread executor for background operations
val executor = Executors.newSingleThreadExecutor()
val mainHandler = Handler(Looper.getMainLooper())
// Execute network operation in background
executor.execute {
val client = StatusOkHttpClientFactory().createNewNetworkModuleClient()
// Post results back to main thread
mainHandler.post {
client?.let {
val factory = OkHttpUrlLoader.Factory(it)
registry.replace(
GlideUrl::class.java,
InputStream::class.java,
factory
)
}
}
}
// Clean up executor
executor.shutdown()
}
override fun applyOptions(context: Context, builder: GlideBuilder) {
// No additional options needed
}
}
@@ -15,8 +15,10 @@ import cl.json.RNSharePackage
import com.reactnativecommunity.blurview.BlurViewPackage
import im.status.ethereum.keycard.RNStatusKeycardPackage
import im.status.ethereum.module.StatusPackage
import im.status.ethereum.module.StatusBackendClient
import im.status.ethereum.pushnotifications.PushNotificationPackage
import im.status.ethereum.StatusOkHttpClientFactory
import android.util.Log
class MainApplication : NavigationApplication() {
@@ -39,6 +41,26 @@ class MainApplication : NavigationApplication() {
override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
override fun createReactInstanceManager() =
super.createReactInstanceManager().apply {
addReactInstanceEventListener {
val serverEnabledEnv = true
val statusGoEndpointEnv = "127.0.0.1:60000"
val rootDataDirEnv = "/Users/frank/Downloads/tmp"
val statusGoEndpoint = "http://${statusGoEndpointEnv}/statusgo/"
val signalEndpoint = "ws://${statusGoEndpointEnv}/signals"
Log.d("MainApplication", "Configuring StatusBackendServer with serverEnabled=$serverEnabledEnv, statusGoEndpoint=$statusGoEndpoint, signalEndpoint=$signalEndpoint, rootDataDir=$rootDataDirEnv")
StatusBackendClient.getInstance()?.configStatusBackendServer(
serverEnabled = serverEnabledEnv,
statusGoEndpoint = statusGoEndpoint,
signalEndpoint = signalEndpoint,
rootDataDir = rootDataDirEnv
)
OkHttpClientProvider.setOkHttpClientFactory(StatusOkHttpClientFactory())
Log.d("MainApplication", "StatusBackendServer configured")
}
}
}
override val reactNativeHost: ReactNativeHost
@@ -47,7 +69,7 @@ class MainApplication : NavigationApplication() {
override fun onCreate() {
super.onCreate()
OkHttpClientProvider.setOkHttpClientFactory(StatusOkHttpClientFactory())
//OkHttpClientProvider.setOkHttpClientFactory(StatusOkHttpClientFactory())
WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG_WEBVIEW == "1")
+35
View File
@@ -0,0 +1,35 @@
## Solution to use Status Backend Server
`StatusBackendClient` is the entry point to use Status Backend Server. We need always to call `status-im.setup.status-backend-client/init` whether `STATUS_BACKEND_SERVER_ENABLED` is `1` or not. If it's not enabled, the invocation to functions in `native-module.core` will be delegated to built-in status-go library, otherwise it will be delegated to status-go running in status-backend server. Currently, all functions has usages in `native-module.core` should be supported delegated to.
related [PR](https://github.com/status-im/status-mobile/pull/21550)
## Usage
### Add environment variables to your local machine:
```shell
# enable using status backend server or not, otherwise it will use built-in status-go library
export STATUS_BACKEND_SERVER_ENABLED=1
#The host should contain an IP address and a port separated by a colon.
#The port comes from your running status backend server.
#If you run it by PORT=60000 make run-status-backend , then host will likely be 127.0.0.1:60000
export STATUS_BACKEND_SERVER_HOST="127.0.0.1:60000"
export STATUS_BACKEND_SERVER_ROOT_DATA_DIR="/path/to/your/root/data/dir"
```
You need to change `STATUS_BACKEND_SERVER_ROOT_DATA_DIR` to your preferred directory and ensure it exists, it should be in absolute path.
All the db files and log files(requests.log/geth.log) and keystore files etc will be stored in this directory.
### Start the status backend server:
```shell
PORT=60000 make run-status-backend
```
MAKE SURE the status-backend is checked out to a revision that's at least compatible with the revision in status-mobile/status-go-version.json before starting the server.
For the Android simulator, you need to reverse the port:
```shell
adb reverse tcp:60000 tcp:60000
```
### Known issues
- Android devices may not display images due to TLS certificate validation issues with the image server
- exportUnencryptedDatabaseV2/import-multiaccount does not work for android, probably cause of tech debt, I found it during creating the draft PR.
+30 -29
View File
@@ -1,6 +1,7 @@
PODS:
- BigInt (5.2.0)
- boost (1.83.0)
- CryptoSwift (1.5.1)
- CryptoSwift (1.8.3)
- DoubleConversion (1.1.6)
- FBLazyVector (0.73.5)
- FBReactNativeSpec (0.73.5):
@@ -16,20 +17,24 @@ PODS:
- hermes-engine/Pre-built (= 0.73.5)
- hermes-engine/Pre-built (0.73.5)
- HMSegmentedControl (1.5.6)
- Keycard (3.0.6):
- Keycard (3.1.0):
- BigInt
- CryptoSwift
- secp256k1
- SSZipArchive
- libevent (2.1.12)
- libwebp (1.2.4):
- libwebp/demux (= 1.2.4)
- libwebp/mux (= 1.2.4)
- libwebp/webp (= 1.2.4)
- libwebp/demux (1.2.4):
- libwebp (1.3.2):
- libwebp/demux (= 1.3.2)
- libwebp/mux (= 1.3.2)
- libwebp/sharpyuv (= 1.3.2)
- libwebp/webp (= 1.3.2)
- libwebp/demux (1.3.2):
- libwebp/webp
- libwebp/mux (1.2.4):
- libwebp/mux (1.3.2):
- libwebp/demux
- libwebp/webp (1.2.4)
- libwebp/sharpyuv (1.3.2)
- libwebp/webp (1.3.2):
- libwebp/sharpyuv
- RCT-Folly (2022.05.16.00):
- boost
- DoubleConversion
@@ -935,8 +940,6 @@ PODS:
- React
- react-native-orientation-locker (1.5.0):
- React-Core
- react-native-pdf (6.7.5):
- React-Core
- react-native-shake (3.4.0):
- React
- react-native-slider (3.0.0):
@@ -1150,15 +1153,15 @@ PODS:
- glog
- RCT-Folly (= 2022.05.16.00)
- React-Core
- RNImageCropPicker (0.40.0):
- RNImageCropPicker (0.41.2):
- React-Core
- React-RCTImage
- RNImageCropPicker/QBImagePickerController (= 0.40.0)
- TOCropViewController
- RNImageCropPicker/QBImagePickerController (0.40.0):
- RNImageCropPicker/QBImagePickerController (= 0.41.2)
- TOCropViewController (~> 2.7.4)
- RNImageCropPicker/QBImagePickerController (0.41.2):
- React-Core
- React-RCTImage
- TOCropViewController
- TOCropViewController (~> 2.7.4)
- RNKeychain (8.1.2):
- React-Core
- RNLinearGradient (3.0.0-alpha.1):
@@ -1187,7 +1190,7 @@ PODS:
- secp256k1 (0.1.6)
- SocketRocket (0.6.1)
- SSZipArchive (2.4.3)
- TOCropViewController (2.6.1)
- TOCropViewController (2.7.4)
- Yoga (1.14.0)
DEPENDENCIES:
@@ -1235,7 +1238,6 @@ DEPENDENCIES:
- react-native-lottie-splash-screen (from `../node_modules/react-native-lottie-splash-screen`)
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
- react-native-orientation-locker (from `../node_modules/react-native-orientation-locker`)
- react-native-pdf (from `../node_modules/react-native-pdf`)
- react-native-shake (from `../node_modules/react-native-shake`)
- "react-native-slider (from `../node_modules/@react-native-community/slider`)"
- react-native-status (from `../modules/react-native-status`)
@@ -1285,6 +1287,7 @@ DEPENDENCIES:
SPEC REPOS:
trunk:
- BigInt
- CryptoSwift
- fmt
- HMSegmentedControl
@@ -1380,8 +1383,6 @@ EXTERNAL SOURCES:
:path: "../node_modules/@react-native-community/netinfo"
react-native-orientation-locker:
:path: "../node_modules/react-native-orientation-locker"
react-native-pdf:
:path: "../node_modules/react-native-pdf"
react-native-shake:
:path: "../node_modules/react-native-shake"
react-native-slider:
@@ -1476,26 +1477,27 @@ EXTERNAL SOURCES:
CHECKOUT OPTIONS:
Keycard:
:commit: 17381678bb981c3c553bb7202a26e642cecb1817
:commit: afb19836d6730ade5035f6f86ad90715be336af2
:git: https://github.com/status-im/Keycard.swift.git
secp256k1:
:commit: 46a1fa30d9b8babeae85ff519050f42394ab5fcc
:commit: 4ab977cc2b2d7319be858bcb30a5d189bb149884
:git: https://github.com/status-im/secp256k1.swift.git
:submodules: true
SPEC CHECKSUMS:
BigInt: f668a80089607f521586bbe29513d708491ef2f7
boost: d3f49c53809116a5d38da093a8aa78bf551aed09
CryptoSwift: c4f2debceb38bf44c80659afe009f71e23e4a082
CryptoSwift: 967f37cea5a3294d9cce358f78861652155be483
DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953
FBLazyVector: 56e0e498dbb513b96c40bac6284729ba4e62672d
FBReactNativeSpec: 146c741a3f40361f6bc13a4ba284678cbedb5881
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
glog: c0eca6ffed0a6fde1965ef26dcc7bf528f052af4
glog: 53cc9a00a2af0495ac1613e9fe3df6e646a3f405
hermes-engine: 1d1835b2cc54c381909d94d1b3c8e0a2f1a94a0e
HMSegmentedControl: 34c1f54d822d8308e7b24f5d901ec674dfa31352
Keycard: ac6df4d91525c3c82635ac24d4ddd9a80aca5fc8
Keycard: 3bd24405b9a875b3b3cf1ab1be69b5c30c5f28bc
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef
libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009
RCT-Folly: 7169b2b1c44399c76a47b5deaaba715eeeb476c0
RCTRequired: 2544c0f1081a5fa12e108bb8cb40e5f4581ccd87
RCTTypeSafety: 50efabe2b115c11ed03fbf3fd79e2f163ddb5d7c
@@ -1530,7 +1532,6 @@ SPEC CHECKSUMS:
react-native-lottie-splash-screen: 4e1b1fd9d6633f9cd2106d6877eb5ba0147f3e2b
react-native-netinfo: ddaca8bbb9e6e914b1a23787ccb879bc642931c9
react-native-orientation-locker: 851f6510d8046ea2f14aa169b1e01fcd309a94ba
react-native-pdf: 103940c90d62adfd259f63cca99c7c0c306b514c
react-native-shake: de052eaa3eadc4a326b8ddd7ac80c06e8d84528c
react-native-slider: 12bd76d3d568c9c5500825db54123d44b48e4ad4
react-native-status: 21f75d492fd311dc111303da38a7a2b23a8a8466
@@ -1566,7 +1567,7 @@ SPEC CHECKSUMS:
RNFastImage: 1f2cab428712a4baaf78d6169eaec7f622556dd7
RNFS: 2bd9eb49dc82fa9676382f0585b992c424cd59df
RNGestureHandler: 15c6ef51acba34c49ff03003806cf5dd6098f383
RNImageCropPicker: 486e2f7e2b0461ce24321f751410dce1b3b49e6d
RNImageCropPicker: 771e2ca319d2cf92e04ebf334ece892ee9a6728f
RNKeychain: a65256b6ca6ba6976132cc4124b238a5b13b3d9c
RNLinearGradient: ccaaebce083b54180da61d992e7fa56abfe000d6
RNPermissions: 08e619529cced22695f4b6d0efcc0a233e278903
@@ -1579,7 +1580,7 @@ SPEC CHECKSUMS:
secp256k1: f61d67e6fdcb85fd727acf1bf35ace6036db540c
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
SSZipArchive: fe6a26b2a54d5a0890f2567b5cc6de5caa600aef
TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863
TOCropViewController: 80b8985ad794298fb69d3341de183f33d1853654
Yoga: a716eea57d0d3430219c0a5a233e1e93ee931eb7
PODFILE CHECKSUM: 5dd4a5279260d370bcdf016158c825ceb404ee06
+12
View File
@@ -572,6 +572,8 @@
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Status-StatusIm-StatusImTests/Pods-Status-StatusIm-StatusImTests-resources.sh",
"${PODS_CONFIGURATION_BUILD_DIR}/CryptoSwift/CryptoSwift.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/RNImageCropPickerPrivacyInfo.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/RNPermissions/RNPermissionsPrivacyInfo.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle",
@@ -579,6 +581,8 @@
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/CryptoSwift.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNImageCropPickerPrivacyInfo.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNPermissionsPrivacyInfo.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle",
@@ -614,6 +618,8 @@
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Status-StatusIm/Pods-Status-StatusIm-resources.sh",
"${PODS_CONFIGURATION_BUILD_DIR}/CryptoSwift/CryptoSwift.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/RNImageCropPickerPrivacyInfo.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/RNPermissions/RNPermissionsPrivacyInfo.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle",
@@ -621,6 +627,8 @@
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/CryptoSwift.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNImageCropPickerPrivacyInfo.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNPermissionsPrivacyInfo.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle",
@@ -728,6 +736,8 @@
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Status-StatusImPR/Pods-Status-StatusImPR-resources.sh",
"${PODS_CONFIGURATION_BUILD_DIR}/CryptoSwift/CryptoSwift.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/RNImageCropPickerPrivacyInfo.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/RNPermissions/RNPermissionsPrivacyInfo.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle",
@@ -735,6 +745,8 @@
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/CryptoSwift.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNImageCropPickerPrivacyInfo.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNPermissionsPrivacyInfo.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle",
+8 -1
View File
@@ -29,6 +29,8 @@
#import <Security/Security.h>
#import <react/config/ReactNativeConfig.h>
#import "StatusBackendClient.h"
//TODO: properly import the framework
extern "C" NSString* StatusgoImageServerTLSCert();
@@ -188,7 +190,12 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
__block NSURLCredential *credential = nil;
NSString *pemCert = StatusgoImageServerTLSCert();
NSString *pemCert = [StatusBackendClient executeStatusGoRequestWithResult:@"ImageServerTLSCert"
body:@""
statusgoFunction:^NSString *{
return StatusgoImageServerTLSCert();
}];
pemCert = [pemCert stringByReplacingOccurrencesOfString:@"-----BEGIN CERTIFICATE-----\n" withString:@""];
pemCert = [pemCert stringByReplacingOccurrencesOfString:@"\n-----END CERTIFICATE-----" withString:@""];
NSData *derCert = [[NSData alloc] initWithBase64EncodedString:pemCert options:NSDataBase64DecodingIgnoreUnknownCharacters];
@@ -24,25 +24,23 @@ class AccountManager(private val reactContext: ReactApplicationContext) : ReactC
@ReactMethod
fun createAccountAndLogin(createAccountRequest: String) {
Log.d(TAG, "createAccountAndLogin")
val result = Statusgo.createAccountAndLogin(createAccountRequest)
if (result.startsWith("{\"error\":\"\"")) {
Log.d(TAG, "createAccountAndLogin success: $result")
Log.d(TAG, "Geth node started")
} else {
Log.e(TAG, "createAccountAndLogin failed: $result")
}
StatusBackendClient.executeStatusGoRequest(
endpoint = "CreateAccountAndLogin",
requestBody = createAccountRequest,
statusgoFunction = { Statusgo.createAccountAndLogin(createAccountRequest) }
)
}
@ReactMethod
fun restoreAccountAndLogin(restoreAccountRequest: String) {
Log.d(TAG, "restoreAccountAndLogin")
val result = Statusgo.restoreAccountAndLogin(restoreAccountRequest)
if (result.startsWith("{\"error\":\"\"")) {
Log.d(TAG, "restoreAccountAndLogin success: $result")
Log.d(TAG, "Geth node started")
} else {
Log.e(TAG, "restoreAccountAndLogin failed: $result")
}
StatusBackendClient.executeStatusGoRequest(
endpoint = "RestoreAccountAndLogin",
requestBody = restoreAccountRequest,
statusgoFunction = { Statusgo.restoreAccountAndLogin(restoreAccountRequest) }
)
}
private fun updateConfig(jsonConfigString: String, absRootDirPath: String, keystoreDirPath: String): String {
@@ -173,12 +171,7 @@ class AccountManager(private val reactContext: ReactApplicationContext) : ReactC
accountsData,
chatKey
)
if (result.startsWith("{\"error\":\"\"")) {
Log.d(TAG, "saveAccountAndLoginWithKeycard result: $result")
Log.d(TAG, "Geth node started")
} else {
Log.e(TAG, "saveAccountAndLoginWithKeycard failed: $result")
}
utils.handleStatusGoResponse(result, "saveAccountAndLoginWithKeycard")
} catch (e: JSONException) {
Log.e(TAG, "JSON conversion failed: ${e.message}")
}
@@ -189,11 +182,7 @@ class AccountManager(private val reactContext: ReactApplicationContext) : ReactC
Log.d(TAG, "loginWithKeycard")
utils.migrateKeyStoreDir(accountData, password)
val result = Statusgo.loginWithKeycard(accountData, password, chatKey, nodeConfigJSON)
if (result.startsWith("{\"error\":\"\"")) {
Log.d(TAG, "LoginWithKeycard result: $result")
} else {
Log.e(TAG, "LoginWithKeycard failed: $result")
}
utils.handleStatusGoResponse(result, "loginWithKeycard")
}
@ReactMethod
@@ -201,22 +190,17 @@ class AccountManager(private val reactContext: ReactApplicationContext) : ReactC
Log.d(TAG, "loginWithConfig")
utils.migrateKeyStoreDir(accountData, password)
val result = Statusgo.loginWithConfig(accountData, password, configJSON)
if (result.startsWith("{\"error\":\"\"")) {
Log.d(TAG, "LoginWithConfig result: $result")
} else {
Log.e(TAG, "LoginWithConfig failed: $result")
}
utils.handleStatusGoResponse(result, "loginWithConfig")
}
@ReactMethod
fun loginAccount(request: String) {
Log.d(TAG, "loginAccount")
val result = Statusgo.loginAccount(request)
if (result.startsWith("{\"error\":\"\"")) {
Log.d(TAG, "loginAccount result: $result")
} else {
Log.e(TAG, "loginAccount failed: $result")
}
StatusBackendClient.executeStatusGoRequest(
endpoint = "LoginAccount",
requestBody = request,
statusgoFunction = { Statusgo.loginAccount(request) }
)
}
@ReactMethod
@@ -224,16 +208,31 @@ class AccountManager(private val reactContext: ReactApplicationContext) : ReactC
val absRootDirPath = utils.getNoBackupDirectory()
val newKeystoreDir = utils.pathCombine(absRootDirPath, "keystore")
utils.executeRunnableStatusGoMethod(
{ Statusgo.verifyAccountPassword(newKeystoreDir, address, password) },
val jsonParams = JSONObject()
jsonParams.put("keyStoreDir", newKeystoreDir)
jsonParams.put("address", address)
jsonParams.put("password", password)
StatusBackendClient.executeStatusGoRequestWithCallback(
endpoint = "VerifyAccountPasswordV2",
requestBody = jsonParams.toString(),
statusgoFunction = { Statusgo.verifyAccountPasswordV2(jsonParams.toString()) },
callback
)
}
@ReactMethod
fun verifyDatabasePassword(keyUID: String, password: String, callback: Callback) {
utils.executeRunnableStatusGoMethod(
{ Statusgo.verifyDatabasePassword(keyUID, password) },
val jsonParams = JSONObject()
jsonParams.put("keyUID", keyUID)
jsonParams.put("password", password)
val jsonString = jsonParams.toString()
StatusBackendClient.executeStatusGoRequestWithCallback(
endpoint = "VerifyDatabasePasswordV2",
requestBody = jsonString,
statusgoFunction = { Statusgo.verifyDatabasePasswordV2(jsonString) },
callback
)
}
@@ -249,79 +248,136 @@ class AccountManager(private val reactContext: ReactApplicationContext) : ReactC
@ReactMethod
private fun initializeApplication(request: String, callback: Callback) {
Log.d(TAG, "initializeApplication")
Log.d(TAG, "[Initializing application $request")
utils.executeRunnableStatusGoMethod({ Statusgo.initializeApplication(request) }, callback)
StatusBackendClient.executeStatusGoRequestWithCallback(
"InitializeApplication",
request,
{ Statusgo.initializeApplication(request) },
callback
)
}
@ReactMethod
private fun acceptTerms(callback: Callback) {
Log.d(TAG, "acceptTerms")
utils.executeRunnableStatusGoMethod({ Statusgo.acceptTerms() }, callback)
StatusBackendClient.executeStatusGoRequestWithCallback(
"AcceptTerms",
"",
{ Statusgo.acceptTerms() },
callback
)
}
@ReactMethod
fun logout() {
Log.d(TAG, "logout")
val runnable = Runnable {
val result = Statusgo.logout()
if (result.startsWith("{\"error\":\"\"")) {
Log.d(TAG, "Logout result: $result")
} else {
Log.e(TAG, "Logout failed: $result")
}
}
StatusThreadPoolExecutor.getInstance().execute(runnable)
StatusBackendClient.executeStatusGoRequest(
endpoint = "Logout",
requestBody = "",
statusgoFunction = { Statusgo.logout() }
)
}
@ReactMethod
fun multiAccountStoreAccount(json: String, callback: Callback) {
utils.executeRunnableStatusGoMethod({ Statusgo.multiAccountStoreAccount(json) }, callback)
StatusBackendClient.executeStatusGoRequestWithCallback(
endpoint = "MultiAccountStoreAccount",
requestBody = json,
statusgoFunction = { Statusgo.multiAccountStoreAccount(json) },
callback = callback
)
}
@ReactMethod
fun multiAccountLoadAccount(json: String, callback: Callback) {
utils.executeRunnableStatusGoMethod({ Statusgo.multiAccountLoadAccount(json) }, callback)
StatusBackendClient.executeStatusGoRequestWithCallback(
endpoint = "MultiAccountLoadAccount",
requestBody = json,
statusgoFunction = { Statusgo.multiAccountLoadAccount(json) },
callback = callback
)
}
@ReactMethod
fun multiAccountDeriveAddresses(json: String, callback: Callback) {
utils.executeRunnableStatusGoMethod({ Statusgo.multiAccountDeriveAddresses(json) }, callback)
StatusBackendClient.executeStatusGoRequestWithCallback(
endpoint = "MultiAccountDeriveAddresses",
requestBody = json,
statusgoFunction = { Statusgo.multiAccountDeriveAddresses(json) },
callback = callback
)
}
@ReactMethod
fun multiAccountGenerateAndDeriveAddresses(json: String, callback: Callback) {
utils.executeRunnableStatusGoMethod({ Statusgo.multiAccountGenerateAndDeriveAddresses(json) }, callback)
StatusBackendClient.executeStatusGoRequestWithCallback(
endpoint = "MultiAccountGenerateAndDeriveAddresses",
requestBody = json,
statusgoFunction = { Statusgo.multiAccountGenerateAndDeriveAddresses(json) },
callback = callback
)
}
@ReactMethod
fun multiAccountStoreDerived(json: String, callback: Callback) {
utils.executeRunnableStatusGoMethod({ Statusgo.multiAccountStoreDerivedAccounts(json) }, callback)
StatusBackendClient.executeStatusGoRequestWithCallback(
endpoint = "MultiAccountStoreDerivedAccounts",
requestBody = json,
statusgoFunction = { Statusgo.multiAccountStoreDerivedAccounts(json) },
callback = callback
)
}
@ReactMethod
fun multiAccountImportMnemonic(json: String, callback: Callback) {
utils.executeRunnableStatusGoMethod({ Statusgo.multiAccountImportMnemonic(json) }, callback)
StatusBackendClient.executeStatusGoRequestWithCallback(
endpoint = "MultiAccountImportMnemonic",
requestBody = json,
statusgoFunction = { Statusgo.multiAccountImportMnemonic(json) },
callback = callback
)
}
@ReactMethod
fun multiAccountImportPrivateKey(json: String, callback: Callback) {
utils.executeRunnableStatusGoMethod({ Statusgo.multiAccountImportPrivateKey(json) }, callback)
StatusBackendClient.executeStatusGoRequestWithCallback(
endpoint = "MultiAccountImportPrivateKey",
requestBody = json,
statusgoFunction = { Statusgo.multiAccountImportPrivateKey(json) },
callback = callback
)
}
@ReactMethod
fun deleteMultiaccount(keyUID: String, callback: Callback) {
val keyStoreDir = utils.getKeyStorePath(keyUID)
utils.executeRunnableStatusGoMethod({ Statusgo.deleteMultiaccount(keyUID, keyStoreDir) }, callback)
val params = JSONObject().apply {
put("keyUID", keyUID)
put("keyStoreDir", keyStoreDir)
}
val jsonString = params.toString()
StatusBackendClient.executeStatusGoRequestWithCallback(
endpoint = "DeleteMultiaccountV2",
requestBody = jsonString,
statusgoFunction = { Statusgo.deleteMultiaccountV2(jsonString) },
callback
)
}
@ReactMethod
fun getRandomMnemonic(callback: Callback) {
utils.executeRunnableStatusGoMethod({ Statusgo.getRandomMnemonic() }, callback)
StatusBackendClient.executeStatusGoRequestWithCallback(
"GetRandomMnemonic",
"",
{ Statusgo.getRandomMnemonic() },
callback
)
}
@ReactMethod
fun createAccountFromMnemonicAndDeriveAccountsForPaths(mnemonic: String, callback: Callback) {
utils.executeRunnableStatusGoMethod(
StatusBackendClient.executeStatusGoRequestWithCallback(
"CreateAccountFromMnemonicAndDeriveAccountsForPaths",
mnemonic,
{ Statusgo.createAccountFromMnemonicAndDeriveAccountsForPaths(mnemonic) },
callback
)
@@ -329,7 +385,12 @@ class AccountManager(private val reactContext: ReactApplicationContext) : ReactC
@ReactMethod
fun createAccountFromPrivateKey(json: String, callback: Callback) {
utils.executeRunnableStatusGoMethod({ Statusgo.createAccountFromPrivateKey(json) }, callback)
StatusBackendClient.executeStatusGoRequestWithCallback(
endpoint = "CreateAccountFromPrivateKey",
requestBody = json,
statusgoFunction = { Statusgo.createAccountFromPrivateKey(json) },
callback = callback
)
}
companion object {
@@ -9,6 +9,8 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import statusgo.Statusgo
import java.io.File
import org.json.JSONObject
import org.json.JSONException
class DatabaseManager(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
@@ -17,22 +19,43 @@ class DatabaseManager(private val reactContext: ReactApplicationContext) : React
override fun getName() = "DatabaseManager"
private fun getExportDBFile(): File {
StatusBackendClient.getInstance()?.let {
if (it.serverEnabled) {
return File(it.rootDataDir, exportDBFileName)
}
}
val pubDirectory = reactContext.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
return File(pubDirectory, exportDBFileName)
}
@ReactMethod
fun exportUnencryptedDatabase(accountData: String, password: String, callback: Callback) {
Log.d(TAG, "login")
Log.d(TAG, "exportUnencryptedDatabase")
val newFile = getExportDBFile()
utils.migrateKeyStoreDir(accountData, password)
val result = Statusgo.exportUnencryptedDatabase(accountData, password, newFile.absolutePath)
if (result.startsWith("{\"error\":\"\"")) {
Log.d(TAG, "Login result: $result")
} else {
Log.e(TAG, "Login failed: $result")
try {
val accountJson = JSONObject(accountData)
val params = JSONObject().apply {
put("account", accountJson)
put("password", password)
put("databasePath", newFile.absolutePath)
}
val jsonParams = params.toString()
StatusBackendClient.executeStatusGoRequestWithCallback(
endpoint = "ExportUnencryptedDatabaseV2",
requestBody = jsonParams,
statusgoFunction = { Statusgo.exportUnencryptedDatabaseV2(jsonParams) },
callback = null
)
callback.invoke(newFile.absolutePath)
} catch (e: JSONException) {
Log.e(TAG, "Error parsing account data: ${e.message}")
}
}
@@ -43,11 +66,25 @@ class DatabaseManager(private val reactContext: ReactApplicationContext) : React
val newFile = getExportDBFile()
utils.migrateKeyStoreDir(accountData, password)
val result = Statusgo.importUnencryptedDatabase(accountData, password, newFile.absolutePath)
if (result.startsWith("{\"error\":\"\"")) {
Log.d(TAG, "import result: $result")
} else {
Log.e(TAG, "import failed: $result")
try {
val accountJson = JSONObject(accountData)
val params = JSONObject().apply {
put("account", accountJson)
put("password", password)
put("databasePath", newFile.absolutePath)
}
val jsonParams = params.toString()
StatusBackendClient.executeStatusGoRequest(
endpoint = "ImportUnencryptedDatabaseV2",
requestBody = jsonParams,
statusgoFunction = { Statusgo.importUnencryptedDatabaseV2(jsonParams) }
)
} catch (e: JSONException) {
Log.e(TAG, "Error parsing account data: ${e.message}")
}
}
@@ -7,6 +7,7 @@ import com.facebook.react.bridge.Callback;
import android.util.Log;
import statusgo.Statusgo;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.function.Function;
import android.app.Activity;
import android.view.WindowManager;
@@ -39,67 +40,151 @@ public class EncryptionUtils extends ReactContextBaseJavaModule {
final String commonKeydir = this.utils.pathCombine(this.utils.getNoBackupDirectory(), "/keystore");
final String keydir = this.utils.pathCombine(commonKeydir, keyUID);
this.utils.executeRunnableStatusGoMethod(() -> Statusgo.initKeystore(keydir), callback);
StatusBackendClient.executeStatusGoRequestWithCallback(
"InitKeystore",
keydir,
() -> Statusgo.initKeystore(keydir),
callback
);
}
@ReactMethod
public void reEncryptDbAndKeystore(final String keyUID, final String password, final String newPassword, final Callback callback) throws JSONException {
this.utils.executeRunnableStatusGoMethod(() -> Statusgo.changeDatabasePassword(keyUID, password, newPassword), callback);
JSONObject params = new JSONObject();
params.put("keyUID", keyUID);
params.put("oldPassword", password);
params.put("newPassword", newPassword);
String jsonParams = params.toString();
StatusBackendClient.executeStatusGoRequestWithCallback(
"ChangeDatabasePasswordV2",
jsonParams,
() -> Statusgo.changeDatabasePasswordV2(jsonParams),
callback
);
}
@ReactMethod
public void convertToKeycardAccount(final String keyUID, final String accountData, final String options, final String keycardUID, final String password,
final String newPassword, final Callback callback) throws JSONException {
final String keyStoreDir = this.utils.getKeyStorePath(keyUID);
this.utils.executeRunnableStatusGoMethod(() -> {
Statusgo.initKeystore(keyStoreDir);
return Statusgo.convertToKeycardAccount(accountData, options, keycardUID, password, newPassword);
}, callback);
JSONObject params = new JSONObject();
params.put("keyUID", keyUID);
params.put("account", new JSONObject(accountData));
params.put("settings", new JSONObject(options));
params.put("keycardUID", keycardUID);
params.put("oldPassword", password);
params.put("newPassword", newPassword);
final String jsonParams = params.toString();
StatusBackendClient.executeStatusGoRequest(
"InitKeystore",
keyStoreDir,
() -> Statusgo.initKeystore(keyStoreDir)
);
StatusBackendClient.executeStatusGoRequestWithCallback(
"ConvertToKeycardAccountV2",
jsonParams,
() -> Statusgo.convertToKeycardAccountV2(jsonParams),
callback
);
}
@ReactMethod(isBlockingSynchronousMethod = true)
public String encodeTransfer(final String to, final String value) {
return Statusgo.encodeTransfer(to, value);
try {
JSONObject params = new JSONObject();
params.put("to", to);
params.put("value", value);
String jsonParams = params.toString();
return StatusBackendClient.executeStatusGoRequestWithResult(
"EncodeTransferV2",
jsonParams,
() -> Statusgo.encodeTransferV2(jsonParams)
);
} catch (JSONException e) {
Log.e(TAG, "Error creating JSON for encodeTransfer: " + e.getMessage());
return null;
}
}
@ReactMethod(isBlockingSynchronousMethod = true)
public String encodeFunctionCall(final String method, final String paramsJSON) {
return Statusgo.encodeFunctionCall(method, paramsJSON);
try {
JSONObject params = new JSONObject();
params.put("method", method);
params.put("paramsJSON", new JSONObject(paramsJSON));
String jsonString = params.toString();
return StatusBackendClient.executeStatusGoRequestWithResult(
"EncodeFunctionCallV2",
jsonString,
() -> Statusgo.encodeFunctionCallV2(jsonString)
);
} catch (JSONException e) {
Log.e(TAG, "Error creating JSON for encodeFunctionCall: " + e.getMessage());
return null;
}
}
@ReactMethod(isBlockingSynchronousMethod = true)
public String decodeParameters(final String decodeParamJSON) {
return Statusgo.decodeParameters(decodeParamJSON);
return StatusBackendClient.executeStatusGoRequestWithResult(
"DecodeParameters",
decodeParamJSON,
() -> Statusgo.decodeParameters(decodeParamJSON)
);
}
@ReactMethod(isBlockingSynchronousMethod = true)
public String hexToNumber(final String hex) {
return Statusgo.hexToNumber(hex);
return StatusBackendClient.executeStatusGoRequestWithResult(
"HexToNumber",
hex,
() -> Statusgo.hexToNumber(hex)
);
}
@ReactMethod(isBlockingSynchronousMethod = true)
public String numberToHex(final String numString) {
return Statusgo.numberToHex(numString);
return StatusBackendClient.executeStatusGoRequestWithResult(
"NumberToHex",
numString,
() -> Statusgo.numberToHex(numString)
);
}
@ReactMethod(isBlockingSynchronousMethod = true)
public String sha3(final String str) {
return Statusgo.sha3(str);
return StatusBackendClient.executeStatusGoRequestWithResult(
"Sha3",
str,
() -> Statusgo.sha3(str)
);
}
@ReactMethod(isBlockingSynchronousMethod = true)
public String utf8ToHex(final String str) {
return Statusgo.utf8ToHex(str);
return StatusBackendClient.executeStatusGoRequestWithResult(
"Utf8ToHex",
str,
() -> Statusgo.utf8ToHex(str)
);
}
@ReactMethod(isBlockingSynchronousMethod = true)
public String hexToUtf8(final String str) {
return Statusgo.hexToUtf8(str);
return StatusBackendClient.executeStatusGoRequestWithResult(
"HexToUtf8",
str,
() -> Statusgo.hexToUtf8(str)
);
}
@ReactMethod(isBlockingSynchronousMethod = true)
public String serializeLegacyKey(final String publicKey) {
return Statusgo.serializeLegacyKey(publicKey);
return StatusBackendClient.executeStatusGoRequestWithResult(
"SerializeLegacyKey",
publicKey,
() -> Statusgo.serializeLegacyKey(publicKey)
);
}
@ReactMethod
@@ -130,22 +215,46 @@ public class EncryptionUtils extends ReactContextBaseJavaModule {
@ReactMethod
public void hashTransaction(final String txArgsJSON, final Callback callback) throws JSONException {
this.utils.executeRunnableStatusGoMethod(() -> Statusgo.hashTransaction(txArgsJSON), callback);
StatusBackendClient.executeStatusGoRequestWithCallback(
"HashTransaction",
txArgsJSON,
() -> Statusgo.hashTransaction(txArgsJSON),
callback
);
}
@ReactMethod
public void hashMessage(final String message, final Callback callback) throws JSONException {
this.utils.executeRunnableStatusGoMethod(() -> Statusgo.hashMessage(message), callback);
StatusBackendClient.executeStatusGoRequestWithCallback(
"HashMessage",
message,
() -> Statusgo.hashMessage(message),
callback
);
}
@ReactMethod
public void multiformatDeserializePublicKey(final String multiCodecKey, final String base58btc, final Callback callback) throws JSONException {
this.utils.executeRunnableStatusGoMethod(() -> Statusgo.multiformatDeserializePublicKey(multiCodecKey,base58btc), callback);
JSONObject params = new JSONObject();
params.put("key", multiCodecKey);
params.put("outBase", base58btc);
String jsonParams = params.toString();
StatusBackendClient.executeStatusGoRequestWithCallback(
"MultiformatDeserializePublicKeyV2",
jsonParams,
() -> Statusgo.multiformatDeserializePublicKeyV2(jsonParams),
callback
);
}
@ReactMethod
public void deserializeAndCompressKey(final String desktopKey, final Callback callback) throws JSONException {
this.utils.executeRunnableStatusGoMethod(() -> Statusgo.deserializeAndCompressKey(desktopKey), callback);
StatusBackendClient.executeStatusGoRequestWithCallback(
"DeserializeAndCompressKey",
desktopKey,
() -> Statusgo.deserializeAndCompressKey(desktopKey),
callback
);
}
@ReactMethod
@@ -160,7 +269,12 @@ public class EncryptionUtils extends ReactContextBaseJavaModule {
@ReactMethod
public void signMessage(final String rpcParams, final Callback callback) throws JSONException {
this.utils.executeRunnableStatusGoMethod(() -> Statusgo.signMessage(rpcParams), callback);
StatusBackendClient.executeStatusGoRequestWithCallback(
"SignMessage",
rpcParams,
() -> Statusgo.signMessage(rpcParams),
callback
);
}
@ReactMethod
@@ -206,7 +206,12 @@ class LogManager(private val reactContext: ReactApplicationContext) : ReactConte
put("LogRequestFile", getRequestLogFile().absolutePath)
}
val config = jsonConfig.toString()
utils.executeRunnableStatusGoMethod({ Statusgo.initLogging(config) }, callback)
StatusBackendClient.executeStatusGoRequestWithCallback(
endpoint = "InitLogging",
requestBody = config,
statusgoFunction = { Statusgo.initLogging(config) },
callback = callback
)
}
@ReactMethod(isBlockingSynchronousMethod = true)
@@ -16,7 +16,12 @@ class NetworkManager(private val reactContext: ReactApplicationContext) : ReactC
@ReactMethod
fun startSearchForLocalPairingPeers(callback: Callback) {
utils.executeRunnableStatusGoMethod({ Statusgo.startSearchForLocalPairingPeers() }, callback)
StatusBackendClient.executeStatusGoRequestWithCallback(
endpoint = "StartSearchForLocalPairingPeers",
requestBody = "",
statusgoFunction = { Statusgo.startSearchForLocalPairingPeers() },
callback = callback
)
}
@ReactMethod
@@ -26,48 +31,85 @@ class NetworkManager(private val reactContext: ReactApplicationContext) : ReactC
val keyUID = senderConfig.getString("keyUID")
val keyStorePath = utils.getKeyStorePath(keyUID)
senderConfig.put("keystorePath", keyStorePath)
val jsonString = jsonConfig.toString()
utils.executeRunnableStatusGoMethod(
{ Statusgo.getConnectionStringForBootstrappingAnotherDevice(jsonConfig.toString()) },
StatusBackendClient.executeStatusGoRequestWithCallback(
endpoint = "GetConnectionStringForBootstrappingAnotherDevice",
requestBody = jsonString,
statusgoFunction = { Statusgo.getConnectionStringForBootstrappingAnotherDevice(jsonString) },
callback
)
}
@ReactMethod
fun inputConnectionStringForBootstrapping(connectionString: String, configJSON: String, callback: Callback) {
val jsonConfig = JSONObject(configJSON)
utils.executeRunnableStatusGoMethod(
{ Statusgo.inputConnectionStringForBootstrapping(connectionString, jsonConfig.toString()) },
val receiverClientConfig = JSONObject(configJSON)
val params = JSONObject().apply {
put("connectionString", connectionString)
put("receiverClientConfig", receiverClientConfig)
}
val jsonString = params.toString()
StatusBackendClient.executeStatusGoRequestWithCallback(
endpoint = "InputConnectionStringForBootstrappingV2",
requestBody = jsonString,
statusgoFunction = { Statusgo.inputConnectionStringForBootstrappingV2(jsonString) },
callback
)
}
@ReactMethod
fun sendTransactionWithSignature(txArgsJSON: String, signature: String, callback: Callback) {
utils.executeRunnableStatusGoMethod(
{ Statusgo.sendTransactionWithSignature(txArgsJSON, signature) },
StatusBackendClient.executeStatusGoRequestWithCallback(
endpoint = "SendTransactionWithSignature",
requestBody = txArgsJSON,
statusgoFunction = { Statusgo.sendTransactionWithSignature(txArgsJSON, signature) },
callback
)
}
@ReactMethod
fun sendTransaction(txArgsJSON: String, password: String, callback: Callback) {
utils.executeRunnableStatusGoMethod({ Statusgo.sendTransaction(txArgsJSON, password) }, callback)
val jsonParams = JSONObject().apply {
put("txArgs", JSONObject(txArgsJSON))
put("password", password)
}
val jsonString = jsonParams.toString()
StatusBackendClient.executeStatusGoRequestWithCallback(
endpoint = "SendTransactionV2",
requestBody = jsonString,
statusgoFunction = { Statusgo.sendTransactionV2(jsonString) },
callback
)
}
@ReactMethod
fun callRPC(payload: String, callback: Callback) {
utils.executeRunnableStatusGoMethod({ Statusgo.callRPC(payload) }, callback)
StatusBackendClient.executeStatusGoRequestWithCallback(
endpoint = "CallRPC",
requestBody = payload,
statusgoFunction = { Statusgo.callRPC(payload) },
callback = callback
)
}
@ReactMethod
fun callPrivateRPC(payload: String, callback: Callback) {
utils.executeRunnableStatusGoMethod({ Statusgo.callPrivateRPC(payload) }, callback)
StatusBackendClient.executeStatusGoRequestWithCallback(
endpoint = "CallPrivateRPC",
requestBody = payload,
statusgoFunction = { Statusgo.callPrivateRPC(payload) },
callback = callback
)
}
@ReactMethod
fun recover(rpcParams: String, callback: Callback) {
utils.executeRunnableStatusGoMethod({ Statusgo.recover(rpcParams) }, callback)
StatusBackendClient.executeStatusGoRequestWithCallback(
endpoint = "Recover",
requestBody = rpcParams,
statusgoFunction = { Statusgo.recover(rpcParams) },
callback
)
}
@ReactMethod
@@ -77,22 +119,33 @@ class NetworkManager(private val reactContext: ReactApplicationContext) : ReactC
val keyUID = senderConfig.getString("loggedInKeyUid")
val keyStorePath = utils.getKeyStorePath(keyUID)
senderConfig.put("keystorePath", keyStorePath)
val jsonString = jsonConfig.toString()
utils.executeRunnableStatusGoMethod(
{ Statusgo.getConnectionStringForExportingKeypairsKeystores(jsonConfig.toString()) },
callback)
StatusBackendClient.executeStatusGoRequestWithCallback(
endpoint = "GetConnectionStringForExportingKeypairsKeystores",
requestBody = jsonString,
statusgoFunction = { Statusgo.getConnectionStringForExportingKeypairsKeystores(jsonString) },
callback = callback
)
}
@ReactMethod
fun inputConnectionStringForImportingKeypairsKeystores(connectionString: String, configJSON: String, callback: Callback) {
val jsonConfig = JSONObject(configJSON)
val receiverConfig = jsonConfig.getJSONObject("receiverConfig")
val keystoreFilesReceiverClientConfig = JSONObject(configJSON)
val receiverConfig = keystoreFilesReceiverClientConfig.getJSONObject("receiverConfig")
val keyStorePath = utils.pathCombine(utils.getNoBackupDirectory(), "/keystore")
receiverConfig.put("keystorePath", keyStorePath)
utils.executeRunnableStatusGoMethod(
{ Statusgo.inputConnectionStringForImportingKeypairsKeystores(connectionString, jsonConfig.toString()) },
callback
val params = JSONObject().apply {
put("connectionString", connectionString)
put("keystoreFilesReceiverClientConfig", keystoreFilesReceiverClientConfig)
}
val jsonString = params.toString()
StatusBackendClient.executeStatusGoRequestWithCallback(
endpoint = "InputConnectionStringForImportingKeypairsKeystoresV2",
requestBody = jsonString,
statusgoFunction = { Statusgo.inputConnectionStringForImportingKeypairsKeystoresV2(jsonString) },
callback = callback
)
}
}
@@ -0,0 +1,231 @@
package im.status.ethereum.module
import android.util.Log
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import okhttp3.WebSocket
import okhttp3.WebSocketListener
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.Callback
import java.net.SocketException
import java.net.SocketTimeoutException
import java.util.concurrent.TimeUnit
class StatusBackendClient(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
companion object {
private const val TAG = "StatusBackendClient"
private val JSON = "application/json; charset=utf-8".toMediaType()
private const val TIMEOUT_SECONDS = 30L
@Volatile private var instance: StatusBackendClient? = null
private lateinit var utils: Utils
private val initializationCallbacks = mutableListOf<() -> Unit>()
fun getInstance(): StatusBackendClient? = instance
@JvmStatic
fun executeStatusGoRequest(
endpoint: String,
requestBody: String,
statusgoFunction: () -> String
) {
val statusBackendClient = getInstance()
if (statusBackendClient?.serverEnabled == true) {
val result = statusBackendClient.request(endpoint, requestBody)
result.onSuccess { response ->
utils.handleStatusGoResponse(response, endpoint)
}.onFailure { error ->
Log.e(TAG, "request to $endpoint failed", error)
}
} else {
val result = statusgoFunction()
utils.handleStatusGoResponse(result, endpoint)
}
}
@JvmStatic
fun executeStatusGoRequestWithCallback(
endpoint: String,
requestBody: String,
statusgoFunction: () -> String,
callback: Callback?
) {
val statusBackendClient = getInstance()
if (statusBackendClient?.serverEnabled == true) {
val runnable = Runnable {
val result = statusBackendClient.request(endpoint, requestBody)
result.onSuccess { response ->
callback?.invoke(response)
}.onFailure { error ->
Log.e(TAG, "request to $endpoint failed", error)
callback?.invoke(false)
}
}
StatusThreadPoolExecutor.getInstance().execute(runnable)
} else {
utils.executeRunnableStatusGoMethod(statusgoFunction, callback)
}
}
@JvmStatic
fun executeStatusGoRequestWithResult(
endpoint: String,
requestBody: String,
statusgoFunction: () -> String
): String {
val statusBackendClient = getInstance()
return if (statusBackendClient?.serverEnabled == true) {
val result = statusBackendClient.request(endpoint, requestBody)
result.getOrElse { error ->
Log.e(TAG, "request to $endpoint failed", error)
""
}
} else {
if (endpoint == "ImageServerTLSCert") {
Log.i(TAG, "ImageServerTLSCert endpoint called without server enabled - " +
"stacktrace: ${Thread.currentThread().stackTrace.joinToString("\n")}")
}
statusgoFunction()
}
}
fun addInitializationCallback(callback: () -> Unit) {
if (instance?.initialized == true) {
Log.d(TAG, "Executing initialization callback immediately")
callback()
} else {
Log.d(TAG, "Adding initialization callback")
initializationCallbacks.add(callback)
}
}
}
init {
instance = this
utils = Utils(reactContext)
}
override fun getName(): String = "StatusBackendClient"
private val httpClient = OkHttpClient.Builder()
.connectTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
.writeTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
.readTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build()
private val wsClient = OkHttpClient.Builder()
.retryOnConnectionFailure(true)
.build()
private var webSocket: WebSocket? = null
@Volatile var serverEnabled = true
@Volatile private var statusGoEndpoint: String? = null
@Volatile private var signalEndpoint: String? = null
@Volatile var rootDataDir: String? = null
@Volatile var initialized = false
@ReactMethod
fun configStatusBackendServer(
serverEnabled: Boolean,
statusGoEndpoint: String,
signalEndpoint: String,
rootDataDir: String
) {
configure(serverEnabled, statusGoEndpoint, signalEndpoint, rootDataDir)
this.initialized = true
// Execute callbacks
initializationCallbacks.forEach { it() }
initializationCallbacks.clear()
Log.d(TAG, "StatusBackendClient initialized")
}
private fun configure(
serverEnabled: Boolean,
statusGoEndpoint: String,
signalEndpoint: String,
rootDataDir: String
) {
Log.d(TAG, "configure: serverEnabled=$serverEnabled, statusGoEndpoint=$statusGoEndpoint, " +
"signalEndpoint=$signalEndpoint, rootDataDir=$rootDataDir")
this.serverEnabled = serverEnabled
if (serverEnabled) {
this.statusGoEndpoint = statusGoEndpoint
this.signalEndpoint = signalEndpoint
this.rootDataDir = rootDataDir
connectWebSocket()
} else {
disconnectWebSocket()
this.statusGoEndpoint = null
this.signalEndpoint = null
this.rootDataDir = null
}
}
private fun connectWebSocket() {
if (!serverEnabled || signalEndpoint == null) {
return
}
val request = Request.Builder()
.url("$signalEndpoint")
.build()
webSocket = wsClient.newWebSocket(request, object : WebSocketListener() {
override fun onMessage(webSocket: WebSocket, text: String) {
StatusModule.getInstance()?.handleSignal(text)
}
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
Log.e(TAG, "WebSocket error: ${t.message}")
}
})
}
private fun disconnectWebSocket() {
webSocket?.cancel()
webSocket = null
}
fun request(endpoint: String, body: String): Result<String> {
if (!serverEnabled || statusGoEndpoint == null) {
return Result.failure(IllegalStateException("Status backend server is not enabled"))
}
val fullUrl = "$statusGoEndpoint$endpoint"
return try {
val request = Request.Builder()
.url(fullUrl)
.post(body.toRequestBody(JSON))
.build()
httpClient.newCall(request).execute().use { response ->
val responseBody = response.body?.string() ?: ""
if (response.isSuccessful) {
Log.d(TAG, "Request to $endpoint succeeded: $responseBody")
Result.success(responseBody)
} else {
val errorMsg = "Request failed with code ${response.code}: $responseBody"
Log.e(TAG, "Request to $endpoint failed: $errorMsg")
Result.failure(Exception(errorMsg))
}
}
} catch (e: Exception) {
when (e) {
is SocketTimeoutException -> Log.e(TAG, "Request to $endpoint timed out", e)
is SocketException -> Log.e(TAG, "Socket error for $endpoint", e)
else -> Log.e(TAG, "Request to $endpoint failed with exception", e)
}
Result.failure(e)
}
}
}
@@ -9,12 +9,17 @@ import statusgo.SignalHandler
import statusgo.Statusgo
import org.json.JSONException
import android.view.WindowManager
import org.json.JSONObject
class StatusModule(private val reactContext: ReactApplicationContext, private val rootedDevice: Boolean) : ReactContextBaseJavaModule(reactContext), LifecycleEventListener, SignalHandler {
companion object {
private const val TAG = "StatusModule"
private var module: StatusModule? = null
fun getInstance(): StatusModule? {
return module
}
}
private val utils: Utils = Utils(reactContext)
@@ -56,45 +61,97 @@ class StatusModule(private val reactContext: ReactApplicationContext, private va
@ReactMethod
fun connectionChange(type: String, isExpensive: Boolean) {
Log.d(TAG, "ConnectionChange: $type, is expensive $isExpensive")
Statusgo.connectionChange(type, if (isExpensive) 1 else 0)
val params = JSONObject().apply {
put("type", type)
put("expensive", isExpensive)
}
val jsonString = params.toString()
StatusBackendClient.executeStatusGoRequest(
endpoint = "ConnectionChangeV2",
requestBody = jsonString,
statusgoFunction = { Statusgo.connectionChangeV2(jsonString) }
)
}
@ReactMethod
fun appStateChange(type: String) {
Log.d(TAG, "AppStateChange: $type")
Statusgo.appStateChange(type)
fun appStateChange(state: String) {
Log.d(TAG, "AppStateChange: $state")
val params = JSONObject().apply {
put("state", state)
}
val jsonString = params.toString()
StatusBackendClient.executeStatusGoRequest(
endpoint = "AppStateChangeV2",
requestBody = jsonString,
statusgoFunction = { Statusgo.appStateChangeV2(jsonString) }
)
}
@ReactMethod
fun startLocalNotifications() {
Log.d(TAG, "startLocalNotifications")
Statusgo.startLocalNotifications()
StatusBackendClient.executeStatusGoRequest(
endpoint = "StartLocalNotifications",
requestBody = "",
statusgoFunction = { Statusgo.startLocalNotifications() }
)
}
@ReactMethod
fun getNodeConfig(callback: Callback) {
utils.executeRunnableStatusGoMethod({ Statusgo.getNodeConfig() }, callback)
StatusBackendClient.executeStatusGoRequestWithCallback(
endpoint = "GetNodeConfig",
requestBody = "",
statusgoFunction = { Statusgo.getNodeConfig() },
callback = callback
)
}
@ReactMethod
fun addCentralizedMetric(request: String, callback: Callback) {
utils.executeRunnableStatusGoMethod({ Statusgo.addCentralizedMetric(request) }, callback)
StatusBackendClient.executeStatusGoRequestWithCallback(
endpoint = "AddCentralizedMetric",
requestBody = request,
statusgoFunction = { Statusgo.addCentralizedMetric(request) },
callback
)
}
@ReactMethod
fun toggleCentralizedMetrics(request: String, callback: Callback) {
utils.executeRunnableStatusGoMethod({ Statusgo.toggleCentralizedMetrics(request) }, callback)
StatusBackendClient.executeStatusGoRequestWithCallback(
endpoint = "ToggleCentralizedMetrics",
requestBody = request,
statusgoFunction = { Statusgo.toggleCentralizedMetrics(request) },
callback
)
}
@ReactMethod
fun deleteImportedKey(keyUID: String, address: String, password: String, callback: Callback) {
val keyStoreDir = utils.getKeyStorePath(keyUID)
utils.executeRunnableStatusGoMethod({ Statusgo.deleteImportedKey(address, password, keyStoreDir) }, callback)
val params = JSONObject().apply {
put("address", address)
put("password", password)
put("keyStoreDir", keyStoreDir)
}
val jsonString = params.toString()
StatusBackendClient.executeStatusGoRequestWithCallback(
endpoint = "DeleteImportedKeyV2",
requestBody = jsonString,
statusgoFunction = { Statusgo.deleteImportedKeyV2(jsonString) },
callback
)
}
@ReactMethod(isBlockingSynchronousMethod = true)
fun fleets(): String {
return Statusgo.fleets()
return StatusBackendClient.executeStatusGoRequestWithResult(
endpoint = "Fleets",
requestBody = "",
statusgoFunction = { Statusgo.fleets() }
)
}
override fun getConstants(): Map<String, Any>? {
@@ -9,7 +9,12 @@ import statusgo.Statusgo
class StatusPackage(private val rootedDevice: Boolean) : ReactPackage {
companion object {
fun getImageTLSCert(): String = Statusgo.imageServerTLSCert()
fun getImageTLSCert(): String =
StatusBackendClient.executeStatusGoRequestWithResult(
endpoint = "ImageServerTLSCert",
requestBody = "",
statusgoFunction = { Statusgo.imageServerTLSCert() }
)
}
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
@@ -26,6 +31,7 @@ class StatusPackage(private val rootedDevice: Boolean) : ReactPackage {
add(NetworkManager(reactContext))
add(MailManager(reactContext))
add(RNSelectableTextInputModule(reactContext))
add(StatusBackendClient(reactContext))
}
return modules
@@ -25,6 +25,11 @@ class Utils(private val reactContext: ReactApplicationContext) : ReactContextBas
}
fun getNoBackupDirectory(): String {
StatusBackendClient.getInstance()?.let { client ->
if (client.serverEnabled && client.rootDataDir != null) {
return client.rootDataDir!!
}
}
return reactContext.noBackupFilesDir.absolutePath
}
@@ -34,6 +39,11 @@ class Utils(private val reactContext: ReactApplicationContext) : ReactContextBas
}
fun getPublicStorageDirectory(): File? {
StatusBackendClient.getInstance()?.let { client ->
if (client.serverEnabled && client.rootDataDir != null) {
return File(client.rootDataDir!!)
}
}
// Environment.getExternalStoragePublicDirectory doesn't work as expected on Android Q
// https://developer.android.com/reference/android/os/Environment#getExternalStoragePublicDirectory(java.lang.String)
return reactContext.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
@@ -69,8 +79,22 @@ class Utils(private val reactContext: ReactApplicationContext) : ReactContextBas
val keydirFile = File(keydir)
if (!keydirFile.exists() || keydirFile.list().isEmpty()) {
Log.d(TAG, "migrateKeyStoreDir")
Statusgo.migrateKeyStoreDir(accountData, password, commonKeydir, keydir)
Statusgo.initKeystore(keydir)
val jsonParams = JSONObject()
jsonParams.put("account", JSONObject(accountData)) // Remove 'new' keyword
jsonParams.put("password", password)
jsonParams.put("oldDir", commonKeydir)
jsonParams.put("newDir", keydir)
StatusBackendClient.executeStatusGoRequest(
endpoint = "MigrateKeyStoreDirV2",
requestBody = jsonParams.toString(),
statusgoFunction = { Statusgo.migrateKeyStoreDirV2(jsonParams.toString()) }
)
StatusBackendClient.executeStatusGoRequest(
endpoint = "InitKeystore",
requestBody = keydir,
statusgoFunction = { Statusgo.initKeystore(keydir) }
)
}
} catch (e: JSONException) {
Log.e(TAG, "JSON conversion failed: ${e.message}")
@@ -99,15 +123,15 @@ class Utils(private val reactContext: ReactApplicationContext) : ReactContextBas
return false
}
fun executeRunnableStatusGoMethod(method: Supplier<String>, callback: Callback) {
fun executeRunnableStatusGoMethod(method: Supplier<String>, callback: Callback?) {
if (!checkAvailability()) {
callback.invoke(false)
callback?.invoke(false)
return
}
val runnableTask = Runnable {
val res = method.get()
callback.invoke(res)
callback?.invoke(res)
}
StatusThreadPoolExecutor.getInstance().execute(runnableTask)
@@ -115,7 +139,15 @@ class Utils(private val reactContext: ReactApplicationContext) : ReactContextBas
@ReactMethod
fun validateMnemonic(seed: String, callback: Callback) {
executeRunnableStatusGoMethod({ Statusgo.validateMnemonic(seed) }, callback)
val jsonParams = JSONObject()
jsonParams.put("mnemonic", seed)
val jsonString = jsonParams.toString()
StatusBackendClient.executeStatusGoRequestWithCallback(
endpoint = "ValidateMnemonicV2",
requestBody = jsonString,
statusgoFunction = { Statusgo.validateMnemonicV2(jsonString) },
callback
)
}
fun is24Hour(): Boolean {
@@ -124,17 +156,29 @@ class Utils(private val reactContext: ReactApplicationContext) : ReactContextBas
@ReactMethod(isBlockingSynchronousMethod = true)
fun checkAddressChecksum(address: String): String {
return Statusgo.checkAddressChecksum(address)
return StatusBackendClient.executeStatusGoRequestWithResult(
endpoint = "CheckAddressChecksum",
requestBody = address,
statusgoFunction = { Statusgo.checkAddressChecksum(address) }
)
}
@ReactMethod(isBlockingSynchronousMethod = true)
fun isAddress(address: String): String {
return Statusgo.isAddress(address)
return StatusBackendClient.executeStatusGoRequestWithResult(
endpoint = "IsAddress",
requestBody = address,
statusgoFunction = { Statusgo.isAddress(address) }
)
}
@ReactMethod(isBlockingSynchronousMethod = true)
fun toChecksumAddress(address: String): String {
return Statusgo.toChecksumAddress(address)
return StatusBackendClient.executeStatusGoRequestWithResult(
endpoint = "ToChecksumAddress",
requestBody = address,
statusgoFunction = { Statusgo.toChecksumAddress(address) }
)
}
fun readableArrayToStringArray(r: ReadableArray): Array<String> {
@@ -150,6 +194,19 @@ class Utils(private val reactContext: ReactApplicationContext) : ReactContextBas
@ReactMethod(isBlockingSynchronousMethod = true)
fun validateConnectionString(connectionString: String): String {
return Statusgo.validateConnectionString(connectionString)
return StatusBackendClient.executeStatusGoRequestWithResult(
endpoint = "ValidateConnectionString",
requestBody = connectionString,
statusgoFunction = { Statusgo.validateConnectionString(connectionString) }
)
}
fun handleStatusGoResponse(response: String, source: String) {
//TODO(frank) we should remove sensitive data from the response
if (response.startsWith("{\"error\":\"\"")) {
Log.d(TAG, "$source success: $response")
} else {
Log.e(TAG, "$source failed: $response")
}
}
}
@@ -3,6 +3,7 @@
#import "React/RCTEventDispatcher.h"
#import "Statusgo.h"
#import "Utils.h"
#import "StatusBackendClient.h"
@implementation AccountManager
@@ -12,23 +13,29 @@ RCT_EXPORT_METHOD(createAccountAndLogin:(NSString *)request) {
#if DEBUG
NSLog(@"createAccountAndLogin() method called");
#endif
StatusgoCreateAccountAndLogin(request);
[StatusBackendClient executeStatusGoRequest:@"CreateAccountAndLogin"
body:request
statusgoFunction:^NSString *{
return StatusgoCreateAccountAndLogin(request);
}];
}
RCT_EXPORT_METHOD(restoreAccountAndLogin:(NSString *)request) {
#if DEBUG
NSLog(@"restoreAccountAndLogin() method called");
#endif
StatusgoRestoreAccountAndLogin(request);
[StatusBackendClient executeStatusGoRequest:@"RestoreAccountAndLogin"
body:request
statusgoFunction:^NSString *{
return StatusgoRestoreAccountAndLogin(request);
}];
}
-(NSString *) prepareDirAndUpdateConfig:(NSString *)config
withKeyUID:(NSString *)keyUID {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error = nil;
NSURL *rootUrl =[[fileManager
URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask]
lastObject];
NSURL *rootUrl =[Utils getRootUrl];
NSURL *absTestnetFolderName = [rootUrl URLByAppendingPathComponent:@"ethereum/testnet"];
if (![fileManager fileExistsAtPath:absTestnetFolderName.path])
@@ -102,8 +109,24 @@ RCT_EXPORT_METHOD(deleteMultiaccount:(NSString *)keyUID
NSLog(@"DeleteMultiaccount() method called");
#endif
NSURL *multiaccountKeystoreDir = [Utils getKeyStoreDirForKeyUID:keyUID];
NSString *result = StatusgoDeleteMultiaccount(keyUID, multiaccountKeystoreDir.path);
callback(@[result]);
NSDictionary *params = @{
@"keyUID": keyUID,
@"keyStoreDir": multiaccountKeystoreDir.path
};
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:params options:0 error:&error];
if (error) {
NSLog(@"Error creating JSON: %@", error);
return;
}
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
[StatusBackendClient executeStatusGoRequestWithCallback:@"DeleteMultiaccountV2"
body:jsonString
statusgoFunction:^NSString *{
return StatusgoDeleteMultiaccountV2(jsonString);
}
callback:callback];
}
RCT_EXPORT_METHOD(prepareDirAndUpdateConfig:(NSString *)keyUID
@@ -163,36 +186,69 @@ RCT_EXPORT_METHOD(loginWithConfig:(NSString *)accountData
RCT_EXPORT_METHOD(loginAccount:(NSString *)request) {
#if DEBUG
NSLog(@"LoginAccount() method called");
NSLog(@"loginAccount() method called");
#endif
NSString *result = StatusgoLoginAccount(request);
NSLog(@"%@", result);
[StatusBackendClient executeStatusGoRequest:@"LoginAccount"
body:request
statusgoFunction:^NSString *{
return StatusgoLoginAccount(request);
}];
}
RCT_EXPORT_METHOD(verify:(NSString *)address
password:(NSString *)password
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"VerifyAccountPassword() method called");
NSLog(@"VerifyAccountPasswordV2() method called");
#endif
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *rootUrl =[[fileManager
URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask]
lastObject];
NSURL *absKeystoreUrl = [rootUrl URLByAppendingPathComponent:@"keystore"];
NSString *result = StatusgoVerifyAccountPassword(absKeystoreUrl.path, address, password);
callback(@[result]);
NSURL *rootUrl = [Utils getRootUrl];
NSString *keystorePath = [rootUrl.path stringByAppendingPathComponent:@"keystore"];
NSDictionary *params = @{
@"keyStoreDir": keystorePath,
@"address": address,
@"password": password
};
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:params options:0 error:&error];
if (error) {
NSLog(@"Error creating JSON: %@", error);
return;
}
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
[StatusBackendClient executeStatusGoRequestWithCallback:@"VerifyAccountPasswordV2"
body:jsonString
statusgoFunction:^NSString *{
return StatusgoVerifyAccountPasswordV2(jsonString);
}
callback:callback];
}
RCT_EXPORT_METHOD(verifyDatabasePassword:(NSString *)keyUID
password:(NSString *)password
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"VerifyDatabasePassword() method called");
NSLog(@"VerifyDatabasePasswordV2() method called");
#endif
NSString *result = StatusgoVerifyDatabasePassword(keyUID, password);
callback(@[result]);
NSDictionary *params = @{
@"keyUID": keyUID,
@"password": password
};
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:params options:0 error:&error];
if (error) {
NSLog(@"Error creating JSON: %@", error);
return;
}
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
[StatusBackendClient executeStatusGoRequestWithCallback:@"VerifyDatabasePasswordV2"
body:jsonString
statusgoFunction:^NSString *{
return StatusgoVerifyDatabasePasswordV2(jsonString);
}
callback:callback];
}
RCT_EXPORT_METHOD(initializeApplication:(NSString *)request
@@ -200,62 +256,173 @@ RCT_EXPORT_METHOD(initializeApplication:(NSString *)request
#if DEBUG
NSLog(@"initializeApplication() method called");
#endif
NSString *result = StatusgoInitializeApplication(request);
callback(@[result]);
[StatusBackendClient executeStatusGoRequestWithCallback:@"InitializeApplication"
body:request
statusgoFunction:^NSString *{
return StatusgoInitializeApplication(request);
}
callback:callback];
}
RCT_EXPORT_METHOD(acceptTerms:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"acceptTerms() method called");
#endif
NSString *result = StatusgoAcceptTerms();
callback(@[result]);
[StatusBackendClient executeStatusGoRequestWithCallback:@"AcceptTerms"
body:@""
statusgoFunction:^NSString *{
return StatusgoAcceptTerms();
}
callback:callback];
}
RCT_EXPORT_METHOD(openAccounts:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"OpenAccounts() method called");
#endif
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *rootUrl =[[fileManager
URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask]
lastObject];
NSURL *rootUrl =[Utils getRootUrl];
NSString *result = StatusgoOpenAccounts(rootUrl.path);
callback(@[result]);
}
RCT_EXPORT_METHOD(logout) {
#if DEBUG
NSLog(@"Logout() method called");
NSLog(@"Logout() method called");
#endif
NSString *result = StatusgoLogout();
[StatusBackendClient executeStatusGoRequest:@"Logout"
body:@""
statusgoFunction:^NSString *{
return StatusgoLogout();
}];
}
NSLog(@"%@", result);
RCT_EXPORT_METHOD(multiAccountStoreAccount:(NSString *)json
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"MultiAccountStoreAccount() method called");
#endif
[StatusBackendClient executeStatusGoRequestWithCallback:@"MultiAccountStoreAccount"
body:json
statusgoFunction:^NSString *{
return StatusgoMultiAccountStoreAccount(json);
}
callback:callback];
}
RCT_EXPORT_METHOD(multiAccountLoadAccount:(NSString *)json
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"MultiAccountLoadAccount() method called");
#endif
[StatusBackendClient executeStatusGoRequestWithCallback:@"MultiAccountLoadAccount"
body:json
statusgoFunction:^NSString *{
return StatusgoMultiAccountLoadAccount(json);
}
callback:callback];
}
RCT_EXPORT_METHOD(multiAccountDeriveAddresses:(NSString *)json
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"multiAccountDeriveAddresses() method called");
#endif
[StatusBackendClient executeStatusGoRequestWithCallback:@"MultiAccountDeriveAddresses"
body:json
statusgoFunction:^NSString *{
return StatusgoMultiAccountDeriveAddresses(json);
}
callback:callback];
}
RCT_EXPORT_METHOD(multiAccountGenerateAndDeriveAddresses:(NSString *)json
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"MultiAccountGenerateAndDeriveAddresses() method called");
#endif
[StatusBackendClient executeStatusGoRequestWithCallback:@"MultiAccountGenerateAndDeriveAddresses"
body:json
statusgoFunction:^NSString *{
return StatusgoMultiAccountGenerateAndDeriveAddresses(json);
}
callback:callback];
}
RCT_EXPORT_METHOD(multiAccountStoreDerived:(NSString *)json
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"MultiAccountStoreDerived() method called");
#endif
[StatusBackendClient executeStatusGoRequestWithCallback:@"MultiAccountStoreDerivedAccounts"
body:json
statusgoFunction:^NSString *{
return StatusgoMultiAccountStoreDerivedAccounts(json);
}
callback:callback];
}
RCT_EXPORT_METHOD(multiAccountImportMnemonic:(NSString *)json
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"MultiAccountImportMnemonic() method called");
#endif
[StatusBackendClient executeStatusGoRequestWithCallback:@"MultiAccountImportMnemonic"
body:json
statusgoFunction:^NSString *{
return StatusgoMultiAccountImportMnemonic(json);
}
callback:callback];
}
RCT_EXPORT_METHOD(multiAccountImportPrivateKey:(NSString *)json
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"MultiAccountImportPrivateKey() method called");
#endif
[StatusBackendClient executeStatusGoRequestWithCallback:@"MultiAccountImportPrivateKey"
body:json
statusgoFunction:^NSString *{
return StatusgoMultiAccountImportPrivateKey(json);
}
callback:callback];
}
RCT_EXPORT_METHOD(getRandomMnemonic:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"GetRandomMnemonic() method called");
#endif
NSString *result = StatusgoGetRandomMnemonic();
callback(@[result]);
[StatusBackendClient executeStatusGoRequestWithCallback:@"GetRandomMnemonic"
body:@""
statusgoFunction:^NSString *{
return StatusgoGetRandomMnemonic();
}
callback:callback];
}
RCT_EXPORT_METHOD(createAccountFromMnemonicAndDeriveAccountsForPaths:(NSString *)mnemonic callback:(RCTResponseSenderBlock)callback) {
RCT_EXPORT_METHOD(createAccountFromMnemonicAndDeriveAccountsForPaths:(NSString *)mnemonic
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"createAccountFromMnemonicAndDeriveAccountsForPaths() method called");
#endif
NSString *result = StatusgoCreateAccountFromMnemonicAndDeriveAccountsForPaths(mnemonic);
callback(@[result]);
[StatusBackendClient executeStatusGoRequestWithCallback:@"CreateAccountFromMnemonicAndDeriveAccountsForPaths"
body:mnemonic
statusgoFunction:^NSString *{
return StatusgoCreateAccountFromMnemonicAndDeriveAccountsForPaths(mnemonic);
}
callback:callback];
}
RCT_EXPORT_METHOD(createAccountFromPrivateKey:(NSString *)configJSON callback:(RCTResponseSenderBlock)callback) {
RCT_EXPORT_METHOD(createAccountFromPrivateKey:(NSString *)json
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"createAccountFromPrivateKey() method called");
#endif
NSString *result = StatusgoCreateAccountFromPrivateKey(configJSON);
callback(@[result]);
[StatusBackendClient executeStatusGoRequestWithCallback:@"CreateAccountFromPrivateKey"
body:json
statusgoFunction:^NSString *{
return StatusgoCreateAccountFromPrivateKey(json);
}
callback:callback];
}
@end
@@ -3,7 +3,7 @@
#import "React/RCTEventDispatcher.h"
#import "Statusgo.h"
#import "Utils.h"
#import "StatusBackendClient.h"
@implementation DatabaseManager
RCT_EXPORT_MODULE();
@@ -16,8 +16,31 @@ RCT_EXPORT_METHOD(exportUnencryptedDatabase:(NSString *)accountData
#endif
NSString *filePath = [Utils getExportDbFilePath];
StatusgoExportUnencryptedDatabase(accountData, password, filePath);
NSDictionary *params = @{
@"account": [NSJSONSerialization JSONObjectWithData:[accountData dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil],
@"password": password,
@"databasePath": filePath
};
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:params options:0 error:&error];
if (error) {
NSLog(@"Error creating JSON: %@", [error localizedDescription]);
callback(@[filePath]);
return;
}
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
[StatusBackendClient executeStatusGoRequestWithCallback:@"ExportUnencryptedDatabaseV2"
body:jsonString
statusgoFunction:^NSString *{
return StatusgoExportUnencryptedDatabaseV2(jsonString);
}
callback:nil];
callback(@[filePath]);
}
@@ -26,7 +49,31 @@ RCT_EXPORT_METHOD(importUnencryptedDatabase:(NSString *)accountData
#if DEBUG
NSLog(@"importUnencryptedDatabase() method called");
#endif
"";
NSString *filePath = [Utils getExportDbFilePath];
[Utils migrateKeystore:accountData password:password];
NSDictionary *params = @{
@"account": [NSJSONSerialization JSONObjectWithData:[accountData dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil],
@"password": password,
@"databasePath": filePath
};
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:params options:0 error:&error];
if (error) {
NSLog(@"Error creating JSON: %@", [error localizedDescription]);
return;
}
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
[StatusBackendClient executeStatusGoRequest:@"ImportUnencryptedDatabaseV2"
body:jsonString
statusgoFunction:^NSString *{
return StatusgoImportUnencryptedDatabaseV2(jsonString);
}];
}
@@ -3,6 +3,7 @@
#import "React/RCTEventDispatcher.h"
#import "Statusgo.h"
#import "Utils.h"
#import "StatusBackendClient.h"
@implementation EncryptionUtils
@@ -15,21 +16,14 @@ RCT_EXPORT_METHOD(initKeystore:(NSString *)keyUID
#if DEBUG
NSLog(@"initKeystore() method called");
#endif
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *rootUrl =[[fileManager
URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask]
lastObject];
NSURL *commonKeystoreDir = [rootUrl URLByAppendingPathComponent:@"keystore"];
NSURL *keystoreDir = [commonKeystoreDir URLByAppendingPathComponent:keyUID];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^(void)
{
NSString *res = StatusgoInitKeystore(keystoreDir.path);
NSLog(@"InitKeyStore result %@", res);
callback(@[]);
});
NSURL *multiaccountKeystoreDir = [Utils getKeyStoreDirForKeyUID:keyUID];
[StatusBackendClient executeStatusGoRequestWithCallback:@"InitKeystore"
body:multiaccountKeystoreDir.path
statusgoFunction:^NSString *{
return StatusgoInitKeystore(multiaccountKeystoreDir.path);
}
callback:callback];
}
RCT_EXPORT_METHOD(reEncryptDbAndKeystore:(NSString *)keyUID
@@ -39,9 +33,26 @@ RCT_EXPORT_METHOD(reEncryptDbAndKeystore:(NSString *)keyUID
#if DEBUG
NSLog(@"reEncryptDbAndKeystore() method called");
#endif
// changes password and re-encrypts keystore
NSString *result = StatusgoChangeDatabasePassword(keyUID, currentPassword, newPassword);
callback(@[result]);
// Construct params into JSON string
NSDictionary *params = @{
@"keyUID": keyUID,
@"oldPassword": currentPassword,
@"newPassword": newPassword
};
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:params options:0 error:&error];
if (error) {
NSLog(@"Error creating JSON: %@", error);
return;
}
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
[StatusBackendClient executeStatusGoRequestWithCallback:@"ChangeDatabasePasswordV2"
body:jsonString
statusgoFunction:^NSString *{
return StatusgoChangeDatabasePasswordV2(jsonString);
}
callback:callback];
}
RCT_EXPORT_METHOD(convertToKeycardAccount:(NSString *)keyUID
@@ -55,47 +66,141 @@ RCT_EXPORT_METHOD(convertToKeycardAccount:(NSString *)keyUID
NSLog(@"convertToKeycardAccount() method called");
#endif
NSURL *multiaccountKeystoreDir = [Utils getKeyStoreDirForKeyUID:keyUID];
StatusgoInitKeystore(multiaccountKeystoreDir.path);
NSString *result = StatusgoConvertToKeycardAccount(accountData, settings, keycardUID, currentPassword, newPassword);
callback(@[result]);
// First initialize keystore
[StatusBackendClient executeStatusGoRequest:@"InitKeystore"
body:multiaccountKeystoreDir.path
statusgoFunction:^NSString *{
return StatusgoInitKeystore(multiaccountKeystoreDir.path);
}];
// Prepare parameters for conversion
NSDictionary *params = @{
@"keyUID": keyUID,
@"account": [NSJSONSerialization JSONObjectWithData:[accountData dataUsingEncoding:NSUTF8StringEncoding]
options:0
error:nil],
@"settings": [NSJSONSerialization JSONObjectWithData:[settings dataUsingEncoding:NSUTF8StringEncoding]
options:0
error:nil],
@"keycardUID": keycardUID,
@"oldPassword": currentPassword,
@"newPassword": newPassword
};
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:params options:0 error:&error];
if (error) {
NSLog(@"Error creating JSON: %@", [error localizedDescription]);
return;
}
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
[StatusBackendClient executeStatusGoRequestWithCallback:@"ConvertToKeycardAccountV2"
body:jsonString
statusgoFunction:^NSString *{
return StatusgoConvertToKeycardAccountV2(jsonString);
}
callback:callback];
}
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(encodeTransfer:(NSString *)to
value:(NSString *)value) {
return StatusgoEncodeTransfer(to,value);
value:(NSString *)value) {
NSDictionary *params = @{
@"to": to,
@"value": value
};
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:params options:0 error:&error];
if (error) {
NSLog(@"Error creating JSON: %@", [error localizedDescription]);
return nil;
}
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
return [StatusBackendClient executeStatusGoRequestWithResult:@"EncodeTransferV2"
body:jsonString
statusgoFunction:^NSString *{
return StatusgoEncodeTransferV2(jsonString);
}];
}
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(encodeFunctionCall:(NSString *)method
paramsJSON:(NSString *)paramsJSON) {
return StatusgoEncodeFunctionCall(method,paramsJSON);
paramsJSON:(NSString *)paramsJSON) {
NSDictionary *params = @{
@"method": method,
@"paramsJSON": paramsJSON
};
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:params options:0 error:&error];
if (error) {
NSLog(@"Error creating JSON: %@", [error localizedDescription]);
return nil;
}
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
return [StatusBackendClient executeStatusGoRequestWithResult:@"EncodeFunctionCallV2"
body:jsonString
statusgoFunction:^NSString *{
return StatusgoEncodeFunctionCallV2(jsonString);
}];
}
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(decodeParameters:(NSString *)decodeParamJSON) {
return StatusgoDecodeParameters(decodeParamJSON);
return [StatusBackendClient executeStatusGoRequestWithResult:@"DecodeParameters"
body:decodeParamJSON
statusgoFunction:^NSString *{
return StatusgoDecodeParameters(decodeParamJSON);
}];
}
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(hexToNumber:(NSString *)hex) {
return StatusgoHexToNumber(hex);
return [StatusBackendClient executeStatusGoRequestWithResult:@"HexToNumber"
body:hex
statusgoFunction:^NSString *{
return StatusgoHexToNumber(hex);
}];
}
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(numberToHex:(NSString *)numString) {
return StatusgoNumberToHex(numString);
return [StatusBackendClient executeStatusGoRequestWithResult:@"NumberToHex"
body:numString
statusgoFunction:^NSString *{
return StatusgoNumberToHex(numString);
}];
}
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(sha3:(NSString *)str) {
return StatusgoSha3(str);
return [StatusBackendClient executeStatusGoRequestWithResult:@"Sha3"
body:str
statusgoFunction:^NSString *{
return StatusgoSha3(str);
}];
}
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(utf8ToHex:(NSString *)str) {
return StatusgoUtf8ToHex(str);
return [StatusBackendClient executeStatusGoRequestWithResult:@"Utf8ToHex"
body:str
statusgoFunction:^NSString *{
return StatusgoUtf8ToHex(str);
}];
}
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(hexToUtf8:(NSString *)str) {
return StatusgoHexToUtf8(str);
return [StatusBackendClient executeStatusGoRequestWithResult:@"HexToUtf8"
body:str
statusgoFunction:^NSString *{
return StatusgoHexToUtf8(str);
}];
}
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(serializeLegacyKey:(NSString *)str) {
return StatusgoSerializeLegacyKey(str);
return [StatusBackendClient executeStatusGoRequestWithResult:@"SerializeLegacyKey"
body:str
statusgoFunction:^NSString *{
return StatusgoSerializeLegacyKey(str);
}];
}
RCT_EXPORT_METHOD(setBlankPreviewFlag:(BOOL *)newValue)
@@ -110,10 +215,14 @@ RCT_EXPORT_METHOD(setBlankPreviewFlag:(BOOL *)newValue)
RCT_EXPORT_METHOD(hashTransaction:(NSString *)txArgsJSON
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"HashTransaction() method called");
NSLog(@"hashTransaction() method called");
#endif
NSString *result = StatusgoHashTransaction(txArgsJSON);
callback(@[result]);
[StatusBackendClient executeStatusGoRequestWithCallback:@"HashTransaction"
body:txArgsJSON
statusgoFunction:^NSString *{
return StatusgoHashTransaction(txArgsJSON);
}
callback:callback];
}
RCT_EXPORT_METHOD(hashMessage:(NSString *)message
@@ -121,8 +230,12 @@ RCT_EXPORT_METHOD(hashMessage:(NSString *)message
#if DEBUG
NSLog(@"hashMessage() method called");
#endif
NSString *result = StatusgoHashMessage(message);
callback(@[result]);
[StatusBackendClient executeStatusGoRequestWithCallback:@"HashMessage"
body:message
statusgoFunction:^NSString *{
return StatusgoHashMessage(message);
}
callback:callback];
}
RCT_EXPORT_METHOD(localPairingPreflightOutboundCheck:(RCTResponseSenderBlock)callback) {
@@ -136,14 +249,34 @@ RCT_EXPORT_METHOD(localPairingPreflightOutboundCheck:(RCTResponseSenderBlock)cal
RCT_EXPORT_METHOD(multiformatDeserializePublicKey:(NSString *)multiCodecKey
base58btc:(NSString *)base58btc
callback:(RCTResponseSenderBlock)callback) {
NSString *result = StatusgoMultiformatDeserializePublicKey(multiCodecKey,base58btc);
callback(@[result]);
NSDictionary *params = @{
@"key": multiCodecKey,
@"outBase": base58btc
};
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:params options:0 error:&error];
if (error) {
NSLog(@"Error creating JSON: %@", error);
return;
}
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
[StatusBackendClient executeStatusGoRequestWithCallback:@"MultiformatDeserializePublicKeyV2"
body:jsonString
statusgoFunction:^NSString *{
return StatusgoMultiformatDeserializePublicKeyV2(jsonString);
}
callback:callback];
}
RCT_EXPORT_METHOD(deserializeAndCompressKey:(NSString *)desktopKey
callback:(RCTResponseSenderBlock)callback) {
NSString *result = StatusgoDeserializeAndCompressKey(desktopKey);
callback(@[result]);
[StatusBackendClient executeStatusGoRequestWithCallback:@"DeserializeAndCompressKey"
body:desktopKey
statusgoFunction:^NSString *{
return StatusgoDeserializeAndCompressKey(desktopKey);
}
callback:callback];
}
RCT_EXPORT_METHOD(hashTypedData:(NSString *)data
@@ -166,13 +299,14 @@ RCT_EXPORT_METHOD(hashTypedDataV4:(NSString *)data
#pragma mark - SignMessage
RCT_EXPORT_METHOD(signMessage:(NSString *)message
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"SignMessage() method called");
#endif
NSString *result = StatusgoSignMessage(message);
callback(@[result]);
RCT_EXPORT_METHOD(signMessage:(NSString *)rpcParams
callback:(RCTResponseSenderBlock)callback) {
[StatusBackendClient executeStatusGoRequestWithCallback:@"SignMessage"
body:rpcParams
statusgoFunction:^NSString *{
return StatusgoSignMessage(rpcParams);
}
callback:callback];
}
#pragma mark - SignTypedData
@@ -4,6 +4,7 @@
#import "Statusgo.h"
#import "Utils.h"
#import "SSZipArchive.h"
#import "StatusBackendClient.h"
@implementation LogManager
@@ -20,9 +21,7 @@ RCT_EXPORT_METHOD(sendLogs:(NSString *)dbJson
#endif
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error = nil;
NSURL *rootUrl =[[fileManager
URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask]
lastObject];
NSURL *rootUrl =[Utils getRootUrl];
NSURL *zipFile = [rootUrl URLByAppendingPathComponent:@"logs.zip"];
[fileManager removeItemAtPath:zipFile.path error:nil];
@@ -88,37 +87,36 @@ RCT_EXPORT_METHOD(initLogging:(BOOL)enabled
NSString *logFilePath = [logDirectory stringByAppendingPathComponent:@"geth.log"];
NSString *logRequestFilePath = [logDirectory stringByAppendingPathComponent:@"requests.log"];
NSMutableDictionary *jsonConfig = [NSMutableDictionary dictionary];
jsonConfig[@"Enabled"] = @(enabled);
jsonConfig[@"MobileSystem"] = @(mobileSystem);
jsonConfig[@"Level"] = logLevel;
jsonConfig[@"File"] = logFilePath;
jsonConfig[@"LogRequestGo"] = @(logRequestGo);
jsonConfig[@"LogRequestFile"] = logRequestFilePath;
NSError *error = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonConfig options:0 error:&error];
NSDictionary *config = @{
@"Enabled": @(enabled),
@"MobileSystem": @(mobileSystem),
@"Level": logLevel,
@"File": logFilePath,
@"LogRequestGo": @(logRequestGo),
@"LogRequestFile": logRequestFilePath
};
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:config options:0 error:&error];
if (error) {
// Handle JSON serialization error
callback(@[error.localizedDescription]);
NSLog(@"Error creating JSON: %@", [error localizedDescription]);
return;
}
NSString *configJson = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
NSString *config = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
// Call your native logging initialization method here
NSString *initResult = StatusgoInitLogging(config);
callback(@[initResult]);
[StatusBackendClient executeStatusGoRequestWithCallback:@"InitLogging"
body:configJson
statusgoFunction:^NSString *{
return StatusgoInitLogging(configJson);
}
callback:callback];
}
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(logFileDirectory) {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *rootUrl =[[fileManager
URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask]
lastObject];
NSURL *rootUrl = [Utils getRootUrl];
return rootUrl.path;
}
@end
@@ -3,14 +3,18 @@
#import "React/RCTEventDispatcher.h"
#import "Statusgo.h"
#import "Utils.h"
#import "StatusBackendClient.h"
@implementation NetworkManager
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(startSearchForLocalPairingPeers:(RCTResponseSenderBlock)callback) {
NSString *result = StatusgoStartSearchForLocalPairingPeers();
callback(@[result]);
[StatusBackendClient executeStatusGoRequestWithCallback:@"StartSearchForLocalPairingPeers"
body:@""
statusgoFunction:^NSString *{
return StatusgoStartSearchForLocalPairingPeers();
}
callback:callback];
}
RCT_EXPORT_METHOD(getConnectionStringForBootstrappingAnotherDevice:(NSString *)configJSON
@@ -19,6 +23,11 @@ RCT_EXPORT_METHOD(getConnectionStringForBootstrappingAnotherDevice:(NSString *)c
NSData *configData = [configJSON dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSMutableDictionary *configDict = [NSJSONSerialization JSONObjectWithData:configData options:NSJSONReadingMutableContainers error:&error];
if (error) {
NSLog(@"Error parsing JSON: %@", error);
return;
}
NSMutableDictionary *senderConfig = configDict[@"senderConfig"];
NSString *keyUID = senderConfig[@"keyUID"];
NSURL *multiaccountKeystoreDir = [Utils getKeyStoreDirForKeyUID:keyUID];
@@ -27,16 +36,43 @@ RCT_EXPORT_METHOD(getConnectionStringForBootstrappingAnotherDevice:(NSString *)c
[senderConfig setValue:keystoreDir forKey:@"keystorePath"];
NSString *modifiedConfigJSON = [Utils jsonStringWithPrettyPrint:NO fromDictionary:configDict];
NSString *result = StatusgoGetConnectionStringForBootstrappingAnotherDevice(modifiedConfigJSON);
callback(@[result]);
[StatusBackendClient executeStatusGoRequestWithCallback:@"GetConnectionStringForBootstrappingAnotherDevice"
body:modifiedConfigJSON
statusgoFunction:^NSString *{
return StatusgoGetConnectionStringForBootstrappingAnotherDevice(modifiedConfigJSON);
}
callback:callback];
}
RCT_EXPORT_METHOD(inputConnectionStringForBootstrapping:(NSString *)cs
configJSON:(NSString *)configJSON
callback:(RCTResponseSenderBlock)callback) {
NSString *result = StatusgoInputConnectionStringForBootstrapping(cs, configJSON);
callback(@[result]);
NSData *configData = [configJSON dataUsingEncoding:NSUTF8StringEncoding];
NSError *jsonError;
NSDictionary *configDict = [NSJSONSerialization JSONObjectWithData:configData options:0 error:&jsonError];
if (jsonError) {
NSLog(@"Error parsing JSON: %@", jsonError);
return;
}
NSDictionary *params = @{
@"connectionString": cs,
@"receiverClientConfig": configDict
};
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:params options:0 error:&error];
if (error) {
NSLog(@"Error creating JSON: %@", error);
return;
}
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
[StatusBackendClient executeStatusGoRequestWithCallback:@"InputConnectionStringForBootstrappingV2"
body:jsonString
statusgoFunction:^NSString *{
return StatusgoInputConnectionStringForBootstrappingV2(jsonString);
}
callback:callback];
}
RCT_EXPORT_METHOD(sendTransactionWithSignature:(NSString *)txArgsJSON
@@ -45,8 +81,12 @@ RCT_EXPORT_METHOD(sendTransactionWithSignature:(NSString *)txArgsJSON
#if DEBUG
NSLog(@"sendTransactionWithSignature() method called");
#endif
NSString *result = StatusgoSendTransactionWithSignature(txArgsJSON, signature);
callback(@[result]);
[StatusBackendClient executeStatusGoRequestWithCallback:@"SendTransactionWithSignature"
body:txArgsJSON
statusgoFunction:^NSString *{
return StatusgoSendTransactionWithSignature(txArgsJSON, signature);
}
callback:callback];
}
#pragma mark - SendTransaction
@@ -55,30 +95,53 @@ RCT_EXPORT_METHOD(sendTransaction:(NSString *)txArgsJSON
password:(NSString *)password
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"SendTransaction() method called");
NSLog(@"SendTransactionV2() method called");
#endif
NSString *result = StatusgoSendTransaction(txArgsJSON, password);
callback(@[result]);
NSData *txArgsData = [txArgsJSON dataUsingEncoding:NSUTF8StringEncoding];
NSError *jsonError;
NSDictionary *txArgsDict = [NSJSONSerialization JSONObjectWithData:txArgsData options:0 error:&jsonError];
if (jsonError) {
NSLog(@"Error parsing JSON: %@", jsonError);
return;
}
NSDictionary *params = @{
@"txArgs": txArgsDict,
@"password": password
};
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:params options:0 error:&error];
if (error) {
NSLog(@"Error creating JSON: %@", error);
return;
}
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
[StatusBackendClient executeStatusGoRequestWithCallback:@"SendTransactionV2"
body:jsonString
statusgoFunction:^NSString *{
return StatusgoSendTransactionV2(jsonString);
}
callback:callback];
}
RCT_EXPORT_METHOD(callRPC:(NSString *)payload
callback:(RCTResponseSenderBlock)callback) {
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *result = StatusgoCallRPC(payload);
dispatch_async(dispatch_get_main_queue(), ^{
callback(@[result]);
});
});
[StatusBackendClient executeStatusGoRequestWithCallback:@"CallRPC"
body:payload
statusgoFunction:^NSString *{
return StatusgoCallRPC(payload);
}
callback:callback];
}
RCT_EXPORT_METHOD(callPrivateRPC:(NSString *)payload
callback:(RCTResponseSenderBlock)callback) {
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *result = StatusgoCallPrivateRPC(payload);
dispatch_async(dispatch_get_main_queue(), ^{
callback(@[result]);
});
});
[StatusBackendClient executeStatusGoRequestWithCallback:@"CallPrivateRPC"
body:payload
statusgoFunction:^NSString *{
return StatusgoCallPrivateRPC(payload);
}
callback:callback];
}
#pragma mark - Recover
@@ -88,8 +151,12 @@ RCT_EXPORT_METHOD(recover:(NSString *)message
#if DEBUG
NSLog(@"Recover() method called");
#endif
NSString *result = StatusgoRecover(message);
callback(@[result]);
[StatusBackendClient executeStatusGoRequestWithCallback:@"Recover"
body:message
statusgoFunction:^NSString *{
return StatusgoRecover(message);
}
callback:callback];
}
RCT_EXPORT_METHOD(getConnectionStringForExportingKeypairsKeystores:(NSString *)configJSON
@@ -106,8 +173,12 @@ RCT_EXPORT_METHOD(getConnectionStringForExportingKeypairsKeystores:(NSString *)c
[senderConfig setValue:keystoreDir forKey:@"keystorePath"];
NSString *modifiedConfigJSON = [Utils jsonStringWithPrettyPrint:NO fromDictionary:configDict];
NSString *result = StatusgoGetConnectionStringForExportingKeypairsKeystores(modifiedConfigJSON);
callback(@[result]);
[StatusBackendClient executeStatusGoRequestWithCallback:@"GetConnectionStringForExportingKeypairsKeystores"
body:modifiedConfigJSON
statusgoFunction:^NSString *{
return StatusgoGetConnectionStringForExportingKeypairsKeystores(modifiedConfigJSON);
}
callback:callback];
}
RCT_EXPORT_METHOD(inputConnectionStringForImportingKeypairsKeystores:(NSString *)cs
@@ -119,14 +190,25 @@ RCT_EXPORT_METHOD(inputConnectionStringForImportingKeypairsKeystores:(NSString *
NSMutableDictionary *configDict = [NSJSONSerialization JSONObjectWithData:configData options:NSJSONReadingMutableContainers error:&error];
NSMutableDictionary *receiverConfig = configDict[@"receiverConfig"];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *rootUrl =[[fileManager URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *rootUrl =[Utils getRootUrl];
NSURL *multiaccountKeystoreDir = [rootUrl URLByAppendingPathComponent:@"keystore"];
NSString *keystoreDir = multiaccountKeystoreDir.path;
[receiverConfig setValue:keystoreDir forKey:@"keystorePath"];
NSString *modifiedConfigJSON = [Utils jsonStringWithPrettyPrint:NO fromDictionary:configDict];
NSString *result = StatusgoInputConnectionStringForImportingKeypairsKeystores(cs, modifiedConfigJSON);
callback(@[result]);
NSDictionary *params = @{
@"connectionString": cs,
@"keystoreFilesReceiverClientConfig": modifiedConfigJSON
};
NSString *paramsJSON = [Utils jsonStringWithPrettyPrint:NO fromDictionary:params];
[StatusBackendClient executeStatusGoRequestWithCallback:@"InputConnectionStringForImportingKeypairsKeystoresV2"
body:paramsJSON
statusgoFunction:^NSString *{
return StatusgoInputConnectionStringForImportingKeypairsKeystoresV2(paramsJSON);
}
callback:callback];
}
@end
@@ -5,5 +5,6 @@
#import "RCTLog.h"
@interface Status : NSObject <RCTBridgeModule, StatusgoSignalHandler>
+ (Status *)sharedInstance;
- (void)handleSignal:(NSString *)signal;
@end
@@ -2,13 +2,22 @@
#import "React/RCTBridge.h"
#import "React/RCTEventDispatcher.h"
#import "Statusgo.h"
#import "StatusBackendClient.h"
#import "Utils.h"
static RCTBridge *bridge;
@implementation Status
+ (Status *)sharedInstance {
static Status *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
self = [super init];
if (!self) {
@@ -73,74 +82,17 @@ RCT_EXPORT_METHOD(deleteImportedKey:(NSString *)keyUID
password:(NSString *)password
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"DeleteImportedKey() method called");
NSLog(@"DeleteImportedKeyV2() method called");
#endif
NSURL *multiaccountKeystoreDir = [Utils getKeyStoreDirForKeyUID:keyUID];
NSString *result = StatusgoDeleteImportedKey(address, password, multiaccountKeystoreDir.path);
callback(@[result]);
}
RCT_EXPORT_METHOD(multiAccountGenerateAndDeriveAddresses:(NSString *)json
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"MultiAccountGenerateAndDeriveAddresses() method called");
#endif
NSString *result = StatusgoMultiAccountGenerateAndDeriveAddresses(json);
callback(@[result]);
}
RCT_EXPORT_METHOD(multiAccountStoreAccount:(NSString *)json
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"MultiAccountStoreAccount() method called");
#endif
NSString *result = StatusgoMultiAccountStoreAccount(json);
callback(@[result]);
}
RCT_EXPORT_METHOD(multiAccountLoadAccount:(NSString *)json
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"MultiAccountLoadAccount() method called");
#endif
NSString *result = StatusgoMultiAccountLoadAccount(json);
callback(@[result]);
}
RCT_EXPORT_METHOD(multiAccountStoreDerived:(NSString *)json
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"MultiAccountStoreDerived() method called");
#endif
NSString *result = StatusgoMultiAccountStoreDerivedAccounts(json);
callback(@[result]);
}
RCT_EXPORT_METHOD(multiAccountImportPrivateKey:(NSString *)json
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"MultiAccountImportPrivateKey() method called");
#endif
NSString *result = StatusgoMultiAccountImportPrivateKey(json);
callback(@[result]);
}
RCT_EXPORT_METHOD(multiAccountImportMnemonic:(NSString *)json
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"MultiAccountImportMnemonic() method called");
#endif
NSString *result = StatusgoMultiAccountImportMnemonic(json);
callback(@[result]);
}
RCT_EXPORT_METHOD(multiAccountDeriveAddresses:(NSString *)json
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"MultiAccountDeriveAddresses() method called");
#endif
NSString *result = StatusgoMultiAccountDeriveAddresses(json);
callback(@[result]);
NSString *jsonParams = [NSString stringWithFormat:@"{\"address\":\"%@\",\"password\":\"%@\",\"keyStoreDir\":\"%@\"}",
address, password, multiaccountKeystoreDir.path];
[StatusBackendClient executeStatusGoRequestWithCallback:@"DeleteImportedKeyV2"
body:jsonParams
statusgoFunction:^NSString *{
return StatusgoDeleteImportedKeyV2(jsonParams);
}
callback:callback];
}
#pragma mark - GetNodeConfig
@@ -149,12 +101,20 @@ RCT_EXPORT_METHOD(getNodeConfig:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"GetNodeConfig() method called");
#endif
NSString *result = StatusgoGetNodeConfig();
callback(@[result]);
[StatusBackendClient executeStatusGoRequestWithCallback:@"GetNodeConfig"
body:@""
statusgoFunction:^NSString *{
return StatusgoGetNodeConfig();
}
callback:callback];
}
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(fleets) {
return StatusgoFleets();
return [StatusBackendClient executeStatusGoRequestWithResult:@"Fleets"
body:@""
statusgoFunction:^NSString *{
return StatusgoFleets();
}];
}
RCT_EXPORT_METHOD(closeApplication) {
@@ -166,14 +126,55 @@ RCT_EXPORT_METHOD(connectionChange:(NSString *)type
#if DEBUG
NSLog(@"ConnectionChange() method called");
#endif
StatusgoConnectionChange(type, isExpensive ? 1 : 0);
NSDictionary *params = @{
@"type": type,
@"expensive": @(isExpensive)
};
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:params
options:0
error:&error];
if (error) {
NSLog(@"Error creating JSON: %@", error);
return;
}
if (jsonData) {
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
[StatusBackendClient executeStatusGoRequest:@"ConnectionChangeV2"
body:jsonString
statusgoFunction:^NSString *{
return StatusgoConnectionChangeV2(jsonString);
}];
} else {
NSLog(@"Failed to create JSON data");
}
}
RCT_EXPORT_METHOD(appStateChange:(NSString *)type) {
RCT_EXPORT_METHOD(appStateChange:(NSString *)state) {
#if DEBUG
NSLog(@"AppStateChange() method called");
#endif
StatusgoAppStateChange(type);
NSDictionary *params = @{@"state": state};
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:params
options:0
error:&error];
if (error) {
NSLog(@"Error creating JSON: %@", error);
return;
}
if (jsonData) {
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
[StatusBackendClient executeStatusGoRequest:@"AppStateChangeV2"
body:jsonString
statusgoFunction:^NSString *{
return StatusgoAppStateChangeV2(jsonString);
}];
} else {
NSLog(@"Failed to create JSON data");
}
}
RCT_EXPORT_METHOD(addCentralizedMetric:(NSString *)request
@@ -181,8 +182,12 @@ RCT_EXPORT_METHOD(addCentralizedMetric:(NSString *)request
#if DEBUG
NSLog(@"addCentralizedMetric() method called");
#endif
NSString *result = StatusgoAddCentralizedMetric(request);
callback(@[result]);
[StatusBackendClient executeStatusGoRequestWithCallback:@"AddCentralizedMetric"
body:request
statusgoFunction:^NSString *{
return StatusgoAddCentralizedMetric(request);
}
callback:callback];
}
RCT_EXPORT_METHOD(toggleCentralizedMetrics:(NSString *)request
@@ -190,15 +195,23 @@ RCT_EXPORT_METHOD(toggleCentralizedMetrics:(NSString *)request
#if DEBUG
NSLog(@"toggleCentralizedMetrics() method called");
#endif
NSString *result = StatusgoToggleCentralizedMetrics(request);
callback(@[result]);
[StatusBackendClient executeStatusGoRequestWithCallback:@"ToggleCentralizedMetrics"
body:request
statusgoFunction:^NSString *{
return StatusgoToggleCentralizedMetrics(request);
}
callback:callback];
}
RCT_EXPORT_METHOD(startLocalNotifications) {
#if DEBUG
NSLog(@"StartLocalNotifications() method called");
#endif
StatusgoStartLocalNotifications();
[StatusBackendClient executeStatusGoRequest:@"StartLocalNotifications"
body:@""
statusgoFunction:^NSString *{
return StatusgoStartLocalNotifications();
}];
}
#pragma mark - deviceinfo
@@ -0,0 +1,31 @@
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
@interface StatusBackendClient : NSObject <RCTBridgeModule>
@property (nonatomic) BOOL serverEnabled;
@property (nonatomic, strong) NSString *statusGoEndpoint;
@property (nonatomic, strong) NSString *signalEndpoint;
@property (nonatomic, strong) NSString *rootDataDir;
@property (nonatomic, strong) NSURLSessionWebSocketTask *webSocket;
// Add sharedInstance class method declaration
+ (instancetype)sharedInstance;
- (void)request:(NSString *)endpoint
body:(NSString *)body
callback:(void (^)(NSString *response, NSError *error))callback;
+ (void)executeStatusGoRequest:(NSString *)endpoint
body:(NSString *)body
statusgoFunction:(NSString * (^)(void))statusgoFunction;
+ (void)executeStatusGoRequestWithCallback:(NSString *)endpoint
body:(NSString *)body
statusgoFunction:(NSString * (^)(void))statusgoFunction
callback:(RCTResponseSenderBlock)callback;
+ (NSString *)executeStatusGoRequestWithResult:(NSString *)endpoint
body:(NSString *)body
statusgoFunction:(NSString * (^)(void))statusgoFunction;
@end
@@ -0,0 +1,246 @@
#import "StatusBackendClient.h"
#import "RCTStatus.h"
#import "Utils.h"
@implementation StatusBackendClient {
NSURLSessionWebSocketTask *_webSocket;
BOOL _serverEnabled;
NSString *_statusGoEndpoint;
NSString *_signalEndpoint;
NSString *_rootDataDir;
}
RCT_EXPORT_MODULE();
+ (BOOL)requiresMainQueueSetup {
return YES;
}
+ (instancetype)allocWithZone:(NSZone *)zone {
static StatusBackendClient *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [super allocWithZone:zone];
});
return sharedInstance;
}
+ (instancetype)sharedInstance {
return [[self alloc] init];
}
- (instancetype)init {
static StatusBackendClient *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [super init];
if (sharedInstance) {
sharedInstance->_serverEnabled = NO;
sharedInstance->_statusGoEndpoint = nil;
sharedInstance->_signalEndpoint = nil;
sharedInstance->_rootDataDir = nil;
sharedInstance->_webSocket = nil;
}
});
return sharedInstance;
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (BOOL)serverEnabled {
return _serverEnabled;
}
- (NSString *)statusGoEndpoint {
return _statusGoEndpoint;
}
- (NSString *)signalEndpoint {
return _signalEndpoint;
}
- (NSString *)rootDataDir {
return _rootDataDir;
}
- (void)connectWebSocket {
if (!self.serverEnabled || !self.signalEndpoint) {
return;
}
NSString *fullUrl = [NSString stringWithFormat:@"%@", self.signalEndpoint];
NSURL *url = [NSURL URLWithString:fullUrl];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
self.webSocket = [session webSocketTaskWithURL:url];
[self.webSocket resume];
[self receiveMessage];
}
- (void)receiveMessage {
__weak typeof(self) weakSelf = self;
[self.webSocket receiveMessageWithCompletionHandler:^(NSURLSessionWebSocketMessage *message, NSError *error) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf || !strongSelf.webSocket) {
return;
}
if (error) {
NSLog(@"WebSocket error: %@", error);
// Attempt to reconnect after error
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[strongSelf connectWebSocket];
});
return;
}
if (message) {
if (message.type == NSURLSessionWebSocketMessageTypeString && message.string) {
[[Status sharedInstance] handleSignal:message.string];
}
// Continue receiving messages only if the connection is still active
if (strongSelf.webSocket) {
[strongSelf receiveMessage];
}
}
}];
}
- (void)disconnectWebSocket {
if (self.webSocket) {
[self.webSocket cancel];
self.webSocket = nil;
}
}
- (void)request:(NSString *)endpoint
body:(NSString *)body
callback:(void (^)(NSString *response, NSError *error))callback {
NSString *fullUrlString = [NSString stringWithFormat:@"%@%@", self.statusGoEndpoint, endpoint];
NSURL *url = [NSURL URLWithString:fullUrlString];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
request.HTTPMethod = @"POST";
[request setValue:@"application/json; charset=utf-8" forHTTPHeaderField:@"Content-Type"];
NSData *bodyData = [body dataUsingEncoding:NSUTF8StringEncoding];
request.HTTPBody = bodyData;
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(@"request error: %@", error);
callback(nil, error);
return;
}
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
callback(responseString, nil);
}];
[task resume];
}
RCT_EXPORT_METHOD(configStatusBackendServer:(BOOL)serverEnabled
statusGoEndpoint:(NSString *)statusGoEndpoint
signalEndpoint:(NSString *)signalEndpoint
rootDataDir:(NSString *)rootDataDir) {
[self configureWithEnabled:serverEnabled
statusGoEndpoint:statusGoEndpoint
signalEndpoint:signalEndpoint
rootDataDir:rootDataDir];
}
- (void)configureWithEnabled:(BOOL)serverEnabled
statusGoEndpoint:(NSString *)statusGoEndpoint
signalEndpoint:(NSString *)signalEndpoint
rootDataDir:(NSString *)rootDataDir {
_serverEnabled = serverEnabled;
if (serverEnabled) {
_statusGoEndpoint = statusGoEndpoint;
_signalEndpoint = signalEndpoint;
_rootDataDir = rootDataDir;
[self connectWebSocket];
} else {
[self disconnectWebSocket];
_statusGoEndpoint = nil;
_signalEndpoint = nil;
_rootDataDir = nil;
}
}
+ (void)executeStatusGoRequest:(NSString *)endpoint
body:(NSString *)body
statusgoFunction:(NSString * (^)(void))statusgoFunction {
StatusBackendClient *client = [StatusBackendClient sharedInstance];
if (client.serverEnabled) {
[client request:endpoint body:body callback:^(NSString *response, NSError *error) {
[Utils handleStatusGoResponse:response source:endpoint error:error];
}];
} else {
NSString *result = statusgoFunction();
[Utils handleStatusGoResponse:result source:endpoint error:nil];
}
}
+ (void)executeStatusGoRequestWithCallback:(NSString *)endpoint
body:(NSString *)body
statusgoFunction:(NSString * (^)(void))statusgoFunction
callback:(RCTResponseSenderBlock)callback {
StatusBackendClient *client = [StatusBackendClient sharedInstance];
if (client.serverEnabled) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[client request:endpoint body:body callback:^(NSString *response, NSError *error) {
if (error) {
NSLog(@"request to %@ failed: %@", endpoint, error);
if (callback) {
callback(@[@(NO)]);
}
} else {
if (callback) {
callback(@[response]);
}
}
}];
});
} else {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *result = statusgoFunction();
if (callback) {
callback(@[result]);
}
});
}
}
+ (NSString *)executeStatusGoRequestWithResult:(NSString *)endpoint
body:(NSString *)body
statusgoFunction:(NSString * (^)(void))statusgoFunction {
StatusBackendClient *client = [StatusBackendClient sharedInstance];
if (client.serverEnabled) {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSString *resultString = @"";
[client request:endpoint body:body callback:^(NSString *response, NSError *error) {
if (error) {
NSLog(@"request to %@ failed: %@", endpoint, error);
resultString = @"";
} else {
resultString = response;
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return resultString;
} else {
return statusgoFunction();
}
}
@end
@@ -6,11 +6,13 @@
@interface Utils : NSObject <RCTBridgeModule>
+ (NSURL *)getRootUrl;
+ (NSString *)jsonStringWithPrettyPrint:(BOOL)prettyPrint fromDictionary:(NSDictionary *)dictionary;
+ (NSString *)jsonStringWithPrettyPrint:(BOOL)prettyPrint fromArray:(NSArray *)array;
+ (NSURL *)getKeyStoreDirForKeyUID:(NSString *)keyUID;
+ (NSString *)getExportDbFilePath;
+ (NSString *)getKeyUID:(NSString *)jsonString;
+ (void)migrateKeystore:(NSString *)accountData password:(NSString *)password;
+ (void)handleStatusGoResponse:(NSString *)response source:(NSString *)source error:(NSError *)error;
@end
@@ -3,11 +3,28 @@
#import "React/RCTEventDispatcher.h"
#import "Statusgo.h"
#import "Utils.h"
#import "StatusBackendClient.h"
@implementation Utils
RCT_EXPORT_MODULE();
#pragma mark - Private Methods
+ (NSURL *)getRootUrl {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *rootUrl;
StatusBackendClient *client = [StatusBackendClient sharedInstance];
if (client.serverEnabled && client.rootDataDir) {
rootUrl = [NSURL fileURLWithPath:client.rootDataDir];
} else {
rootUrl = [[fileManager URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask] lastObject];
}
return rootUrl;
}
+ (NSString *)jsonStringWithPrettyPrint:(BOOL)prettyPrint fromDictionary:(NSDictionary *)dictionary {
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dictionary
@@ -38,7 +55,7 @@ RCT_EXPORT_MODULE();
+ (NSURL *)getKeyStoreDirForKeyUID:(NSString *)keyUID {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *rootUrl = [[fileManager URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *rootUrl = [self getRootUrl];
NSURL *oldKeystoreDir = [rootUrl URLByAppendingPathComponent:@"keystore"];
NSURL *multiaccountKeystoreDir = [oldKeystoreDir URLByAppendingPathComponent:keyUID];
@@ -57,6 +74,11 @@ RCT_EXPORT_MODULE();
}
+ (NSString *) getExportDbFilePath {
StatusBackendClient *client = [StatusBackendClient sharedInstance];
if (client.serverEnabled && client.rootDataDir) {
return [client.rootDataDir stringByAppendingPathComponent:@"export.db"];
}
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"export.db"];
NSFileManager *fileManager = [NSFileManager defaultManager];
@@ -70,17 +92,36 @@ RCT_EXPORT_MODULE();
+ (void) migrateKeystore:(NSString *)accountData
password:(NSString *)password {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *rootUrl =[[fileManager
URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask]
lastObject];
NSURL *rootUrl =[self getRootUrl];
NSURL *keyUID = [self getKeyStoreDirForKeyUID:accountData];
NSData *jsonData = [accountData dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *accountJson = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
if (error) {
NSLog(@"Error parsing accountData: %@", error);
return;
}
NSString *keyUID = [self getKeyUID:accountData];
NSURL *oldKeystoreDir = [rootUrl URLByAppendingPathComponent:@"keystore"];
NSURL *multiaccountKeystoreDir = [self getKeyStoreDirForKeyUID:keyUID.path];
NSURL *multiaccountKeystoreDir = [self getKeyStoreDirForKeyUID:keyUID];
NSArray *keys = [fileManager contentsOfDirectoryAtPath:multiaccountKeystoreDir.path error:nil];
if (keys.count == 0) {
NSString *migrationResult = StatusgoMigrateKeyStoreDir(accountData, password, oldKeystoreDir.path, multiaccountKeystoreDir.path);
NSDictionary *params = @{
@"account": accountJson,
@"password": password,
@"oldDir": oldKeystoreDir.path,
@"newDir": multiaccountKeystoreDir.path
};
NSData *paramsJsonData = [NSJSONSerialization dataWithJSONObject:params options:0 error:&error];
if (error) {
NSLog(@"Error creating params JSON: %@", error);
return;
}
NSString *jsonString = [[NSString alloc] initWithData:paramsJsonData encoding:NSUTF8StringEncoding];
NSString *migrationResult = StatusgoMigrateKeyStoreDirV2(jsonString);
NSLog(@"keystore migration result %@", migrationResult);
NSString *initKeystoreResult = StatusgoInitKeystore(multiaccountKeystoreDir.path);
@@ -89,18 +130,19 @@ RCT_EXPORT_MODULE();
}
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(backupDisabledDataDir) {
StatusBackendClient *client = [StatusBackendClient sharedInstance];
if (client.serverEnabled && client.rootDataDir) {
return client.rootDataDir;
}
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *rootUrl =[[fileManager
URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask]
lastObject];
NSURL *rootUrl = [Utils getRootUrl];
return rootUrl.path;
}
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(keystoreDir) {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *rootUrl =[[fileManager
URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask]
lastObject];
NSURL *rootUrl =[Utils getRootUrl];
NSURL *commonKeystoreDir = [rootUrl URLByAppendingPathComponent:@"keystore"];
@@ -110,9 +152,17 @@ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(keystoreDir) {
RCT_EXPORT_METHOD(validateMnemonic:(NSString *)seed
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"validateMnemonic() method called");
NSLog(@"validateMnemonicV2() method called");
#endif
NSString *result = StatusgoValidateMnemonic(seed);
NSDictionary *params = @{@"mnemonic": seed};
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:params options:0 error:&error];
if (error) {
NSLog(@"Error creating JSON: %@", error);
return;
}
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
NSString *result = StatusgoValidateMnemonicV2(jsonString);
callback(@[result]);
}
@@ -132,4 +182,17 @@ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(validateConnectionString:(NSString *)cs)
return StatusgoValidateConnectionString(cs);
}
+ (void)handleStatusGoResponse:(NSString *)response source:(NSString *)source error:(NSError *)error {
if (error) {
NSLog(@"%@ failed: %@", source, error);
return;
}
if ([response hasPrefix:@"{\"error\":\"\""]) {
NSLog(@"%@ success: %@", source, response);
} else {
NSLog(@"%@ failed: %@", source, response);
}
}
@end
+1 -1
View File
@@ -56,7 +56,7 @@ in {
yarn = super.yarn.override { nodejs = super.nodejs_20; };
openjdk = super.openjdk17_headless;
xcodeWrapper = callPackage ./pkgs/xcodeenv/compose-xcodewrapper.nix { } {
versions = ["15.1" "15.2" "15.3" "15.4"];
versions = ["15.1" "15.2" "15.3" "15.4" "16.0"];
};
go = super.go_1_21;
clang = super.clang_15;
+1 -1
View File
@@ -51,7 +51,7 @@
"react-native-get-random-values": "^1.11.0",
"react-native-gifted-charts": "^1.3.2",
"react-native-hole-view": "^3.0.0-alpha4",
"react-native-image-crop-picker": "0.40.0",
"react-native-image-crop-picker": "0.41.2",
"react-native-image-resizer": "^1.2.3",
"react-native-keychain": "8.1.2",
"react-native-linear-gradient": "3.0.0-alpha.1",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

+55 -57
View File
@@ -51,61 +51,60 @@
;; the target files (a.k.a hot reload). When false, you can manually
;; reload by calling `shadow.cljs.devtools.api/watch-compile-all!`.
:devtools {:autobuild #shadow/env ["SHADOW_AUTOBUILD_ENABLED" :default true :as :bool]}
:dev {:devtools {:before-load-async status-im.setup.hot-reload/before-reload
:after-load-async status-im.setup.hot-reload/reload
:build-notify status-im.setup.hot-reload/build-notify
:preloads [;; The official recommendation is to
;; load the debugger preload first.
flow-storm.api
re-frisk-remote.preload
status-im.setup.schema-preload
;; In order to use component test helpers in the REPL we
;; need to preload namespaces that are not normally required
;; by production code, such as
;; @testing-library/react-native.
test-helpers.component]}
:closure-defines
{status-im.config/POKT_TOKEN #shadow/env "POKT_TOKEN"
status-im.config/INFURA_TOKEN #shadow/env "INFURA_TOKEN"
status-im.config/STATUS_BUILD_PROXY_USER #shadow/env "STATUS_BUILD_PROXY_USER"
status-im.config/STATUS_BUILD_PROXY_PASSWORD #shadow/env "STATUS_BUILD_PROXY_PASSWORD"
status-im.config/MIXPANEL_APP_ID #shadow/env "MIXPANEL_APP_ID"
status-im.config/MIXPANEL_TOKEN #shadow/env "MIXPANEL_TOKEN"
status-im.config/OPENSEA_API_KEY #shadow/env "OPENSEA_API_KEY"
status-im.config/RARIBLE_MAINNET_API_KEY #shadow/env "RARIBLE_MAINNET_API_KEY"
status-im.config/RARIBLE_TESTNET_API_KEY #shadow/env "RARIBLE_TESTNET_API_KEY"
status-im.config/ALCHEMY_ETHEREUM_MAINNET_TOKEN #shadow/env "ALCHEMY_ETHEREUM_MAINNET_TOKEN"
status-im.config/ALCHEMY_ETHEREUM_GOERLI_TOKEN #shadow/env "ALCHEMY_ETHEREUM_GOERLI_TOKEN"
status-im.config/ALCHEMY_ETHEREUM_SEPOLIA_TOKEN #shadow/env "ALCHEMY_ETHEREUM_SEPOLIA_TOKEN"
status-im.config/ALCHEMY_ARBITRUM_MAINNET_TOKEN #shadow/env "ALCHEMY_ARBITRUM_MAINNET_TOKEN"
status-im.config/ALCHEMY_ARBITRUM_GOERLI_TOKEN #shadow/env "ALCHEMY_ARBITRUM_GOERLI_TOKEN"
status-im.config/ALCHEMY_ARBITRUM_SEPOLIA_TOKEN #shadow/env "ALCHEMY_ARBITRUM_SEPOLIA_TOKEN"
status-im.config/ALCHEMY_OPTIMISM_MAINNET_TOKEN #shadow/env "ALCHEMY_OPTIMISM_MAINNET_TOKEN"
status-im.config/ALCHEMY_OPTIMISM_GOERLI_TOKEN #shadow/env "ALCHEMY_OPTIMISM_GOERLI_TOKEN"
status-im.config/ALCHEMY_OPTIMISM_SEPOLIA_TOKEN #shadow/env "ALCHEMY_OPTIMISM_SEPOLIA_TOKEN"
status-im.config/STATUS_BACKEND_ENABLED #shadow/env "STATUS_BACKEND_ENABLED"
;; The dir <STATUS_BACKEND_STORAGE_DIR>/data musts exist before running the app.
status-im.config/STATUS_BACKEND_STORAGE_DIR #shadow/env "STATUS_BACKEND_STORAGE_DIR"
status-im.config/STATUS_BACKEND_PORT #shadow/env "STATUS_BACKEND_PORT"
status-im.config/STATUS_BACKEND_ADDRESS #shadow/env "STATUS_BACKEND_ADDRESS"}
:compiler-options {:output-feature-set :es5
;; We disable `:fn-deprecated` warnings because we
;; are managing deprecation via clj-kondo and we
;; don't want the terminal output to be littered
;; with warnings on every code reload.
:warnings {:fn-deprecated false}
:closure-defines {re-frame.trace/trace-enabled? true}
:source-map false
;; This seems to be necessary while using the REPL,
;; otherwise sometimes you'll get weird errors when
;; instrumenting functions.
:static-fns false
:infer-externs true
:reader-features #{:mobile}}
;; if you want to use a real device, set your local ip
;; in the SHADOW_HOST env variable to make sure that
;; it will use the right interface
:local-ip #shadow/env "SHADOW_HOST"}
:dev
{:devtools {:before-load-async status-im.setup.hot-reload/before-reload
:after-load-async status-im.setup.hot-reload/reload
:build-notify status-im.setup.hot-reload/build-notify
:preloads [;; The official recommendation is to
;; load the debugger preload first.
flow-storm.api
re-frisk-remote.preload
status-im.setup.schema-preload
;; In order to use component test helpers in the REPL we need to
;; preload namespaces that are not normally required by
;; production code, such as @testing-library/react-native.
test-helpers.component]}
:closure-defines
{status-im.config/POKT_TOKEN #shadow/env "POKT_TOKEN"
status-im.config/INFURA_TOKEN #shadow/env "INFURA_TOKEN"
status-im.config/STATUS_BUILD_PROXY_USER #shadow/env "STATUS_BUILD_PROXY_USER"
status-im.config/STATUS_BUILD_PROXY_PASSWORD #shadow/env "STATUS_BUILD_PROXY_PASSWORD"
status-im.config/STATUS_BACKEND_SERVER_ENABLED #shadow/env "STATUS_BACKEND_SERVER_ENABLED"
status-im.config/STATUS_BACKEND_SERVER_HOST #shadow/env "STATUS_BACKEND_SERVER_HOST"
status-im.config/STATUS_BACKEND_SERVER_ROOT_DATA_DIR #shadow/env
"STATUS_BACKEND_SERVER_ROOT_DATA_DIR"
status-im.config/MIXPANEL_APP_ID #shadow/env "MIXPANEL_APP_ID"
status-im.config/MIXPANEL_TOKEN #shadow/env "MIXPANEL_TOKEN"
status-im.config/OPENSEA_API_KEY #shadow/env "OPENSEA_API_KEY"
status-im.config/RARIBLE_MAINNET_API_KEY #shadow/env "RARIBLE_MAINNET_API_KEY"
status-im.config/RARIBLE_TESTNET_API_KEY #shadow/env "RARIBLE_TESTNET_API_KEY"
status-im.config/ALCHEMY_ETHEREUM_MAINNET_TOKEN #shadow/env "ALCHEMY_ETHEREUM_MAINNET_TOKEN"
status-im.config/ALCHEMY_ETHEREUM_GOERLI_TOKEN #shadow/env "ALCHEMY_ETHEREUM_GOERLI_TOKEN"
status-im.config/ALCHEMY_ETHEREUM_SEPOLIA_TOKEN #shadow/env "ALCHEMY_ETHEREUM_SEPOLIA_TOKEN"
status-im.config/ALCHEMY_ARBITRUM_MAINNET_TOKEN #shadow/env "ALCHEMY_ARBITRUM_MAINNET_TOKEN"
status-im.config/ALCHEMY_ARBITRUM_GOERLI_TOKEN #shadow/env "ALCHEMY_ARBITRUM_GOERLI_TOKEN"
status-im.config/ALCHEMY_ARBITRUM_SEPOLIA_TOKEN #shadow/env "ALCHEMY_ARBITRUM_SEPOLIA_TOKEN"
status-im.config/ALCHEMY_OPTIMISM_MAINNET_TOKEN #shadow/env "ALCHEMY_OPTIMISM_MAINNET_TOKEN"
status-im.config/ALCHEMY_OPTIMISM_GOERLI_TOKEN #shadow/env "ALCHEMY_OPTIMISM_GOERLI_TOKEN"
status-im.config/ALCHEMY_OPTIMISM_SEPOLIA_TOKEN #shadow/env "ALCHEMY_OPTIMISM_SEPOLIA_TOKEN"}
:compiler-options {:output-feature-set :es5
;; We disable `:fn-deprecated` warnings because we
;; are managing deprecation via clj-kondo and we
;; don't want the terminal output to be littered
;; with warnings on every code reload.
:warnings {:fn-deprecated false}
:closure-defines {re-frame.trace/trace-enabled? true}
:source-map false
;; This seems to be necessary while using the REPL,
;; otherwise sometimes you'll get weird errors when
;; instrumenting functions.
:static-fns false
:infer-externs true
:reader-features #{:mobile}}
;; if you want to use a real device, set your local ip
;; in the SHADOW_HOST env variable to make sure that
;; it will use the right interface
:local-ip #shadow/env "SHADOW_HOST"}
:chunks {:fleets legacy.status-im.fleet.default-fleet/default-fleets}
:release
{:closure-defines
@@ -126,8 +125,7 @@
status-im.config/ALCHEMY_ARBITRUM_SEPOLIA_TOKEN #shadow/env "ALCHEMY_ARBITRUM_SEPOLIA_TOKEN"
status-im.config/ALCHEMY_OPTIMISM_MAINNET_TOKEN #shadow/env "ALCHEMY_OPTIMISM_MAINNET_TOKEN"
status-im.config/ALCHEMY_OPTIMISM_GOERLI_TOKEN #shadow/env "ALCHEMY_OPTIMISM_GOERLI_TOKEN"
status-im.config/ALCHEMY_OPTIMISM_SEPOLIA_TOKEN #shadow/env "ALCHEMY_OPTIMISM_SEPOLIA_TOKEN"
status-im.config/STATUS_BACKEND_ENABLED false}
status-im.config/ALCHEMY_OPTIMISM_SEPOLIA_TOKEN #shadow/env "ALCHEMY_OPTIMISM_SEPOLIA_TOKEN"}
:compiler-options {:output-feature-set :es6
;;disable for android build as there
;;is an intermittent warning with deftype
+12 -10
View File
@@ -3,7 +3,8 @@
["react-native" :as rn]
["react-native-status-keycard" :default status-keycard]
[react-native.platform :as platform]
[taoensso.timbre :as log]))
[taoensso.timbre :as log]
[utils.address :as address]))
(defonce event-emitter
(if platform/ios?
@@ -93,14 +94,22 @@
[{:keys [on-success on-failure]}]
(.. status-keycard
(getApplicationInfo)
(then on-success)
(then (fn [response]
(let [info (-> response
(js->clj :keywordize-keys true)
(update :key-uid address/normalized-hex))]
(on-success info))))
(catch on-failure)))
(defn factory-reset
[{:keys [on-success on-failure]}]
(.. status-keycard
(factoryReset)
(then on-success)
(then (fn [response]
(let [info (-> response
(js->clj :keywordize-keys true)
(update :key-uid address/normalized-hex))]
(on-success info))))
(catch on-failure)))
(defn install-applet
@@ -146,13 +155,6 @@
(then on-success)
(catch on-failure)))
(defn save-mnemonic
[{:keys [mnemonic pin on-success on-failure]}]
(.. status-keycard
(saveMnemonic mnemonic pin)
(then on-success)
(catch on-failure)))
(defn unblock-pin
[{:keys [puk new-pin on-success on-failure]}]
(when (and new-pin puk)
+55 -49
View File
@@ -3,31 +3,54 @@
["react-native" :as react-native]
[clojure.string :as string]
[native-module.utils :as native-utils]
[oops.core :as oops]
[react-native.platform :as platform]
[status-backend.config :as status-backend.config]
[status-backend.core :as status-backend]
[status-im.config]
[taoensso.timbre :as log]
[utils.transforms :as types]))
(defn- extract-from-native-modules
[module]
(if status-im.config/STATUS_BACKEND_ENABLED
status-backend/fetch-js-obj
(some-> react-native
(oops/gobj-get "NativeModules")
(oops/gobj-get module))))
(defn status
[]
(when (exists? (.-NativeModules react-native))
(.-Status ^js (.-NativeModules react-native))))
(defn status [] (extract-from-native-modules "Status"))
(defn account-manager [] (extract-from-native-modules "AccountManager"))
(defn encryption [] (extract-from-native-modules "EncryptionUtils"))
(defn database [] (extract-from-native-modules "DatabaseManager"))
(defn ui-helper [] (extract-from-native-modules "UIHelper"))
(defn log-manager [] (extract-from-native-modules "LogManager"))
(defn utils [] (extract-from-native-modules "Utils"))
(defn network [] (extract-from-native-modules "NetworkManager"))
(defn mail-manager [] (extract-from-native-modules "MailManager"))
(defn account-manager
[]
(when (exists? (.-NativeModules react-native))
(.-AccountManager ^js (.-NativeModules react-native))))
(defn encryption
[]
(when (exists? (.-NativeModules react-native))
(.-EncryptionUtils ^js (.-NativeModules react-native))))
(defn database
[]
(when (exists? (.-NativeModules react-native))
(.-DatabaseManager ^js (.-NativeModules react-native))))
(defn ui-helper
[]
(when (exists? (.-NativeModules react-native))
(.-UIHelper ^js (.-NativeModules react-native))))
(defn log-manager
[]
(when (exists? (.-NativeModules react-native))
(.-LogManager ^js (.-NativeModules react-native))))
(defn utils
[]
(when (exists? (.-NativeModules react-native))
(.-Utils ^js (.-NativeModules react-native))))
(defn network
[]
(when (exists? (.-NativeModules react-native))
(.-NetworkManager ^js (.-NativeModules react-native))))
(defn mail-manager
[]
(when (exists? (.-NativeModules react-native))
(.-MailManager ^js (.-NativeModules react-native))))
(defn mail
[opts callback]
@@ -35,11 +58,7 @@
(defn init
[handler]
(if status-im.config/STATUS_BACKEND_ENABLED
(status-backend/init-web-socket handler)
(.addListener ^js (.-DeviceEventEmitter ^js react-native)
"gethEvent"
#(handler (.-jsonEvent ^js %)))))
(.addListener ^js (.-DeviceEventEmitter ^js react-native) "gethEvent" #(handler (.-jsonEvent ^js %))))
(defn clear-web-data
[]
@@ -489,22 +508,21 @@
new-password-hashed
callback))))
(defn convert-to-keycard-profile
[{:keys [key-uid] :as profile} settings password new-password callback]
(defn convert-to-keycard-account
[{:keys [key-uid] :as multiaccount-data} settings current-password# new-password callback]
(log/debug "[native-module] convert-to-keycard-account")
(.convertToKeycardAccount ^js (encryption)
key-uid
(types/clj->json profile)
(types/clj->json multiaccount-data)
(types/clj->json settings)
(:keycard-instance-uid settings)
password
""
current-password#
new-password
#(when callback (callback (types/json->clj %)))))
callback))
(defn backup-disabled-data-dir
[]
(if status-im.config/STATUS_BACKEND_ENABLED
status-backend.config/data-dir-path
(.backupDisabledDataDir ^js (utils))))
(.backupDisabledDataDir ^js (utils)))
(defn fleets
[]
@@ -512,27 +530,15 @@
(defn keystore-dir
[]
(if status-im.config/STATUS_BACKEND_ENABLED
status-backend.config/keystore-dir-path
(.keystoreDir ^js (utils))))
(.keystoreDir ^js (utils)))
(defn log-file-directory
[]
(if status-im.config/STATUS_BACKEND_ENABLED
status-backend.config/log-dir-path
(.logFileDirectory ^js (log-manager))))
(.logFileDirectory ^js (log-manager)))
(defn init-status-go-logging
[{:keys [enable? mobile-system? log-level log-request-go? callback]}]
(if status-im.config/STATUS_BACKEND_ENABLED
(.initLogging ^js (log-manager)
{:Enabled enable?
:MobileSystem mobile-system?
:Level log-level
:LogRequestGo log-request-go?
:LogRequestFile status-backend.config/log-request-file-path}
callback)
(.initLogging ^js (log-manager) enable? mobile-system? log-level log-request-go? callback)))
(.initLogging ^js (log-manager) enable? mobile-system? log-level log-request-go? callback))
(defn get-random-mnemonic
[callback]
+1 -1
View File
@@ -16,7 +16,7 @@
"
[{:keys [holder-name locked?]}]
(let [theme (quo.theme/use-theme)
label (if holder-name
label (if (boolean holder-name)
(i18n/label :t/user-keycard {:name holder-name})
(i18n/label :t/empty-keycard))]
[rn/view {:style (style/card-container locked? theme)}
+2 -4
View File
@@ -3,10 +3,8 @@
(defn container
[container-style]
(merge container-style
{:padding-vertical 7
:padding-horizontal 20
:flex-direction :row
:align-items :flex-start}))
{:flex-direction :row
:align-items :flex-start}))
(def index
{:margin-left 5})
+1 -1
View File
@@ -45,7 +45,7 @@
:or {type :bullet}}]
(let [theme (quo.theme/use-theme)]
[rn/view {:style (style/container container-style)}
[rn/view
[rn/view {:style style/index}
(case type
:step
[step/view
@@ -8,7 +8,8 @@
(def header
{:flex-direction :row
:justify-content :space-between})
:justify-content :space-between
:height 32})
(def header-title
{:flex 1
@@ -59,6 +59,7 @@
[rn/view {:style (merge style/container container-style)}
[text/text
{:size :heading-1
:number-of-lines 1
:weight :semi-bold
:style style/text
:accessibility-label accessibility-label}
-20
View File
@@ -1,20 +0,0 @@
(ns status-backend.config
(:require [status-im.config]))
(def ^:private url
(str "http://"
status-im.config/STATUS_BACKEND_ADDRESS
":"
status-im.config/STATUS_BACKEND_PORT))
(def data-dir-path
(str status-im.config/STATUS_BACKEND_STORAGE_DIR "/data"))
(def ^:private public-storage-dir
(str status-im.config/STATUS_BACKEND_STORAGE_DIR "/public"))
(def status-go-url (str url "/statusgo/"))
(def signals-url (str url "/signals"))
(def keystore-dir-path (str data-dir-path "/keystore"))
(def log-dir-path (str data-dir-path "/log"))
(def log-request-file-path (str public-storage-dir "/requests.log"))
-46
View File
@@ -1,46 +0,0 @@
(ns status-backend.core
(:require
[clojure.string :as string]
[oops.core :as oops]
[status-backend.config :as config]
[taoensso.timbre :as log]
[utils.network.core :as network]))
(def ^:private default-fetch-params
{:method :POST
:headers {"Accept" "application/json"
"Content-Type" "application/json"}})
(defn fetch
([endpoint]
(fetch endpoint nil (fn [])))
([endpoint body-params]
(fetch endpoint body-params (fn [])))
([endpoint body-params callback]
(let [url (str config/status-go-url endpoint)
params (assoc default-fetch-params :body body-params)]
(network/fetch url params callback))))
(defn- method-name->endpoint
[s]
(let [first-letter-capitalized (string/capitalize (first s))]
(str first-letter-capitalized (subs s 1))))
(def fetch-js-obj
"Performs a request to the status-backend (by using `status-backend.core/fetch`) using
the method invoked as endpoint name. E.g
This call: (.myMethod fetch-js-obj params)
Is the same as: (fetch \"MyMethod\" params)
"
(js/Proxy. #js {}
#js {:get (fn [_target native-module-method-name]
(let [endpoint (method-name->endpoint native-module-method-name)]
(partial fetch endpoint)))}))
(defn init-web-socket
[on-message]
(let [web-socket (js/WebSocket. config/signals-url)]
(oops/oset! web-socket "onopen" #(log/debug "[backend] Web Socket connected"))
(oops/oset! web-socket "onmessage" #(-> % (oops/oget "data") on-message))
(oops/oset! web-socket "onerror" #(log/error "[backend] Web Socket error" %))
(oops/oset! web-socket "onclose" #(log/debug "[backend] Web Socket closed" %))))
@@ -7,7 +7,8 @@
:margin-bottom 8})
(def item-text
{:margin-left -24})
{:margin-top 10
:margin-left -4})
(def info-text
{:margin-top -5})
-5
View File
@@ -22,11 +22,6 @@
:qr-code (js/require "../resources/images/ui2/qr-code.png")
:keycard-logo (js/require "../resources/images/ui2/keycard-logo.png")
:keycard-watermark (js/require "../resources/images/ui2/keycard-watermark.png")
:keycard-buy (js/require "../resources/images/ui2/keycard-buy.png")
:keycard-migration (js/require "../resources/images/ui2/keycard-migration.png")
:keycard-migration-failed (js/require "../resources/images/ui2/keycard-migration-failed.png")
:keycard-migration-succeeded (js/require "../resources/images/ui2/keycard-migration-succeeded.png")
:not-keycard (js/require "../resources/images/ui2/not-keycard.png")
:discover (js/require "../resources/images/ui2/discover.png")
:invite-friends (js/require "../resources/images/ui2/invite-friends.png")
:transaction-progress (js/require "../resources/images/ui2/transaction-progress.png")
@@ -1,10 +1,9 @@
(ns status-im.common.standard-authentication.enter-password.view
(:require
[clojure.string :as string]
[quo.core :as quo]
[react-native.core :as rn]
[status-im.common.standard-authentication.core :as standard-authentication]
[status-im.common.standard-authentication.enter-password.style :as style]
[status-im.common.standard-authentication.password-input.view :as password-input]
[status-im.contexts.profile.utils :as profile.utils]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
@@ -32,12 +31,11 @@
:full-name (profile.utils/displayed-name profile)
:customization-color customization-color
:size 24}]]
[standard-authentication/password-input
[password-input/view
{:on-press-biometrics on-press-biometrics
:blur? true
:processing processing
:error {:error? (not (string/blank? error))
:error-message error}
:error error
:default-password password
:sign-in-enabled? sign-in-enabled?}]
[quo/button
@@ -1,5 +1,6 @@
(ns status-im.common.standard-authentication.password-input.view
(:require
[clojure.string :as string]
[quo.core :as quo]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
@@ -11,6 +12,14 @@
[utils.re-frame :as rf]
[utils.security.core :as security]))
(defn- get-error-message
[error]
(if (and (some? error)
(or (= error "file is not a database")
(string/starts-with? (string/lower-case error) "failed")))
(i18n/label :t/oops-wrong-password)
error))
(defn- error-info
[error-message processing shell?]
(let [theme (quo.theme/use-theme)
@@ -40,21 +49,21 @@
(i18n/label :t/forgot-password)]]]))
(defn view
[{:keys [shell? on-press-biometrics blur? processing error]}]
(let [{:keys [error?
error-message]} error
default-value (rn/use-ref-atom "") ;;bug on Android
;;https://github.com/status-im/status-mobile/issues/19004
theme (quo.theme/use-theme)
on-change-password (rn/use-callback
(fn [entered-password]
(reset! default-value entered-password)
(debounce/debounce-and-dispatch
[:profile/on-password-input-changed
{:password (security/mask-data
entered-password)
:error ""}]
100)))]
[{:keys [shell? on-press-biometrics blur?]}]
(let [{:keys [error processing]} (rf/sub [:profile/login])
error-message (rn/use-memo #(get-error-message error) [error])
error? (boolean (seq error-message))
default-value (rn/use-ref-atom "") ;;bug on Android
;;https://github.com/status-im/status-mobile/issues/19004
theme (quo.theme/use-theme)
on-change-password (rn/use-callback
(fn [entered-password]
(reset! default-value entered-password)
(debounce/debounce-and-dispatch [:profile/on-password-input-changed
{:password (security/mask-data
entered-password)
:error ""}]
100)))]
[:<>
[rn/view {:style {:flex-direction :row}}
[quo/input
+11 -6
View File
@@ -29,12 +29,6 @@
(goog-define WALLET_CONNECT_PROJECT_ID "87815d72a81d739d2a7ce15c2cfdefb3")
(goog-define MIXPANEL_APP_ID "3350627")
(goog-define MIXPANEL_TOKEN "5c73bda2d36a9f688a5ee45641fb6775")
(goog-define STATUS_BACKEND_ENABLED false)
;; Warn: <STATUS_BACKEND_STORAGE_DIR>/data musts exist before running the app if
;; `STATUS_BACKEND_ENABLED` is true.
(goog-define STATUS_BACKEND_STORAGE_DIR "")
(goog-define STATUS_BACKEND_PORT 9050)
(goog-define STATUS_BACKEND_ADDRESS "localhost")
(def mainnet-rpc-url (str "https://eth-archival.rpc.grove.city/v1/" POKT_TOKEN))
(def goerli-rpc-url (str "https://goerli-archival.gateway.pokt.network/v1/lb/" POKT_TOKEN))
@@ -140,3 +134,14 @@
;; Alert banners are disabled for debug builds because alert banners overlay
;; interfere with react-native debug tools, such as inspector and Perf monitor
(def enable-alert-banner? (enabled? (get-config :ENABLE_ALERT_BANNER "0")))
;; enable using status backend server or not, otherwise it will use built-in status-go library
;; see doc/use-status-backend-server.md for more details
(goog-define STATUS_BACKEND_SERVER_ENABLED "0")
;; The host should contain an IP address and a port separated by a colon.
;; The port comes from your running status backend server.
;; If you run it by PORT=60000 make run-status-backend , then host will likely be 127.0.0.1:60000
(goog-define STATUS_BACKEND_SERVER_HOST "")
;; /path/to/root/data/dir
;; make sure it exists, it should be in absolute path
(goog-define STATUS_BACKEND_SERVER_ROOT_DATA_DIR "")
+1 -2
View File
@@ -580,14 +580,13 @@
(def ^:const sheet-screen-handle-height 20)
(def ^:const status-hostname "status.app")
(def ^:const get-keycard-url "https://get.keycard.tech/")
(def ^:const community-joined-notification-type "communityJoined")
(def ^:const default-telemetry-server-url "https://telemetry.status.im")
(def ^:const contact-item-height 56)
(def ^:const page-nav-height 56)
(def ^:const currency-item-height 64)
(def ^:const slippages [0.1 0.5 1])
@@ -4,18 +4,20 @@
[status-im.common.events-helper :as events-helper]
[status-im.common.standard-authentication.core :as standard-auth]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
[utils.re-frame :as rf]
[utils.security.core :as security]))
(defn view
[]
(let [profile-name (rf/sub [:profile/name])
profile-picture (rf/sub [:profile/image])
customization-color (rf/sub [:profile/customization-color])
{:keys [on-success]} (rf/sub [:get-screen-params])]
(let [profile-name (rf/sub [:profile/name])
profile-picture (rf/sub [:profile/image])
customization-color (rf/sub [:profile/customization-color])]
[:<>
[quo/page-nav
{:icon-name :i/close
:on-press events-helper/navigate-back}]
{:key :header
:background :blur
:icon-name :i/close
:on-press events-helper/navigate-back}]
[quo/page-top
{:title (i18n/label :t/authorise-with-password)
:description :context-tag
@@ -29,5 +31,5 @@
:container-style {}
:customization-color customization-color
:track-text (i18n/label :t/slide-to-authorise)
:on-auth-success #(when on-success (on-success %))
:on-auth-success #(println "TBD" (security/safe-unmask-data %))
:auth-button-label (i18n/label :t/confirm)}]]]))
+15 -8
View File
@@ -10,8 +10,10 @@
[]
[:<>
[quo/page-nav
{:icon-name :i/arrow-left
:on-press events-helper/navigate-back}]
{:key :header
:background :blur
:icon-name :i/arrow-left
:on-press events-helper/navigate-back}]
[quo/page-top
{:title (i18n/label :t/check-keycard)
:description :text
@@ -21,9 +23,14 @@
{:resize-mode :contain
:source (resources/get-image :check-your-keycard)}]]
[quo/divider-label (i18n/label :t/tips-scan-keycard)]
[quo/markdown-list {:description (i18n/label :t/remove-phone-case)}]
[quo/markdown-list {:description (i18n/label :t/keep-card-steady)}]
[quo/bottom-actions
{:actions :one-action
:button-one-label (i18n/label :t/ready-to-scan)
:button-one-props {:on-press #(rf/dispatch [:keycard/migration.check-empty-card])}}]])
[rn/view {:style {:padding-horizontal 10}}
[quo/markdown-list
{:container-style {:padding-vertical 10}
:description (i18n/label :t/remove-phone-case)}]
[quo/markdown-list
{:container-style {:padding-bottom 25}
:description (i18n/label :t/keep-card-steady)}]]
[quo/button
{:on-press #(rf/dispatch [:keycard/connect])
:container-style {:margin-horizontal 20}}
(i18n/label :t/ready-to-scan)]])
+103 -42
View File
@@ -3,10 +3,10 @@
[native-module.core :as native-module]
[react-native.async-storage :as async-storage]
[react-native.platform :as platform]
status-im.contexts.keycard.nfc.effects
[status-im.contexts.keycard.utils :as keycard.utils]
[status-im.contexts.profile.config :as profile.config]
[utils.re-frame :as rf]))
[taoensso.timbre :as log]
[utils.re-frame :as rf]
[utils.transforms :as transforms]))
(defonce ^:private active-listeners (atom []))
@@ -32,45 +32,106 @@
(rf/reg-fx :effects.keycard/register-card-events register-card-events)
(rf/reg-fx :effects.keycard/unregister-card-events unregister-card-events)
(rf/reg-fx :effects.keycard/get-application-info
(fn [args]
(keycard/get-application-info (keycard.utils/wrap-handlers args))))
(rf/reg-fx :effects.keycard/get-keys
(fn [args]
(keycard/get-keys (keycard.utils/wrap-handlers args))))
(rf/reg-fx :effects.keycard/sign
(fn [args]
(keycard/sign (keycard.utils/wrap-handlers args))))
(rf/reg-fx :keycard/init-card
(fn [args]
(keycard/init-card (keycard.utils/wrap-handlers args))))
(rf/reg-fx :effects.keycard/generate-and-load-key
(fn [args]
(keycard/generate-and-load-key (keycard.utils/wrap-handlers args))))
(rf/reg-fx :effects.keycard/login-with-keycard
(fn [{:keys [key-uid password whisper-private-key]}]
(native-module/login-account
(assoc (profile.config/login)
:keyUid key-uid
:password password
:keycardWhisperPrivateKey whisper-private-key))))
(rf/reg-fx :effects.keycard/set-pairing-to-keycard
(fn [pairings]
(keycard/set-pairings pairings)))
(defn check-nfc-enabled
[]
(log/debug "[keycard] check-nfc-enabled")
(keycard/check-nfc-enabled
{:on-success
(fn [response]
(log/debug "[keycard response] check-nfc-enabled")
(rf/dispatch [:keycard/on-check-nfc-enabled-success response]))}))
(rf/reg-fx :effects.keycard/check-nfc-enabled check-nfc-enabled)
(rf/reg-fx
:keycard/persist-pairings
(fn [pairings]
(async-storage/set-item! "status-keycard-pairings" pairings)))
:effects.keycard.ios/start-nfc
(fn [args]
(log/debug "fx start-nfc")
(keycard/start-nfc args)))
(rf/reg-fx :effects.keycard/retrieve-pairings
(fn []
(async-storage/get-item
"status-keycard-pairings"
#(rf/dispatch [:keycard/on-retrieve-pairings-success %]))))
(rf/reg-fx
:effects.keycard.ios/stop-nfc
(fn [args]
(log/debug "fx stop-nfc")
(keycard/stop-nfc args)))
(defn- error-object->map
[^js object]
{:code (.-code object)
:error (.-message object)})
(defn get-application-info
[{:keys [on-success on-failure] :as args}]
(log/debug "[keycard] get-application-info")
(keycard/get-application-info
(assoc
args
:on-success
(fn [response]
(log/debug "[keycard response succ] get-application-info")
(when on-success
(on-success response)))
:on-failure
(fn [response]
(log/error "[keycard response fail] get-application-info")
(when on-failure
(on-failure (error-object->map response)))))))
(rf/reg-fx :effects.keycard/get-application-info get-application-info)
(defn get-keys
[{:keys [on-success on-failure] :as args}]
(log/debug "[keycard] get-keys")
(keycard/get-keys
(assoc
args
:on-success
(fn [response]
(log/debug "[keycard response succ] get-keys")
(when on-success
(on-success (transforms/js->clj response))))
:on-failure
(fn [response]
(log/warn "[keycard response fail] get-keys"
(error-object->map response))
(when on-failure
(on-failure (error-object->map response)))))))
(rf/reg-fx :effects.keycard/get-keys get-keys)
(defn login
[{:keys [key-uid password whisper-private-key]}]
(native-module/login-account
(assoc (profile.config/login)
:keyUid key-uid
:password password
:keycardWhisperPrivateKey whisper-private-key)))
(rf/reg-fx :effects.keycard/login-with-keycard login)
(defn retrieve-pairings
[]
(async-storage/get-item
"status-keycard-pairings"
#(rf/dispatch [:keycard/on-retrieve-pairings-success %])))
(rf/reg-fx :effects.keycard/retrieve-pairings retrieve-pairings)
(defn set-pairing-to-keycard
[pairings]
(keycard/set-pairings pairings))
(rf/reg-fx :effects.keycard/set-pairing-to-keycard set-pairing-to-keycard)
(defn sign
[{:keys [on-success on-failure] :as args}]
(log/debug "[keycard] sign")
(keycard/sign
(assoc
args
:on-success
(fn [response]
(log/debug "[keycard response succ] sign")
(when on-success
(on-success (transforms/js->clj response))))
:on-failure
(fn [response]
(log/warn "[keycard response fail] sign"
(error-object->map response))
(when on-failure
(on-failure (error-object->map response)))))))
(rf/reg-fx :effects.keycard/sign sign)
+9 -12
View File
@@ -3,7 +3,7 @@
[react-native.core :as rn]
[status-im.common.events-helper :as events-helper]
[status-im.common.resources :as resources]
[status-im.contexts.keycard.migrate.sheets.view :as sheets.migrate]
[status-im.contexts.keycard.sheets.migrate.view :as sheets.migrate]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
@@ -11,8 +11,10 @@
[]
[:<>
[quo/page-nav
{:icon-name :i/close
:on-press events-helper/navigate-back}]
{:key :header
:background :blur
:icon-name :i/arrow-left
:on-press events-helper/navigate-back}]
[quo/page-top
{:title (i18n/label :t/keycard-empty)
:description :text
@@ -24,15 +26,10 @@
:subtitle (i18n/label :t/use-keycard-login-sign)
:button-label (i18n/label :t/import-profile-key-pair)
:accessibility-label :get-keycard
:image (resources/get-image :keycard-buy)
:on-press (fn []
(rf/dispatch
[:show-bottom-sheet
{:theme :dark
:content (fn []
[sheets.migrate/view
{:on-continue #(rf/dispatch
[:keycard/migration.get-phrase])}])}]))}]]
:image (resources/get-image :generate-keys)
:on-press #(rf/dispatch [:show-bottom-sheet
{:theme :dark
:content (fn [] [sheets.migrate/view])}])}]]
[quo/information-box
{:type :default
:style {:margin-top 32 :margin-horizontal 28}}
+26 -21
View File
@@ -1,37 +1,42 @@
(ns status-im.contexts.keycard.error.view
(:require [quo.core :as quo]
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[status-im.common.events-helper :as events-helper]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(def titles
{:keycard/error.keycard-blank {:title (i18n/label :t/keycard-empty)
:description (i18n/label :t/no-key-pair-keycard)}
:keycard/error.keycard-wrong-profile {:title (i18n/label :t/keycard-not-empty)
:description (i18n/label :t/cant-store-new-keys)}
:keycard/error.keycard-unpaired {:title (i18n/label :t/keycard-full)
:description (i18n/label :t/pairing-slots-occupied)}
:keycard/error.keycard-frozen {:title (i18n/label :t/keycard-locked)
:description (i18n/label :t/cant-use-right-now)}
:keycard/error.keycard-locked {:title (i18n/label :t/keycard-locked)
:description (i18n/label :t/cant-use-right-now)}})
{:keycard/error.keycard-wrong {:title "Keycard is not empty"
:description "You cant use it to store new keys right now"}
:keycard/error.keycard-unpaired {:title "Keycard is full"
:description "All pairing slots are occupied"}
:keycard/error.keycard-frozen {:title "Keycard is locked"
:description "You cant use it right now"}
:keycard/error.keycard-locked {:title "Keycard is locked"
:description "You cant use it right now"}})
(defn view
[]
(let [error (rf/sub [:keycard/application-info-error])
(let [{:keys [top bottom]} (safe-area/get-insets)
error (rf/sub [:keycard/application-info-error])
{:keys [title description]} (get titles error)]
[:<>
[quo/overlay
{:type :shell
:container-style {:padding-top top
:padding-bottom bottom}}
[quo/page-nav
{:icon-name :i/close
:on-press events-helper/navigate-back}]
{:key :header
:background :blur
:icon-name :i/arrow-left
:on-press events-helper/navigate-back}]
[quo/page-top
{:title title
:description :text
:description-text description}]
[rn/view {:style {:margin-horizontal 20}}
[quo/keycard {:holder-name ""}]
[quo/information-box
{:type :default
:style {:margin-top 20}}
(i18n/label :t/unlock-reset-instructions)]]]))
[rn/view {:height 226}]
[rn/view {:padding-horizontal 20}
[quo/info-message
{:container-style {:padding-top 15}
:icon :i/info
:size :default}
"To unlock or factory reset the Keycard, please use the Status desktop app. If you'd like this features on mobile, feel free to upvote them and discuss in the Status community."]]]))
+70 -71
View File
@@ -1,117 +1,116 @@
(ns status-im.contexts.keycard.events
(:require [re-frame.core :as rf]
status-im.contexts.keycard.login.events
status-im.contexts.keycard.migrate.events
status-im.contexts.keycard.migrate.re-encrypting.events
status-im.contexts.keycard.nfc.events
status-im.contexts.keycard.nfc.sheets.events
status-im.contexts.keycard.nfc-sheet.events
status-im.contexts.keycard.pin.events
status-im.contexts.keycard.sign.events
[status-im.contexts.keycard.utils :as keycard.utils]
utils.datetime))
[taoensso.timbre :as log]))
(rf/reg-event-fx :keycard/on-check-nfc-enabled-success
(fn [{:keys [db]} [nfc-enabled?]]
{:db (assoc-in db [:keycard :nfc-enabled?] nfc-enabled?)}))
(rf/reg-event-fx :keycard.ios/on-nfc-user-cancelled
(fn [{:keys [db]}]
(log/debug "[keycard] nfc user cancelled")
{:db (-> db
(assoc-in [:keycard :pin :status] nil)
(assoc-in [:keycard :on-nfc-cancelled-event-vector] nil))
:fx [(when-let [on-nfc-cancelled-event-vector (get-in db [:keycard :on-nfc-cancelled-event-vector])]
[:dispatch on-nfc-cancelled-event-vector])]}))
(rf/reg-event-fx :keycard/on-card-connected
(fn [{:keys [db]} _]
(log/debug "[keycard] card globally connected")
{:db (assoc-in db [:keycard :card-connected?] true)
:fx [(when-let [event (get-in db [:keycard :on-card-connected-event-vector])]
[:dispatch event])]}))
(rf/reg-event-fx :keycard/on-card-disconnected
(fn [{:keys [db]} _]
(log/debug "[keycard] card disconnected")
{:db (assoc-in db [:keycard :card-connected?] false)
:fx [(when-let [event (get-in db [:keycard :on-card-disconnected-event-vector])]
[:dispatch event])]}))
(rf/reg-event-fx :keycard.ios/start-nfc
(fn [_]
{:effects.keycard.ios/start-nfc nil}))
(rf/reg-event-fx :keycard.ios/on-nfc-timeout
(fn [{:keys [db]} _]
(log/debug "[keycard] nfc timeout")
{:db (assoc-in db [:keycard :card-connected?] false)
:fx [[:dispatch-later [{:ms 500 :dispatch [:keycard.ios/start-nfc]}]]]}))
(rf/reg-event-fx :keycard/on-retrieve-pairings-success
(fn [{:keys [db]} [pairings]]
{:db (assoc-in db [:keycard :pairings] pairings)
:fx [[:effects.keycard/set-pairing-to-keycard pairings]]}))
(rf/reg-event-fx :keycard/update-pairings
(fn [{:keys [db]} [instance-uid pairing]]
(let [pairings (get-in db [:keycard :pairings])
new-pairings (assoc pairings
instance-uid
{:pairing pairing
:paired-on (utils.datetime/timestamp)})]
{:db (assoc-in db [:keycard :pairings] new-pairings)
:keycard/persist-pairings new-pairings})))
(rf/reg-event-fx :keycard.ios/on-start-nfc-success
(fn [{:keys [db]} [{:keys [on-cancel-event-vector]}]]
(log/debug "[keycard] nfc started success")
{:db (assoc-in db [:keycard :on-nfc-cancelled-event-vector] on-cancel-event-vector)}))
(rf/reg-event-fx :keycard/on-action-with-pin-error
(fn [{:keys [db]} [error]]
(log/debug "[keycard] on-action-with-pin-error: " error)
(let [tag-was-lost? (keycard.utils/tag-lost? (:error error))
pin-retries-count (keycard.utils/pin-retries (:error error))]
(if (or tag-was-lost? (nil? pin-retries-count))
(if tag-was-lost?
{:db (assoc-in db [:keycard :pin :status] nil)}
{:db (-> db
(assoc-in [:keycard :application-info :pin-retry-counter] pin-retries-count)
(assoc-in [:keycard :pin :status] :error))
:fx [[:dispatch [:keycard/disconnect]]
(when (zero? pin-retries-count)
[:dispatch
[:keycard/on-application-info-error
:keycard/error.keycard-locked]])]}))))
(if (nil? pin-retries-count)
{:effects.utils/show-popup {:title "wrong-keycard"}}
{:db (-> db
(assoc-in [:keycard :application-info :pin-retry-counter] pin-retries-count)
(assoc-in [:keycard :pin :status] :error))
:fx [[:dispatch [:keycard/disconnect]]
(when (zero? pin-retries-count)
[:effects.utils/show-popup {:title "frozen-keycard"}])]})))))
(rf/reg-event-fx :keycard/get-keys
(fn [_ [data]]
{:effects.keycard/get-keys data}))
(rf/reg-event-fx :keycard/on-get-application-info-success
(fn [{:keys [db]} [application-info {:keys [key-uid on-success-fx]}]]
(if-let [error (keycard.utils/validate-application-info key-uid application-info)]
(case error
:keycard/error.not-keycard
{:fx [[:dispatch [:keycard/disconnect]]
[:dispatch [:open-modal :screen/keycard.not-keycard]]]}
:keycard/error.keycard-blank
{:fx [[:dispatch [:keycard/disconnect]]
[:dispatch [:open-modal :screen/keycard.empty]]]}
{:db (assoc-in db [:keycard :application-info-error] error)
:fx [[:dispatch [:keycard/disconnect]]
[:dispatch [:open-modal :screen/keycard.error]]]})
{:db (-> db
(assoc-in [:keycard :application-info] application-info)
(assoc-in [:keycard :pin :status] :verifying))
:fx on-success-fx})))
(rf/reg-event-fx :keycard/get-application-info
(fn [_ [{:keys [on-success on-failure]}]]
{:effects.keycard/get-application-info {:on-success on-success :on-failure on-failure}}))
(rf/reg-event-fx :keycard/cancel-connection
(fn [{:keys [db]}]
{:db (update db :keycard dissoc :on-card-connected-event-vector :on-nfc-cancelled-event-vector)}))
{:db (-> db
(assoc-in [:keycard :on-card-connected-event-vector] nil)
(assoc-in [:keycard :on-nfc-cancelled-event-vector] nil))}))
(rf/reg-event-fx :keycard/disconnect
(fn [_ _]
{:fx [[:dispatch [:keycard/cancel-connection]]
[:dispatch [:keycard/hide-connection-sheet]]]}))
(rf/reg-event-fx :keycard/on-application-info-error
(fn [{:keys [db]} [error]]
{:db (assoc-in db [:keycard :application-info-error] error)
:fx [[:dispatch [:keycard/disconnect]]
[:dispatch
[:open-modal
(if (= :keycard/error.not-keycard error)
:screen/keycard.not-keycard
:screen/keycard.error)]]]}))
(rf/reg-event-fx :keycard/update-application-info
(fn [{:keys [db]} [app-info]]
{:db (update db
:keycard
#(-> %
(assoc :application-info app-info)
(dissoc :application-info-error)))}))
(rf/reg-event-fx :keycard/get-application-info
(fn [_ [{:keys [key-uid on-success on-error]}]]
{:effects.keycard/get-application-info
{:on-success (fn [{:keys [instance-uid new-pairing] :as app-info}]
(rf/dispatch [:keycard/update-application-info app-info])
(when (and instance-uid new-pairing)
(rf/dispatch [:keycard/update-pairings instance-uid new-pairing]))
(if-let [error (keycard.utils/validate-application-info key-uid app-info)]
(if on-error
(on-error error)
(rf/dispatch [:keycard/on-application-info-error error]))
(when on-success (on-success app-info))))
:on-error (fn []
(if on-error
(on-error :keycard/error.not-keycard)
(rf/dispatch [:keycard/on-application-info-error
:keycard/error.not-keycard])))}}))
(rf/reg-event-fx :keycard/connect
(fn [{:keys [db]} [{:keys [key-uid on-success on-error on-connect-event-vector]}]]
(let [event-vector
(or on-connect-event-vector
[:keycard/get-application-info
{:key-uid key-uid
:on-success on-success
:on-error on-error}])]
(fn [{:keys [db]} [args]]
(let [connected? (get-in db [:keycard :card-connected?])
event-vector [:keycard/get-application-info
{:on-success #(rf/dispatch [:keycard/on-get-application-info-success % args])}]]
{:db (assoc-in db [:keycard :on-card-connected-event-vector] event-vector)
:fx [[:dispatch
[:keycard/show-connection-sheet
{:on-cancel-event-vector [:keycard/cancel-connection]}]]
(when (get-in db [:keycard :card-connected?])
(when connected?
[:dispatch event-vector])]})))
@@ -5,6 +5,7 @@
(fn [{:keys [db]} [data]]
(let [{:keys [key-uid encryption-public-key
whisper-private-key]} data
key-uid (str "0x" key-uid)
profile (get-in db [:profile/profiles-overview key-uid])]
{:db
(-> db
@@ -1,132 +0,0 @@
(ns status-im.contexts.keycard.migrate.events
(:require [clojure.string :as string]
[status-im.contexts.keycard.pin.view :as keycard.pin]
[utils.re-frame :as rf]
[utils.security.core :as security]))
(rf/reg-event-fx :keycard/migration.check-empty-card
(fn [{:keys [db]}]
{:fx [[:dispatch
[:keycard/connect
{:key-uid (get-in db [:profile/profile :key-uid])
:on-success
(fn []
;;TODO keys already on the keycard, new flow needs to be implemented
;; https://github.com/status-im/status-mobile/issues/21446
(rf/dispatch [:keycard/disconnect])
(rf/dispatch [:open-modal :screen/keycard.authorise
{:on-success
#(rf/dispatch [:keycard/migration.authorisation-success %])}]))
:on-error
(fn [error]
(if (= error :keycard/error.keycard-blank)
(do
(rf/dispatch [:keycard/disconnect])
(rf/dispatch [:open-modal :screen/keycard.empty]))
(rf/dispatch [:keycard/on-application-info-error error])))}]]]}))
(defn get-application-info-and-continue
[key-uid]
(rf/dispatch [:keycard/get-application-info
{:key-uid key-uid
:on-success #(rf/dispatch [:keycard/migration.continue])
:on-error
(fn [error]
(if (= error :keycard/error.keycard-blank)
(rf/dispatch [:keycard/migration.continue])
(rf/dispatch [:keycard/on-application-info-error error])))}]))
(rf/reg-event-fx :keycard/migration.continue
(fn [{:keys [db]}]
(let [key-uid (get-in db [:profile/profile :key-uid])
{:keys [initialized? has-master-key?]} (get-in db [:keycard :application-info])
{:keys [masked-phrase pin]} (get-in db [:keycard :migration])
on-failure (fn []
(rf/dispatch [:keycard/disconnect])
(rf/dispatch [:navigate-to
:screen/keycard.migrate.fail]))]
(cond
(not initialized?)
{:fx [[:keycard/init-card
{:pin pin
:on-success #(get-application-info-and-continue key-uid)
:on-failure on-failure}]]}
(not has-master-key?)
{:fx [[:effects.keycard/generate-and-load-key
{:mnemonic (security/safe-unmask-data masked-phrase)
:pin pin
:on-success #(get-application-info-and-continue key-uid)
:on-failure on-failure}]]}
:else
{:fx [[:effects.keycard/get-keys
{:pin pin
:on-success #(rf/dispatch [:keycard/migration.convert-to-keycard-profile %])
:on-failure on-failure}]]}))))
(rf/reg-event-fx :keycard/migration.start
(fn [{:keys [db]}]
{:fx [[:dispatch
[:keycard/connect
{:key-uid (get-in db [:profile/profile :key-uid])
:on-success #(rf/dispatch [:keycard/migration.continue])
:on-error
(fn [error]
(if (= error :keycard/error.keycard-blank)
(rf/dispatch [:keycard/migration.continue])
(rf/dispatch [:keycard/on-application-info-error error])))}]]]}))
(rf/reg-event-fx :keycard/migration.get-phrase
(fn [{:keys [db]}]
{:db (assoc-in db [:keycard :migration] nil)
:fx [[:dispatch [:navigate-back]]
(if (string/blank? (get-in db [:profile/profile :mnemonic]))
[:dispatch
[:open-modal :screen/use-recovery-phrase
{:on-success #(rf/dispatch [:keycard/migration.phrase-entered %])}]]
[:dispatch
[:open-modal :screen/backup-recovery-phrase
{:on-success #(rf/dispatch [:keycard/migration.phrase-backed-up %])}]])]}))
(rf/reg-event-fx :keycard/migration.phrase-entered
(fn [{:keys [db]} [{:keys [phrase]}]]
{:db (assoc-in db [:keycard :migration :masked-phrase] (security/mask-data phrase))
:fx [[:dispatch [:navigate-back]]
[:dispatch
[:open-modal :screen/keycard.authorise
{:on-success #(rf/dispatch [:keycard/migration.authorisation-success %])}]]]}))
(rf/reg-event-fx :keycard/migration.phrase-backed-up
(fn [{:keys [db]}]
{:db (assoc-in db
[:keycard :migration :masked-phrase]
(security/mask-data (get-in db [:profile/profile :mnemonic])))
:fx [[:dispatch [:profile.settings/profile-update :mnemonic nil]]
[:dispatch [:navigate-back]]
[:dispatch
[:open-modal :screen/keycard.authorise
{:on-success #(rf/dispatch [:keycard/migration.authorisation-success %])}]]]}))
(rf/reg-event-fx :keycard/migration.authorisation-success
(fn [{:keys [db]} [masked-password]]
(let [{:keys [initialized?]} (get-in db [:keycard :application-info])]
{:db (assoc-in db [:keycard :migration :masked-password] masked-password)
:fx [[:dispatch [:navigate-back]]
(if initialized?
[:dispatch
[:show-bottom-sheet
{:content (fn []
[keycard.pin/auth
{:on-complete #(rf/dispatch [:keycard/migration.pin-created %])}])}]]
[:dispatch
[:open-modal :screen/keycard.pin.create
{:on-complete (fn [new-pin]
(rf/dispatch [:navigate-back])
(rf/dispatch [:keycard/migration.pin-created new-pin]))}]])]})))
(rf/reg-event-fx :keycard/migration.pin-created
(fn [{:keys [db]} [pin]]
{:db (assoc-in db [:keycard :migration :pin] pin)
:fx [[:dispatch [:open-modal :screen/keycard.migrate]]]}))
@@ -1,32 +0,0 @@
(ns status-im.contexts.keycard.migrate.fail.view
(:require [quo.core :as quo]
[react-native.core :as rn]
[status-im.common.resources :as resources]
[status-im.constants :as constants]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn view
[]
(let [profile-name (rf/sub [:profile/name])
profile-picture (rf/sub [:profile/image])
customization-color (rf/sub [:profile/customization-color])]
[:<>
[quo/page-top
{:title (i18n/label :t/failed-to-migrate-key-pair)
:description :context-tag
:context-tag {:full-name profile-name
:profile-picture profile-picture
:customization-color customization-color}
:container-style {:margin-top constants/page-nav-height}}]
[rn/view {:style {:flex 1 :align-items :center :justify-content :center}}
[rn/image
{:resize-mode :contain
:source (resources/get-image :keycard-migration-failed)}]]
[quo/divider-label (i18n/label :t/what-you-can-do)]
[quo/markdown-list {:description (i18n/label :t/log-out-remove-profile)}]
[quo/markdown-list {:description (i18n/label :t/recover-status-profile)}]
[quo/bottom-actions
{:actions :one-action
:button-one-label (i18n/label :t/log-out-remove)
:button-one-props {:on-press #(rf/dispatch [:logout])}}]]))
@@ -1,31 +0,0 @@
(ns status-im.contexts.keycard.migrate.re-encrypting.events
(:require [clojure.string :as string]
[utils.re-frame :as rf]
[utils.security.core :as security]))
(rf/reg-event-fx :keycard/migration.convert-to-keycard-profile
(fn [{:keys [db]} [{:keys [key-uid instance-uid encryption-public-key]}]]
(let [{:keys [masked-password]} (get-in db [:keycard :migration])
{:keys [pairing paired-on]} (get-in db [:keycard :pairings instance-uid])
{:keys [kdfIterations]} (:profile/profile db)]
{:fx [[:dispatch [:keycard/disconnect]]
[:dispatch [:navigate-back]]
[:dispatch [:open-modal :screen/keycard.re-encrypting]]
[:effects.profile/convert-to-keycard-profile
{:profile
{:key-uid key-uid
:keycard-pairing pairing
:kdfIterations kdfIterations}
:settings
{:keycard-instance-uid instance-uid
:keycard-paired-on paired-on
:keycard-pairing pairing}
:password
(security/safe-unmask-data masked-password)
:new-password
encryption-public-key
:callback
(fn [{:keys [error]}]
(if (string/blank? error)
(rf/dispatch [:navigate-to :screen/keycard.migrate.success])
(rf/dispatch [:navigate-to :screen/keycard.migrate.fail])))}]]})))
@@ -1,27 +0,0 @@
(ns status-im.contexts.keycard.migrate.re-encrypting.view
(:require [quo.core :as quo]
[react-native.core :as rn]
[status-im.common.resources :as resources]
[status-im.constants :as constants]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn view
[]
(let [profile-name (rf/sub [:profile/name])
profile-picture (rf/sub [:profile/image])
customization-color (rf/sub [:profile/customization-color])]
[:<>
[quo/page-top
{:title (i18n/label :t/re-encrypting-data)
:description :context-tag
:context-tag {:full-name profile-name
:profile-picture profile-picture
:customization-color customization-color}
:container-style {:margin-top constants/page-nav-height}}]
[quo/text {:style {:padding-horizontal 20}}
(i18n/label :t/do-not-quit)]
[rn/view {:style {:flex 1 :align-items :center :justify-content :center}}
[rn/image
{:resize-mode :contain
:source (resources/get-image :keycard-migration)}]]]))
@@ -1,31 +0,0 @@
(ns status-im.contexts.keycard.migrate.success.view
(:require [quo.core :as quo]
[react-native.core :as rn]
[status-im.common.resources :as resources]
[status-im.constants :as constants]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn view
[]
(let [profile-name (rf/sub [:profile/name])
profile-picture (rf/sub [:profile/image])
customization-color (rf/sub [:profile/customization-color])]
[:<>
[quo/page-top
{:title (i18n/label :t/key-pair-migrated-successfully)
:description :context-tag
:context-tag {:full-name profile-name
:profile-picture profile-picture
:customization-color customization-color}
:container-style {:margin-top constants/page-nav-height}}]
[quo/text {:style {:padding-horizontal 20}}
(i18n/label :t/use-keycard-for-status)]
[rn/view {:style {:flex 1 :align-items :center :justify-content :center}}
[rn/image
{:resize-mode :contain
:source (resources/get-image :keycard-migration-succeeded)}]]
[quo/button
{:on-press #(rf/dispatch [:logout])
:container-style {:margin-horizontal 20}}
(i18n/label :t/logout)]]))
@@ -1,34 +0,0 @@
(ns status-im.contexts.keycard.migrate.view
(:require [quo.core :as quo]
[react-native.core :as rn]
[status-im.common.events-helper :as events-helper]
[status-im.common.resources :as resources]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn view
[]
(let [profile-name (rf/sub [:profile/name])
profile-picture (rf/sub [:profile/image])
customization-color (rf/sub [:profile/customization-color])]
[:<>
[quo/page-nav
{:icon-name :i/close
:on-press events-helper/navigate-back}]
[quo/page-top
{:title (i18n/label :t/ready-to-migrate-key-pair)
:description :context-tag
:context-tag {:full-name profile-name
:profile-picture profile-picture
:customization-color customization-color}}]
[rn/view {:style {:flex 1 :align-items :center :justify-content :center}}
[rn/image
{:resize-mode :contain
:source (resources/get-image :keycard-migration)}]]
[quo/divider-label (i18n/label :t/tips-scan-keycard)]
[quo/markdown-list {:description (i18n/label :t/remove-phone-case)}]
[quo/markdown-list {:description (i18n/label :t/keep-card-steady)}]
[quo/bottom-actions
{:actions :one-action
:button-one-label (i18n/label :t/scan-keycard)
:button-one-props {:on-press #(rf/dispatch [:keycard/migration.start])}}]]))
@@ -1,11 +0,0 @@
(ns status-im.contexts.keycard.nfc.effects
(:require [keycard.keycard :as keycard]
[utils.re-frame :as rf]))
(rf/reg-fx :effects.keycard/check-nfc-enabled
(fn []
(keycard/check-nfc-enabled
{:on-success #(rf/dispatch [:keycard/on-check-nfc-enabled-success %])})))
(rf/reg-fx :effects.keycard.ios/start-nfc keycard/start-nfc)
(rf/reg-fx :effects.keycard.ios/stop-nfc keycard/stop-nfc)
@@ -1,27 +0,0 @@
(ns status-im.contexts.keycard.nfc.events
(:require [utils.re-frame :as rf]))
(rf/reg-event-fx :keycard.ios/start-nfc
(fn [_]
{:fx [[:effects.keycard.ios/start-nfc]]}))
(rf/reg-event-fx :keycard.ios/on-start-nfc-success
(fn [{:keys [db]} [{:keys [on-cancel-event-vector]}]]
{:db (assoc-in db [:keycard :on-nfc-cancelled-event-vector] on-cancel-event-vector)}))
(rf/reg-event-fx :keycard.ios/on-nfc-timeout
(fn [{:keys [db]} _]
{:db (assoc-in db [:keycard :card-connected?] false)
:fx [[:dispatch-later [{:ms 500 :dispatch [:keycard.ios/start-nfc]}]]]}))
(rf/reg-event-fx :keycard/on-check-nfc-enabled-success
(fn [{:keys [db]} [nfc-enabled?]]
{:db (assoc-in db [:keycard :nfc-enabled?] nfc-enabled?)}))
(rf/reg-event-fx :keycard.ios/on-nfc-user-cancelled
(fn [{:keys [db]}]
{:db (-> db
(assoc-in [:keycard :pin :status] nil)
(assoc-in [:keycard :on-nfc-cancelled-event-vector] nil))
:fx [(when-let [on-nfc-cancelled-event-vector (get-in db [:keycard :on-nfc-cancelled-event-vector])]
[:dispatch on-nfc-cancelled-event-vector])]}))
@@ -1,4 +1,4 @@
(ns status-im.contexts.keycard.nfc.sheets.events
(ns status-im.contexts.keycard.nfc-sheet.events
(:require [re-frame.core :as rf]
[react-native.platform :as platform]
[taoensso.timbre :as log]))
@@ -1,4 +1,4 @@
(ns status-im.contexts.keycard.nfc.sheets.view
(ns status-im.contexts.keycard.nfc-sheet.view
(:require [quo.foundations.colors :as colors]
quo.theme
[react-native.core :as rn]
@@ -1,24 +1,28 @@
(ns status-im.contexts.keycard.not-keycard.view
(:require [quo.core :as quo]
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[status-im.common.events-helper :as events-helper]
[status-im.common.resources :as resources]
[utils.i18n :as i18n]))
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn view
[]
[:<>
[quo/page-nav
{:icon-name :i/close
:on-press events-helper/navigate-back}]
[quo/page-top
{:title (i18n/label :t/oops-not-keycard)
:description :text
:description-text (i18n/label :t/make-sure-keycard)}]
[rn/view {:style {:flex 1 :align-items :center :justify-content :center}}
[rn/image
{:resize-mode :contain
:source (resources/get-image :not-keycard)}]]
[rn/view {:padding-horizontal 20}
[quo/button {:on-press events-helper/navigate-back}
(i18n/label :t/try-again)]]])
(let [{:keys [top bottom]} (safe-area/get-insets)]
[quo/overlay
{:type :shell
:container-style {:padding-top top
:padding-bottom bottom}}
[quo/page-nav
{:key :header
:background :blur
:icon-name :i/arrow-left
:on-press events-helper/navigate-back}]
[quo/page-top
{:title (i18n/label :t/oops-not-keycard)
:description :text
:description-text (i18n/label :t/make-sure-keycard)}]
[rn/view {:flex 1}]
[rn/view {:padding-horizontal 20}
[quo/button {:on-press #(rf/dispatch [:keycard/connect])}
(i18n/label :t/try-again)]]]))
@@ -2,15 +2,12 @@
(:require [clojure.string :as string]
[quo.core :as quo]
[react-native.core :as rn]
[status-im.common.events-helper :as events-helper]
[status-im.constants :as constants]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
[utils.i18n :as i18n]))
(defn view
[]
(let [{:keys [on-complete]} (rf/sub [:get-screen-params])
[pin set-pin] (rn/use-state "")
[{:keys [on-complete]}]
(let [[pin set-pin] (rn/use-state "")
[first-pin set-first-pin] (rn/use-state "")
[error? set-error] (rn/use-state false)
[stage set-stage] (rn/use-state :create)
@@ -41,15 +38,10 @@
(set-stage :repeat)))))))
[pin stage first-pin])]
[rn/view {:style {:padding-bottom 12 :flex 1}}
[quo/page-nav
{:icon-name :i/close
:on-press events-helper/navigate-back}]
[quo/page-top
{:title (if (= :create stage)
(i18n/label :t/create-keycard-pin)
(i18n/label :t/repeat-keycard-pin))
:description :text
:description-text "Youll need this PIN to login and sign transactions"}]
[quo/drawer-top
{:title (if (= :create stage)
(i18n/label :t/create-keycard-pin)
(i18n/label :t/repeat-keycard-pin))}]
[rn/view {:style {:flex 1 :justify-content :center :align-items :center :padding-vertical 34}}
[quo/pin-input
{:blur? false
@@ -23,7 +23,3 @@
(assoc-in [:keycard :pin :status] nil))
:fx [(when (and on-complete (= (dec max-numbers) (count pin)))
[:effects.keycard.pin/dispatch-on-complete [on-complete new-pin]])]}))))
(rf/reg-event-fx :keycard.pin/clear
(fn [{:keys [db]}]
{:db (assoc-in db [:keycard :pin] nil)}))
+5 -9
View File
@@ -6,12 +6,10 @@
[utils.re-frame :as rf]))
(defn auth
[{:keys [on-complete error]}]
(let [{:keys [error? error-message]} error
{:keys [text status]} (rf/sub [:keycard/pin])
pin-retry-counter (rf/sub [:keycard/pin-retry-counter])
error? (or error? (= status :error))]
(rn/use-unmount #(rf/dispatch [:keycard.pin/clear]))
[{:keys [on-complete]}]
(let [{:keys [text status]} (rf/sub [:keycard/pin])
pin-retry-counter (rf/sub [:keycard/pin-retry-counter])
error? (= status :error)]
[rn/view {:padding-bottom 12 :flex 1}
[rn/view {:flex 1 :justify-content :center :align-items :center :padding 34}
[quo/pin-input
@@ -20,9 +18,7 @@
:number-of-filled-pins (count text)
:error? error?
:info (when error?
(if error-message
error-message
(i18n/label :t/pin-retries-left {:number pin-retry-counter})))}]]
(i18n/label :t/pin-retries-left {:number pin-retry-counter}))}]]
[quo/numbered-keyboard
{:delete-key? true
:on-delete #(rf/dispatch [:keycard.pin/delete-pressed])
@@ -1,14 +1,15 @@
(ns status-im.contexts.keycard.migrate.sheets.view
(ns status-im.contexts.keycard.sheets.migrate.view
(:require [quo.core :as quo]
[react-native.core :as rn]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn view
[{:keys [on-continue]}]
(let [profile-name (rf/sub [:profile/name])
profile-picture (rf/sub [:profile/image])
customization-color (rf/sub [:profile/customization-color])]
[]
(let [profile-name (rf/sub [:profile/name])
profile-picture (rf/sub [:profile/image])
customization-color (rf/sub [:profile/customization-color])
recovery-phrase-backed-up? (rf/sub [:profile/recovery-phrase-backed-up?])]
[:<>
[quo/drawer-top
{:type :context-tag
@@ -27,7 +28,11 @@
[quo/bottom-actions
{:actions :two-actions
:button-one-label (i18n/label :t/continue)
:button-one-props {:on-press on-continue}
:button-one-props {:on-press #(if recovery-phrase-backed-up?
(rf/dispatch [:open-modal :screen/use-recovery-phrase
{:on-success (fn [])}])
(rf/dispatch [:open-modal :screen/backup-recovery-phrase
{:on-success (fn [])}]))}
:button-two-label (i18n/label :t/cancel)
:button-two-props {:type :grey
:on-press #(rf/dispatch [:hide-bottom-sheet])}}]]))
+13 -17
View File
@@ -8,10 +8,6 @@
:s (subs signature 64 128)
:v (str (js/parseInt (subs signature 128 130) 16))}})
(rf/reg-event-fx :keycard/sign
(fn [_ [data]]
{:effects.keycard/sign data}))
(rf/reg-event-fx :keycard/sign-hash
(fn [{:keys [db]} [pin-text tx-hash]]
(let [current-address (get-in db [:wallet :current-viewing-account-address])
@@ -19,16 +15,16 @@
key-uid (get-in db [:profile/profile :key-uid])]
{:fx [[:dispatch
[:keycard/connect
{:key-uid key-uid
:on-success
(fn []
(rf/dispatch
[:keycard/sign
{:pin pin-text
:path path
:hash (utils.address/naked-address tx-hash)
:on-success (fn [signature]
(rf/dispatch [:keycard/disconnect])
(rf/dispatch [:wallet/proceed-with-transactions-signatures
(get-signature-map tx-hash signature)]))
:on-failure #(rf/dispatch [:keycard/on-action-with-pin-error %])}]))}]]]})))
{:key-uid key-uid
:on-success-fx [[:effects.keycard/sign
{:pin pin-text
:path path
:hash (utils.address/naked-address tx-hash)
:on-success
#(do
(rf/dispatch [:keycard/disconnect])
(rf/dispatch
[:wallet/proceed-with-transactions-signatures
(get-signature-map tx-hash %)]))
:on-failure #(rf/dispatch [:keycard/on-action-with-pin-error
%])}]]}]]]})))
+25 -49
View File
@@ -1,7 +1,5 @@
(ns status-im.contexts.keycard.utils
(:require [clojure.string :as string]
[utils.address :as address]
[utils.transforms :as transforms]))
(:require [taoensso.timbre :as log]))
(def pin-mismatch-error #"Unexpected error SW, 0x63C(\d+)|wrongPIN\(retryCounter: (\d+)\)")
@@ -18,55 +16,33 @@
(re-matches #".*NFCError:100.*" error)))
(defn validate-application-info
[profile-key-uid
{:keys [key-uid has-master-key? paired? pin-retry-counter puk-retry-counter] :as application-info}]
[profile-key-uid {:keys [key-uid paired? pin-retry-counter puk-retry-counter] :as application-info}]
(let [profile-mismatch? (or (nil? profile-key-uid) (not= profile-key-uid key-uid))]
(log/debug "[keycard]" "login-with-keycard"
"empty application info" (empty? application-info)
"no key-uid" (empty? key-uid)
"profile-mismatch?" profile-mismatch?
"no pairing" paired?)
(cond
(empty? application-info)
:keycard/error.not-keycard
(cond
(empty? application-info)
:keycard/error.not-keycard
(empty? (:key-uid application-info))
:keycard/error.keycard-blank
(not has-master-key?)
:keycard/error.keycard-blank
profile-mismatch?
:keycard/error.keycard-wrong
(not= profile-key-uid key-uid)
:keycard/error.keycard-wrong-profile
(not paired?)
:keycard/error.keycard-unpaired
(not paired?)
:keycard/error.keycard-unpaired
(and (zero? pin-retry-counter)
(or (nil? puk-retry-counter)
(pos? puk-retry-counter)))
:keycard/error.keycard-frozen
(and (zero? pin-retry-counter)
(or (nil? puk-retry-counter)
(pos? puk-retry-counter)))
:keycard/error.keycard-frozen
(zero? puk-retry-counter)
:keycard/error.keycard-locked
(zero? puk-retry-counter)
:keycard/error.keycard-locked
:else
nil))
(defn- error-object->map
[^js object]
{:code (.-code object)
:error (.-message object)})
(defn normalize-key-uid
[{:keys [key-uid] :as data}]
(if (string/blank? key-uid)
data
(update data :key-uid address/normalized-hex)))
(defn get-on-success
[{:keys [on-success]}]
#(when on-success (on-success (normalize-key-uid (transforms/js->clj %)))))
(defn get-on-failure
[{:keys [on-failure]}]
#(when on-failure (on-failure (error-object->map %))))
(defn wrap-handlers
[args]
(assoc
args
:on-success (get-on-success args)
:on-failure (get-on-failure args)))
:else
nil)))
@@ -37,21 +37,24 @@
(defonce is-dragging? (atom nil))
(defonce drag-amount (atom nil))
;; Layout height calculation
;; 1. Always prefer on-layout height over window height, as some devices include status bar height in
;; the window while others do not.
;; https://github.com/status-im/status-mobile/issues/14633#issuecomment-1366191478
;; 2. This preference is unless on-layout is triggered with a random value.
;; https://github.com/status-im/status-mobile/issues/14849
;; To ensure that on-layout height falls within the actual height range, a difference between window
;; height and the maximum possible status bar height is allowed (assumed to be 60, as the Pixel emulator
;; has a height of 52).
(defn store-screen-height
[evt]
(let [window-height (or (:height (rn/get-window)) 0)
height (or (oget evt "nativeEvent" "layout" "height") 0)]
(when (and (not= height @shell.state/screen-height)
(< (Math/abs (- window-height height)) 60))
(let [window-height (:height (rn/get-window))
height (or (oget evt "nativeEvent" "layout" "height") 0)
width (or (oget evt "nativeEvent" "layout" "width") 0)]
;; Layout height calculation
;; 1. Make sure height is more than width, and on-layout is not fired while the
;; screen is horizontal
;; 2. Initialize values with 0 in case of nil
;; 3. In the case of notch devices, the dimensions height will be smaller than
;; on-layout,
;; (without status bar height included)
;; https://github.com/status-im/status-mobile/issues/14633
;; 4. In the case of devices without a notch, both heights should be the same,
;; but actual values differ in some pixels, so arbitrary 5 pixels is allowed
(when (and (> height width)
(>= (+ height 5) (or window-height 0))
(not= height @shell.state/screen-height))
(reset! shell.state/screen-height height)
(async-storage/set-item! :screen-height height))))
@@ -13,7 +13,3 @@
(rf/reg-fx :effects.profile/enable-local-notifications
(fn []
(native-module/start-local-notifications)))
(rf/reg-fx :effects.profile/convert-to-keycard-profile
(fn [{:keys [profile settings password new-password callback]}]
(native-module/convert-to-keycard-profile profile settings password new-password callback)))
@@ -1,6 +1,5 @@
(ns status-im.contexts.profile.profiles.view
(:require
[clojure.string :as string]
[native-module.core :as native-module]
[quo.core :as quo]
[react-native.core :as rn]
@@ -183,7 +182,7 @@
:render-fn profile-card}]]))
(defn password-input
[processing error]
[]
(let [auth-method (rf/sub [:auth-method])
on-press-biometrics (fn []
(rf/dispatch [:biometric/authenticate
@@ -194,31 +193,18 @@
%])}]))]
[standard-authentication/password-input
{:processing processing
:error error
:shell? true
{:shell? true
:blur? true
:on-press-biometrics (when (= auth-method constants/auth-method-biometric) on-press-biometrics)}]))
(defn- get-error-message
[error]
(if (and (some? error)
(or (= error "file is not a database")
(string/starts-with? (string/lower-case error) "failed")))
(i18n/label :t/oops-wrong-password)
error))
(defn login-section
[{:keys [show-profiles]}]
(let [{:keys [key-uid name keycard-pairing
(let [processing (rf/sub [:profile/login-processing])
{:keys [key-uid name keycard-pairing
customization-color]} (rf/sub [:profile/login-profile])
sign-in-enabled? (rf/sub [:sign-in-enabled?])
profile-picture (rf/sub [:profile/login-profiles-picture key-uid])
{:keys [error processing]} (rf/sub [:profile/login])
error-message (rn/use-memo #(get-error-message error) [error])
error {:error? (boolean (seq error-message))
:error-message error-message}
login-profile (rn/use-callback #(rf/dispatch [:profile.login/login]))]
login-multiaccount (rn/use-callback #(rf/dispatch [:profile.login/login]))]
[rn/keyboard-avoiding-view
{:style style/login-container
:keyboard-vertical-offset (- (safe-area/get-bottom))}
@@ -253,20 +239,17 @@
:card-style style/login-profile-card}]
(if keycard-pairing
[keycard.pin/auth
{:error error
:on-complete
(fn [pin]
{:on-complete
(fn [pin-text]
(rf/dispatch
[:keycard/connect
{:key-uid key-uid
:on-success
(fn []
(rf/dispatch
[:keycard/get-keys
{:pin pin
:on-success #(rf/dispatch [:keycard.login/on-get-keys-success %])
:on-failure #(rf/dispatch [:keycard/on-action-with-pin-error %])}]))}]))}]
[password-input processing error])]
{:key-uid key-uid
:on-success-fx [[:effects.keycard/get-keys
{:pin pin-text
:on-success #(rf/dispatch [:keycard.login/on-get-keys-success %])
:on-failure #(rf/dispatch [:keycard/on-action-with-pin-error
%])}]]}]))}]
[password-input])]
(when-not keycard-pairing
[quo/button
{:size 40
@@ -275,7 +258,7 @@
:accessibility-label :login-button
:icon-left :i/unlocked
:disabled? (or (not sign-in-enabled?) processing)
:on-press login-profile
:on-press login-multiaccount
:container-style {:margin-bottom (+ (safe-area/get-bottom) 12)}}
(i18n/label :t/log-in)])]))
@@ -4,7 +4,6 @@
[react-native.core :as rn]
[status-im.common.events-helper :as events-helper]
[status-im.common.resources :as resources]
[status-im.constants :as constants]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
@@ -28,11 +27,11 @@
:subtitle (i18n/label :t/secure-wallet-card)
:button-label (i18n/label :t/buy-keycard)
:accessibility-label :get-keycard
:image (resources/get-image :keycard-buy)
:on-press #(rf/dispatch [:browser.ui/open-url constants/get-keycard-url])}]
:image (resources/get-image :generate-keys)
:on-press #()}]
[rn/view {:style {:margin-top 24}}
[quo/text
{:style {:margin-bottom 1
{:style {:margin-bottom 12
:color colors/white-opa-70}
:size :paragraph-2
:weight :medium}
@@ -41,9 +41,10 @@
(for [label points]
^{:key label}
[quo/markdown-list
{:description (i18n/label label)
:blur? true
:type (when lock? :lock)}])]})
{:description (i18n/label label)
:blur? true
:type (when lock? :lock)
:container-style {:padding-top 8}}])]})
(defn- on-privacy-policy-press
[]
@@ -561,7 +561,7 @@
:error error-message}))
{:fx [[:dispatch
(cond
(and failure? swap?) [:wallet/swap-proposal-error error]
(and failure? swap?) [:wallet/swap-proposal-error error-message]
failure? [:wallet/suggested-routes-error error-message]
swap? [:wallet/swap-proposal-success (fix-routes data)]
:else [:wallet/suggested-routes-success (fix-routes data)
+47 -145
View File
@@ -27,14 +27,12 @@
:test-networks-enabled? test-networks-enabled?
:token-symbol (get-in data [:asset-to-pay :symbol])}))
network' (or network
(swap-utils/select-network asset-to-pay))
start-point (if open-new-screen? :action-menu :swap-button)]
(swap-utils/select-network asset-to-pay))]
{:db (-> db
(assoc-in [:wallet :ui :swap :asset-to-pay] asset-to-pay)
(assoc-in [:wallet :ui :swap :asset-to-receive] asset-to-receive)
(assoc-in [:wallet :ui :swap :network] network')
(assoc-in [:wallet :ui :swap :launch-screen] view-id)
(assoc-in [:wallet :ui :swap :start-point] start-point))
(assoc-in [:wallet :ui :swap :launch-screen] view-id))
:fx (if network'
[[:dispatch [:wallet/switch-current-viewing-account (:address account)]]
[:dispatch
@@ -42,13 +40,6 @@
[:open-modal :screen/wallet.setup-swap]
[:navigate-to-within-stack
[:screen/wallet.setup-swap :screen/wallet.swap-select-asset-to-pay]])]
[:dispatch
[:centralized-metrics/track :metric/swap-start
{:network (:chain-id network)
:pay_token (:symbol asset-to-pay)
:receive_token (:symbol asset-to-receive)
:start_point start-point
:launch_screen view-id}]]
[:dispatch [:wallet.swap/set-default-slippage]]]
[[:dispatch
[:show-bottom-sheet
@@ -66,36 +57,22 @@
(rf/reg-event-fx :wallet.swap/select-asset-to-pay
(fn [{:keys [db]} [{:keys [token]}]]
(let [previous-token (get-in db [:wallet :ui :swap :asset-to-pay])
network (get-in db [:wallet :ui :swap :network])]
{:db (update-in db
[:wallet :ui :swap]
#(-> %
(assoc :asset-to-pay token)
(dissoc :amount
:amount-hex
:last-request-uuid
:swap-proposal
:error-response
:loading-swap-proposal?
:approval-transaction-id
:approved-amount)))
:fx [[:dispatch
[:centralized-metrics/track :metric/swap-asset-to-pay-changed
{:network (:chain-id network)
:previous_token (:symbol previous-token)
:new_token (:symbol token)}]]]})))
{:db (update-in db
[:wallet :ui :swap]
#(-> %
(assoc :asset-to-pay token)
(dissoc :amount
:amount-hex
:last-request-uuid
:swap-proposal
:error-response
:loading-swap-proposal?
:approval-transaction-id
:approved-amount)))}))
(rf/reg-event-fx :wallet.swap/select-asset-to-receive
(fn [{:keys [db]} [{:keys [token]}]]
(let [previous-token (get-in db [:wallet :ui :swap :asset-to-receive])
network (get-in db [:wallet :ui :swap :network])]
{:db (assoc-in db [:wallet :ui :swap :asset-to-receive] token)
:fx [[:dispatch
[:centralized-metrics/track :metric/swap-asset-to-receive-changed
{:network (:chain-id network)
:previous_token (:symbol previous-token)
:new_token (:symbol token)}]]]})))
{:db (assoc-in db [:wallet :ui :swap :asset-to-receive] token)}))
(rf/reg-event-fx :wallet.swap/set-default-slippage
(fn [{:keys [db]}]
@@ -155,15 +132,9 @@
:last-request-uuid request-uuid
:amount amount
:amount-hex amount-in-hex
:loading-swap-proposal? true
:initial-response? true)
:loading-swap-proposal? true)
clean-approval-transaction?
(dissoc :approval-transaction-id :approved-amount :swap-proposal)))
:fx [[:dispatch
[:centralized-metrics/track :metric/swap-proposal-start
{:network swap-chain-id
:pay_token pay-token-id
:receive_token receive-token-id}]]]
:json-rpc/call [{:method "wallet_getSuggestedRoutesAsync"
:params params
:on-error (fn [error]
@@ -177,10 +148,6 @@
(fn [{:keys [db]} [swap-proposal]]
(let [last-request-uuid (get-in db [:wallet :ui :swap :last-request-uuid])
amount-hex (get-in db [:wallet :ui :swap :amount-hex])
asset-to-pay (get-in db [:wallet :ui :swap :asset-to-pay])
asset-to-receive (get-in db [:wallet :ui :swap :asset-to-receive])
network (get-in db [:wallet :ui :swap :network])
initial-response? (get-in db [:wallet :ui :swap :initial-response?])
view-id (:view-id db)
request-uuid (:uuid swap-proposal)
best-routes (:best swap-proposal)
@@ -196,45 +163,22 @@
:swap-proposal (when-not (empty? best-routes)
(assoc (first best-routes) :uuid request-uuid))
:error-response error-response
:loading-swap-proposal? false
:initial-response? false)}
(and initial-response? (seq best-routes))
(assoc :fx
[[:dispatch
[:centralized-metrics/track :metric/swap-proposal-received
{:network (:chain-id network)
:pay_token (:symbol asset-to-pay)
:receive_token (:symbol asset-to-receive)}]]])
(and initial-response? (empty? best-routes))
(assoc :fx
[[:dispatch
[:centralized-metrics/track :metric/swap-proposal-failed
{:error (:code error-response)}]]])
:loading-swap-proposal? false)}
;; Router is unstable and it can return a swap proposal and after auto-refetching it can
;; return an error. Ideally this shouldn't happen, but adding this behavior so if the
;; user is in swap confirmation screen or in token approval confirmation screen, we
;; navigate back to setup swap screen so proper error is displayed.
(and (empty? best-routes) (= view-id :screen/wallet.swap-set-spending-cap))
(assoc :fx
[[:dispatch
[:centralized-metrics/track :metric/swap-proposal-failed
{:error (:code error-response)}]]
[:dismiss-modal :screen/wallet.swap-set-spending-cap]])
(assoc :fx [[:dismiss-modal :screen/wallet.swap-set-spending-cap]])
(and (empty? best-routes) (= view-id :screen/wallet.swap-confirmation))
(assoc :fx
[[:dispatch
[:centralized-metrics/track :metric/swap-proposal-failed
{:error (:code error-response)}]]
[:navigate-back]]))))))
(assoc :fx [[:navigate-back]]))))))
(rf/reg-event-fx :wallet/swap-proposal-error
(fn [{:keys [db]} [error-response]]
(fn [{:keys [db]} [error-message]]
{:db (-> db
(update-in [:wallet :ui :swap] dissoc :route :swap-proposal)
(assoc-in [:wallet :ui :swap :loading-swap-proposal?] false)
(assoc-in [:wallet :ui :swap :error-response] error-response))
:fx [[:dispatch
[:centralized-metrics/track :metric/swap-proposal-failed {:error (:code error-response)}]]]}))
(assoc-in [:wallet :ui :swap :error-response] error-message))}))
(rf/reg-event-fx :wallet/stop-get-swap-proposal
(fn []
@@ -335,25 +279,16 @@
(-> amount-out
(number/hex->whole receive-token-decimals)
(money/to-fixed receive-token-decimals)))]
(rf/dispatch [:centralized-metrics/track
(if approval-required?
:metric/swap-approval-execution-start
:metric/swap-transaction-execution-start)
(cond-> {:network swap-chain-id
:pay_token token-id-from}
(not approval-required?)
(assoc :receive_token token-id-to))])
(rf/dispatch [:wallet.swap/add-authorized-transaction
(cond-> {:transaction result
:approval-transaction?
approval-required?}
(not approval-required?)
(assoc :swap-data
{:pay-token-symbol token-id-from
:pay-amount amount
{:pay-token-symbol token-id-from
:pay-amount amount
:receive-token-symbol token-id-to
:receive-amount receive-amount
:swap-chain-id swap-chain-id}))])
:receive-amount receive-amount}))])
(rf/dispatch [:hide-bottom-sheet])
(rf/dispatch [:dismiss-modal
(if approval-required?
@@ -373,15 +308,6 @@
:receive-amount receive-amount})}]
500)))))
:on-error (fn [error]
(rf/dispatch [:centralized-metrics/track
(if approval-required?
:metric/swap-approval-execution-failed
:metric/swap-transaction-execution-failed)
(cond-> {:network swap-chain-id
:error error
:pay_token token-id-from}
(not approval-required?)
(assoc :receive_token token-id-to))])
(log/error "failed swap transaction"
{:event :wallet/swap-transaction
:error error
@@ -416,26 +342,19 @@
(rf/reg-event-fx :wallet.swap/approve-transaction-update
(fn [{:keys [db]} [{:keys [status]}]]
(let [{:keys [amount asset-to-pay swap-proposal
network]} (get-in db [:wallet :ui :swap])
provider-name (:bridge-name swap-proposal)
token-symbol (:symbol asset-to-pay)
swap-chain-id (:chain-id network)
current-viewing-account-address (get-in db
[:wallet :current-viewing-account-address])
account-name (get-in db
[:wallet :accounts
current-viewing-account-address :name])
transaction-confirmed-or-failed? (#{:confirmed :failed} status)
transaction-confirmed? (= status :confirmed)]
(let [{:keys [amount asset-to-pay swap-proposal]} (get-in db [:wallet :ui :swap])
provider-name (:bridge-name swap-proposal)
token-symbol (:symbol asset-to-pay)
current-viewing-account-address (get-in db
[:wallet :current-viewing-account-address])
account-name (get-in db
[:wallet :accounts
current-viewing-account-address :name])
transaction-confirmed-or-failed? (#{:confirmed :failed} status)
transaction-confirmed? (= status :confirmed)]
(when transaction-confirmed-or-failed?
(cond-> {:fx
[[:dispatch
[:centralized-metrics/track :metric/swap-approval-execution-finished
{:network swap-chain-id
:pay_token token-symbol
:succeeded transaction-confirmed?}]]
[:dispatch
[:toasts/upsert
{:id :approve-transaction-update
:type (if transaction-confirmed? :positive :negative)
@@ -458,23 +377,16 @@
(rf/reg-event-fx :wallet.swap/swap-transaction-update
(fn [{:keys [db]} [{:keys [tx-hash status]}]]
(let [{:keys [pay-amount pay-token-symbol
receive-amount receive-token-symbol
swap-chain-id]} (get-in db
[:wallet :transactions tx-hash
:swap-data])
transaction-confirmed-or-failed? (#{:confirmed :failed} status)
transaction-confirmed? (= status :confirmed)]
receive-amount receive-token-symbol]} (get-in db
[:wallet :transactions tx-hash
:swap-data])
transaction-confirmed-or-failed? (#{:confirmed :failed} status)
transaction-confirmed? (= status :confirmed)]
(when transaction-confirmed-or-failed?
{:db (-> db
(update-in [:wallet :swap-transaction-ids] disj tx-hash)
(update-in [:wallet :transactions] dissoc tx-hash))
:fx [[:dispatch
[:centralized-metrics/track :metric/swap-transaction-execution-finished
{:network swap-chain-id
:pay_token pay-token-symbol
:receive_token receive-token-symbol
:succeeded transaction-confirmed?}]]
[:dispatch
[:toasts/upsert
{:id :swap-transaction-update
:type (if transaction-confirmed? :positive :negative)
@@ -489,13 +401,13 @@
(rf/reg-event-fx :wallet.swap/flip-assets
(fn [{:keys [db]}]
(let [{:keys [asset-to-pay asset-to-receive
swap-proposal amount network]} (get-in db [:wallet :ui :swap])
receive-token-decimals (:decimals asset-to-receive)
amount-out (when swap-proposal (:amount-out swap-proposal))
receive-amount (when amount-out
(-> amount-out
(number/hex->whole receive-token-decimals)
(money/to-fixed receive-token-decimals)))]
swap-proposal amount]} (get-in db [:wallet :ui :swap])
receive-token-decimals (:decimals asset-to-receive)
amount-out (when swap-proposal (:amount-out swap-proposal))
receive-amount (when amount-out
(-> amount-out
(number/hex->whole receive-token-decimals)
(money/to-fixed receive-token-decimals)))]
{:db (update-in db
[:wallet :ui :swap]
#(-> %
@@ -508,17 +420,7 @@
:loading-swap-proposal?
:last-request-uuid
:approved-amount
:approval-transaction-id)))
:fx [[:dispatch
[:centralized-metrics/track :metric/swap-asset-to-pay-changed
{:network (:chain-id network)
:previous_token (:symbol asset-to-pay)
:new_token (:symbol asset-to-receive)}]]
[:dispatch
[:centralized-metrics/track :metric/swap-asset-to-receive-changed
{:network (:chain-id network)
:previous_token (:symbol asset-to-receive)
:new_token (:symbol asset-to-pay)}]]]})))
:approval-transaction-id)))})))
(rf/reg-event-fx :wallet/end-swap-flow
(fn [{:keys [db]}]
@@ -40,7 +40,6 @@
(let [[search-text set-search-text] (rn/use-state "")
on-change-text #(set-search-text %)
on-close (fn []
(rf/dispatch [:centralized-metrics/track :metric/swap-closed])
(rf/dispatch [:wallet/clean-swap])
(rf/dispatch [:navigate-back]))]
[rn/safe-area-view {:style style/container}
@@ -29,9 +29,7 @@
(def ^:private default-token-symbol "ETH")
(defn- on-close
[start-point]
(when (= start-point :action-menu)
(rf/dispatch [:centralized-metrics/track :metric/swap-closed]))
[]
(rf/dispatch [:wallet/clean-swap-proposal {:clean-approval-transaction? true}])
(events-helper/navigate-back))
@@ -265,22 +263,16 @@
:text error-text}
pay-input-error?
(merge {:action? true
:on-button-press (fn []
(rf/dispatch [:centralized-metrics/track
:metric/swap-buy-assets])
(rf/dispatch [:show-bottom-sheet
{:content buy-token/view}]))
:on-button-press #(rf/dispatch [:show-bottom-sheet
{:content buy-token/view}])
:button-text (i18n/label :t/add-assets)})
(= error-response-code
constants/router-error-code-not-enough-native-balance)
(merge {:action? true
:on-button-press (fn []
(rf/dispatch [:centralized-metrics/track
:metric/swap-buy-eth])
(rf/dispatch
[:show-bottom-sheet
{:content (fn []
[buy-token/view])}]))
:on-button-press #(rf/dispatch
[:show-bottom-sheet
{:content (fn []
[buy-token/view])}])
:button-text (i18n/label :t/add-eth)}))]
(when (or pay-input-error? error-response)
[quo/alert-banner props])))
@@ -345,7 +337,6 @@
asset-to-pay (rf/sub [:wallet/swap-asset-to-pay])
asset-to-receive (rf/sub [:wallet/swap-asset-to-receive])
network (rf/sub [:wallet/swap-network])
start-point (rf/sub [:wallet/swap-start-point])
pay-input-amount (controlled-input/input-value pay-input-state)
pay-token-decimals (:decimals asset-to-pay)
pay-token-balance-selected-chain (number/convert-to-whole-number
@@ -472,7 +463,7 @@
[asset-to-receive])
[rn/view {:style style/container}
[account-switcher/view
{:on-press #(on-close start-point)
{:on-press on-close
:icon-name :i/arrow-left
:margin-top (safe-area/get-top)
:switcher-type :select-account
@@ -142,7 +142,6 @@
(rf/reg-event-fx :wallet-connect/approve-session-success
(fn [{:keys [db]} [session]]
(log/info "Successfully approved WalletConnect session" session)
(let [total-connected-dapps (data-store/get-total-connected-dapps db)]
{:fx [[:dispatch [:wallet-connect/on-new-session session]]
[:dispatch [:wallet-connect/reset-current-session-proposal]]
@@ -54,7 +54,6 @@
(assoc session
:accounts
(-> sessionJson
transforms/json->clj
:namespaces
:eip155
:accounts)))
@@ -94,10 +93,10 @@
(defn sync-persisted-sessions
[active-sessions persisted-sessions]
(-> (promesa/all
(for [session (find-inactive-sessions active-sessions
persisted-sessions)]
(do (log/info "Syncing disconnected session with persistance" session)
(rpc/wallet-disconnect-persisted-session (:topic session)))))
(for [topic (find-inactive-sessions active-sessions
persisted-sessions)]
(do (log/info "Syncing disconnected session with persistance" topic)
(rpc/wallet-disconnect-persisted-session topic))))
(promesa/catch (fn [err]
(throw (ex-info "Failed to synchronize persisted sessions"
{:error err
@@ -108,11 +107,7 @@
(promesa/let [persisted-sessions (get-persisted-sessions)]
(if online?
(promesa/let [active-sessions (get-active-sessions web3-wallet addresses)]
(log/info "Got active Wallet Connect sessions" (map :topic active-sessions))
;; NOTE: handling the error here, so that if persistance fails, it doesn't affect the active
;; sessions
(-> (sync-persisted-sessions active-sessions persisted-sessions)
(promesa/catch #(log/error %)))
(sync-persisted-sessions active-sessions persisted-sessions)
active-sessions)
persisted-sessions)))
+3 -1
View File
@@ -30,7 +30,8 @@
[status-im.setup.global-error :as global-error]
[status-im.setup.interceptors :as interceptors]
status-im.subs.root
[utils.i18n :as i18n]))
[utils.i18n :as i18n]
[status-im.setup.status-backend-client :as status-backend-client]))
;;;; re-frame RN setup
(set! interop/next-tick js/setTimeout)
@@ -46,6 +47,7 @@
(defn init
[]
(status-backend-client/init)
(navigation/init)
(native-module/init #(re-frame/dispatch [:signals/signal-received %]))
(when platform/android?
+6
View File
@@ -69,6 +69,12 @@
transitions/new-to-status-modal-animations
transitions/push-animations-for-transparent-background)}))
(def keycard-modal-screen-options
(assoc transparent-modal-screen-options
:layout nil
:theme :dark
:insets {:top? true :bottom? true}))
(def sheet-options
{:layout {:componentBackgroundColor :transparent
:orientation ["portrait"]
+13 -63
View File
@@ -32,12 +32,7 @@
[status-im.contexts.keycard.check.view :as keycard.check]
[status-im.contexts.keycard.empty.view :as keycard.empty]
[status-im.contexts.keycard.error.view :as keycard.error]
[status-im.contexts.keycard.migrate.fail.view :as keycard.migrate.fail]
[status-im.contexts.keycard.migrate.re-encrypting.view :as keycard.re-encrypting]
[status-im.contexts.keycard.migrate.success.view :as keycard.migrate.success]
[status-im.contexts.keycard.migrate.view :as keycard.migrate]
[status-im.contexts.keycard.not-keycard.view :as keycard.not-keycard]
[status-im.contexts.keycard.pin.create.view :as pin.create]
[status-im.contexts.onboarding.create-or-sync-profile.view :as create-or-sync-profile]
[status-im.contexts.onboarding.create-password.view :as create-password]
[status-im.contexts.onboarding.create-profile.view :as create-profile]
@@ -315,9 +310,8 @@
:component settings/view}
{:name :screen/settings.keycard
:metrics {:track? :true
:alias-id :settings.keycard}
:options {:insets {:top? true :bottom? true}}
:metrics {:track? :true}
:options options/keycard-modal-screen-options
:component settings.keycard/view}
{:name :edit-profile
@@ -883,73 +877,29 @@
(def keycard-screens
[{:name :screen/keycard.check
:metrics {:track? :true
:alias-id :keycard.check}
:options {:insets {:top? true :bottom? true}}
:metrics {:track? true}
:options options/keycard-modal-screen-options
:component keycard.check/view}
{:name :screen/keycard.empty
:metrics {:track? :true
:alias-id :keycard.empty}
:options {:insets {:top? true :bottom? true}}
:metrics {:track? true}
:options options/keycard-modal-screen-options
:component keycard.empty/view}
{:name :screen/keycard.error
:metrics {:track? :true
:alias-id :keycard.error}
:options {:insets {:top? true :bottom? true}}
:metrics {:track? true}
:options options/keycard-modal-screen-options
:component keycard.error/view}
{:name :screen/keycard.not-keycard
:metrics {:track? :true
:alias-id :keycard.not-keycard}
:options {:insets {:top? true :bottom? true}}
:metrics {:track? true}
:options options/keycard-modal-screen-options
:component keycard.not-keycard/view}
{:name :screen/keycard.authorise
:metrics {:track? :true
:alias-id :keycard.authorise}
:options {:insets {:top? true :bottom? true}}
:component keycard.authorise/view}
{:name :screen/keycard.migrate
:metrics {:track? :true
:alias-id :keycard.migrate}
:options {:insets {:top? true :bottom? true}}
:component keycard.migrate/view}
{:name :screen/keycard.re-encrypting
:metrics {:track? :true
:alias-id :keycard.re-encrypting}
:options {:insets {:top? true :bottom? true}
:popGesture false
:hardwareBackButton {:dismissModalOnPress false
:popStackOnPress false}}
:component keycard.re-encrypting/view}
{:name :screen/keycard.migrate.success
:metrics {:track? :true
:alias-id :keycard.migrate.success}
:options {:insets {:top? true :bottom? true}
:popGesture false
:hardwareBackButton {:dismissModalOnPress false
:popStackOnPress false}}
:component keycard.migrate.success/view}
{:name :screen/keycard.migrate.fail
:metrics {:track? :true
:alias-id :keycard.migrate.fail}
:options {:insets {:top? true :bottom? true}
:popGesture false
:hardwareBackButton {:dismissModalOnPress false
:popStackOnPress false}}
:component keycard.migrate.fail/view}
{:name :screen/keycard.pin.create
:metrics {:track? :true
:alias-id :keycard.pin.create}
:options {:insets {:top? true :bottom? true}}
:component pin.create/view}])
:metrics {:track? true}
:options options/keycard-modal-screen-options
:component keycard.authorise/view}])
(defn screens
[]
+1 -1
View File
@@ -11,7 +11,7 @@
[status-im.common.bottom-sheet-screen.view :as bottom-sheet-screen]
[status-im.common.bottom-sheet.view :as bottom-sheet]
[status-im.common.toasts.view :as toasts]
[status-im.contexts.keycard.nfc.sheets.view :as keycard.sheet]
[status-im.contexts.keycard.nfc-sheet.view :as keycard.sheet]
[status-im.navigation.screens :as screens]
[status-im.setup.hot-reload :as reloader]
[utils.re-frame :as rf]))
@@ -0,0 +1,22 @@
(ns status-im.setup.status-backend-client
(:require ["react-native" :as react-native]
[status-im.config :as config]))
(def default-config
{:server-enabled? (config/enabled? config/STATUS_BACKEND_SERVER_ENABLED)
:status-go-endpoint (str "http://" config/STATUS_BACKEND_SERVER_HOST "/statusgo/")
:signal-endpoint (str "ws://" config/STATUS_BACKEND_SERVER_HOST "/signals")
:root-data-dir config/STATUS_BACKEND_SERVER_ROOT_DATA_DIR})
(defn set-config!
[{:keys [server-enabled? status-go-endpoint signal-endpoint root-data-dir]}]
(when-let [client (.-StatusBackendClient (.-NativeModules react-native))]
(.configStatusBackendServer client
server-enabled?
status-go-endpoint
signal-endpoint
root-data-dir)))
(defn init
[]
(set-config! default-config))
-5
View File
@@ -27,11 +27,6 @@
:<- [:wallet/swap]
:-> :network)
(rf/reg-sub
:wallet/swap-start-point
:<- [:wallet/swap]
:-> :start-point)
(rf/reg-sub
:wallet/swap-error-response
:<- [:wallet/swap]
+12 -10
View File
@@ -213,16 +213,18 @@
(defn get-image-uri
[{:keys [type options]}
profile-picture-options]
((case type
:account get-account-image-uri
:contact get-contact-image-uri
:initials get-initials-avatar-uri
str)
(-> (merge options profile-picture-options)
(assoc :ring?
(if (nil? (:override-ring? options))
(:ring? profile-picture-options)
(:override-ring? options))))))
(let [uri ((case type
:account get-account-image-uri
:contact get-contact-image-uri
:initials get-initials-avatar-uri
str)
(-> (merge options profile-picture-options)
(assoc :ring?
(if (nil? (:override-ring? options))
(:ring? profile-picture-options)
(:override-ring? options)))))]
(println "Image URI:" uri)
uri))
(schema/=> get-image-uri
[:=>
+5 -12
View File
@@ -1,7 +1,6 @@
(ns utils.network.core
(:require
[clojure.string :as string]
[taoensso.timbre :as log]))
[clojure.string :as string]))
(def url-regex
#"https?://(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}(\.[a-z]{2,6})?\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)")
@@ -42,13 +41,7 @@
(map :error)
(not-any? identity)))
(defn fetch
[url {:keys [body] :as params} callback]
(let [js-params (cond-> params
(map? body) (update :body (comp js/JSON.stringify clj->js))
:always clj->js)]
(-> (js/fetch url js-params)
(.then (fn [response]
(.text response)))
(.then callback)
(.catch #(log/error (str "Error while fetching: " url) %)))))
(defn chain-id-available?
[current-networks network]
(let [chain-id (get-in network [:config :NetworkId])]
(every? #(not= chain-id (get-in % [1 :config :NetworkId])) current-networks)))
+3 -3
View File
@@ -3,7 +3,7 @@
"_comment": "Instead use: scripts/update-status-go.sh <rev>",
"owner": "status-im",
"repo": "status-go",
"version": "v3.5.0",
"commit-sha1": "39511298cdc8f50e7659ac8079d5bc8a4763ddc7",
"src-sha256": "1nn8gq9d91ix5mzndr306rq3n68rzj4drym3pfaxzaa73k4v9r07"
"version": "chore/create_v2_endpoint",
"commit-sha1": "e255fb8b1d588f1030fc3db4f123081c58814984",
"src-sha256": "05h1hg3944wg1wnz0wkslvxs27sz9h7aqb882m4ilbkagklfs0nz"
}
-20
View File
@@ -293,8 +293,6 @@
"cant-fetch-info": "Can't fetch info",
"cant-open-public-chat": "Can't open public chat",
"cant-report-bug": "Can't report a bug",
"cant-store-new-keys": "You cant use it to store new keys right now",
"cant-use-right-now": "You cant use it right now",
"card-is-blank": "This card is blank",
"card-reseted": "Card has been reseted",
"card-unpaired": "Card has been unpaired from current device",
@@ -784,8 +782,6 @@
"display-collectibles": "Display collectibles",
"do-not-cheat": "Don't try to cheat",
"do-not-cheat-description": "These 12 words give access to all of your funds so it is important that you write them in the correct order, take it seriously.",
"do-not-quit": "Do not quit the application or turn off your device. Doing so will lead to data corruption, loss of your Status profile and the inability to use Status.",
"do-not-share": "Do not share",
"done": "Done",
"dont-ask": "Don't ask me again",
@@ -1014,7 +1010,6 @@
"failed": "Failed",
"failed-on": "Failed on",
"failed-to-fetch-community": "Failed to fetch community",
"failed-to-migrate-key-pair": "Failed to migrate key pair",
"faq": "Frequently asked questions",
"fast": "Fast",
"favorite-communities": "Your favourite communities",
@@ -1322,7 +1317,6 @@
"key-name-error-too-short": "Key pair name must be at least {{count}} characters",
"key-on-device": "Private key is saved on this device",
"key-pair-imported-successfully": "{{name}} key pair imported successfully",
"key-pair-migrated-successfully": "Profile key pair successfully migrated",
"key-pair-name-updated": "Key pair name updated",
"key-pair-removed": "Key pair and derived accounts has been removed",
"key-pairs-successfully-imported": "{{count}} key pairs successfully imported",
@@ -1350,7 +1344,6 @@
"keycard-factory-reset-text": "Performing this will delete any mnemonic phrase stored on the card. Make sure you have a backup of the mnemonic phrase you've been using with this Keycard.",
"keycard-factory-reset-title": "Are you sure you want to perform a factory reset?",
"keycard-free-pairing-slots": "Keycard has {{n}} free pairing slots",
"keycard-full": "Keycard is full",
"keycard-has-multiaccount-on-it": "This card is full. Each card can hold one main key pair",
"keycard-init-description": "Put the card to the back of your phone to continue",
"keycard-init-title": "Looking for cards...",
@@ -1361,8 +1354,6 @@
"keycard-is-frozen-factory-reset": "Reset with mnemonic",
"keycard-is-frozen-reset": "Reset with PUK",
"keycard-is-frozen-title": "Keycard is frozen",
"keycard-locked": "Keycard is locked",
"keycard-not-empty": "Keycard is not empty",
"keycard-onboarding-finishing-header": "Finishing up",
"keycard-onboarding-intro-header": "Store your keys on Keycard",
"keycard-onboarding-intro-text": "Get ready, this might take a few minutes, but it's important to secure your account",
@@ -1459,8 +1450,6 @@
"log-in": "Log in",
"log-level": "Log level",
"log-level-settings": "Log level settings",
"log-out-remove": "Log out & Remove profile",
"log-out-remove-profile": "Log out and remove profile from this device",
"logged-in": "Logged in",
"logging": "Logging",
"logging-enabled": "Logging enabled?",
@@ -1729,7 +1718,6 @@
"no-fees": "No fees",
"no-group-chats": "No group chats",
"no-group-chats-description": "Much fun. Have friends. Wow!",
"no-key-pair-keycard": "There is no key pair on this Keycard",
"no-keycard-applet-on-card": "No Keycard applet on card",
"no-messages": "No messages",
"no-messages-description": "Heres a cat in a box instead",
@@ -1888,7 +1876,6 @@
"pairing-new-installation-detected-title": "New device detected",
"pairing-no-info": "No info",
"pairing-please-set-a-name": "Please set a name for your device.",
"pairing-slots-occupied": "All pairing slots are occupied",
"paraswap-error": "Paraswap error: {{paraswap-error}}",
"participate-in-the-metaverse": "Participate in the truly free metaverse",
"passphrase": "Passphrase",
@@ -2037,11 +2024,9 @@
"re-encrypt": "Re-encrypt",
"re-encrypt-data": "Re-encrypt your data",
"re-encrypt-key": "Re-encrypt your keys",
"re-encrypting-data": "Re-encrypting data",
"read": "Read",
"read-more": "Read more",
"ready-keycard": "Get your Keycard ready",
"ready-to-migrate-key-pair": "Ready to migrate profile key pair to the Keycard",
"ready-to-scan": "Ready to Scan",
"rearrange-categories": "Rearrange Categories",
"receive": "Receive",
@@ -2060,7 +2045,6 @@
"recover": "Recover",
"recover-key": "Access existing keys",
"recover-keycard-multiaccount-not-supported": "Keys for this account already exist and can't be added again. If you've lost your password, passcode or Keycard, uninstall the app, reinstall and access your keys by entering your seed phrase",
"recover-status-profile": "Recover your Status profile with recovery phrase",
"recover-with-keycard": "Recover with Keycard",
"recover-with-seed-phrase": "Recover with seed phrase",
"recovering-key": "Accessing keys...",
@@ -2186,7 +2170,6 @@
"scan-an-account-qr-code": "Scan an account QR code",
"scan-an-address-qr-code": "Scan an address QR code",
"scan-key-pairs-qr-code": "Scan key pairs QR code",
"scan-keycard": "Scan Keycard",
"scan-or-enter-a-sync-code": "Scan or enter a sync code",
"scan-or-enter-sync-code": "Scan or enter sync code",
"scan-or-enter-sync-code-seen-on-this-device": "Scan or enter sync code seen on this device",
@@ -2639,7 +2622,6 @@
"unknown-status-go-error": "Unknown status-go error",
"unlimited": "Unlimited",
"unlock": "Unlock",
"unlock-reset-instructions": "To unlock or factory reset the Keycard, please use the Status desktop app. If you'd like this feature on mobile, feel free to upvote them and discuss in the Status community.",
"unmute": "Unmute",
"unmute-channel": "Unmute channel",
"unmute-chat": "Unmute chat",
@@ -2678,7 +2660,6 @@
"use-as-profile-picture": "Use as profile picture",
"use-biometrics": "Use biometrics to fill in your password",
"use-keycard": "Use Keycard",
"use-keycard-for-status": "From now on, use this Keycard to login to Status and transact with derived accounts on all synced devices. To start using it, logout and log back in with this Keycard.",
"use-keycard-login-sign": "Use this Keycard to login and sign transactions",
"use-keycard-subtitle": "Keys will be stored on your Keycard",
"use-photo": "Use Photo",
@@ -2828,7 +2809,6 @@
"what-to-do": "What you would like to do?",
"what-we-will-receive": "What we will receive:",
"what-we-wont-receive": "What we won't receive:",
"what-you-can-do": "What you can do:",
"whats-on-your-mind": "Whats on your mind…",
"which-connection-to-use": "Which connection to use for syncing?",
"who-are-you-looking-for": "Who are you looking for ?",
+4 -4
View File
@@ -9469,10 +9469,10 @@ react-native-hole-view@^3.0.0-alpha4:
resolved "https://registry.yarnpkg.com/react-native-hole-view/-/react-native-hole-view-3.0.0-alpha4.tgz#b5f54df380636c3b4dfacd5c297117f068fa4531"
integrity sha512-pAIQCA13Dn42xLFsVziFXHRtTgRJ13SBrb1vwFG7cIFslMjmBhPdFlUrqYcN+wMTt2+OnZhRxgM4Yaf4+gARDg==
react-native-image-crop-picker@0.40.0:
version "0.40.0"
resolved "https://registry.yarnpkg.com/react-native-image-crop-picker/-/react-native-image-crop-picker-0.40.0.tgz#cf378a4cbdb64b9b6ff94399b8515ff3bdccbf9c"
integrity sha512-FpqiCS4CiPQp8qIorlELWKbpi1d9lyhJki9spZr4LShv9hk65GWo6tYJupfTV19o5XcRwJn8d4MHIUoyWMJRTQ==
react-native-image-crop-picker@0.41.2:
version "0.41.2"
resolved "https://registry.npmjs.org/react-native-image-crop-picker/-/react-native-image-crop-picker-0.41.2.tgz#824fa8fee8391fbb3e0b5ae2973221a2dff0cafb"
integrity sha512-GcDu/adXU/1y/MrxsbOfqcGRGWC2pTttt5VGy/jyRJ6GXfoC29fTQf8SG5kGtc5schSR6K+mKYO4uW6eJPljlQ==
react-native-image-resizer@^1.2.3:
version "1.2.3"