From 7acaff616744c3e01acc07260db84905df91e90a Mon Sep 17 00:00:00 2001 From: frank Date: Tue, 19 Nov 2024 18:50:01 +0800 Subject: [PATCH] feat_: support use status backend server (#21450) * chore_: use status.go v2 endpoint https://github.com/status-im/status-go/compare/39511298...e255fb8b * feat: use status backend server (#21550) * chore: add env variable STATUS_BACKEND_SERVER_IMAGE_SERVER_URI_PREFIX * update doc * fix_: image_server lint issue * chore_: update status-go version --- doc/use-status-backend-server.md | 53 ++++ ios/StatusIm/AppDelegate.mm | 9 +- .../status/ethereum/module/AccountManager.kt | 187 ++++++++----- .../status/ethereum/module/DatabaseManager.kt | 59 +++- .../ethereum/module/EncryptionUtils.java | 154 +++++++++-- .../im/status/ethereum/module/LogManager.kt | 7 +- .../status/ethereum/module/NetworkManager.kt | 93 +++++-- .../ethereum/module/StatusBackendClient.kt | 209 +++++++++++++++ .../im/status/ethereum/module/StatusModule.kt | 77 +++++- .../status/ethereum/module/StatusPackage.kt | 8 +- .../java/im/status/ethereum/module/Utils.kt | 77 +++++- .../ios/RCTStatus/AccountManager.m | 251 +++++++++++++++--- .../ios/RCTStatus/DatabaseManager.m | 55 +++- .../ios/RCTStatus/EncryptionUtils.m | 230 ++++++++++++---- .../ios/RCTStatus/LogManager.m | 50 ++-- .../ios/RCTStatus/NetworkManager.m | 144 +++++++--- .../ios/RCTStatus/RCTStatus.h | 1 + .../ios/RCTStatus/RCTStatus.m | 169 ++++++------ .../ios/RCTStatus/StatusBackendClient.h | 31 +++ .../ios/RCTStatus/StatusBackendClient.m | 246 +++++++++++++++++ .../react-native-status/ios/RCTStatus/Utils.h | 2 + .../react-native-status/ios/RCTStatus/Utils.m | 93 +++++-- scripts/lint/re-frame-in-quo-components.sh | 3 +- shadow-cljs.edn | 100 +++---- src/status_im/config.cljs | 13 + src/status_im/core.cljs | 4 +- .../setup/status_backend_client.cljs | 22 ++ src/utils/image_server.cljs | 6 +- status-go-version.json | 6 +- 29 files changed, 1925 insertions(+), 434 deletions(-) create mode 100644 doc/use-status-backend-server.md create mode 100644 modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusBackendClient.kt create mode 100644 modules/react-native-status/ios/RCTStatus/StatusBackendClient.h create mode 100644 modules/react-native-status/ios/RCTStatus/StatusBackendClient.m create mode 100644 src/status_im/setup/status_backend_client.cljs diff --git a/doc/use-status-backend-server.md b/doc/use-status-backend-server.md new file mode 100644 index 0000000000..d89ab9bca8 --- /dev/null +++ b/doc/use-status-backend-server.md @@ -0,0 +1,53 @@ +## 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. +NOTE: not all the native functions used `StatusBackendClient`, only the corresponding functions in `native-module.core` has usages should be supported delegated to ATM. + +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 +``` +However, there is restriction when use adb reverse, we use random port for media server. So I'd suggest use "10.0.2.2:60000" as `STATUS_BACKEND_SERVER_HOST`. + +### Debug status-go using IDEA +Assume you've already set up the development environment for status-go, open status-go project use IDEA, run `make generate` with terminal in the status-go project root directory to ensure all the generated files are up to date, then open `cmd/status-backend/main.go` file, navigate to function `main()`, click the green play button on the left of `main()`, choose `Modify Run Configuration`, in `Program arguments` section, add `--address=localhost:60000 --media-https=false`, then click `OK`, finally click the green play button on the left of `main()` again and choose `Debug ...`. +Basically, you don't have to run `make generate` again and again, just run it once at the beginning. So you can re-run and debug it faster! + +## Known Android simulator issues +- Issue#1: Android simulator may not display images due to TLS certificate validation issues with the image server + - solution: use http instead of https for media server with command: `MEDIA_HTTPS=false PORT=60000 make run-status-backend`, currently status-go does not support http for media server. You have to use this draft [PR](https://github.com/status-im/status-go/pull/6060), you also need to set env variable `STATUS_BACKEND_SERVER_IMAGE_SERVER_URI_PREFIX` to "http://10.0.2.2:" so that `image_server.cljs` can work, and to make `/accountInitials` work, you need to copy `Inter-Medium.ttf` to your host machine from the android simulator, let's say you store it in `/Users/mac/Downloads/Inter-Medium.ttf`, then you need to update `get-font-file-ready` manually in `image_server.cljs` to return the correct path so that status backend server can access it. +- Issue#2: exportUnencryptedDatabaseV2/import-multiaccount does not work for android, probably cause of tech debt, I found it during creating the draft PR. +- Issue#3: unable to invoke `multiaccounts_storeIdentityImage` to change avatar image. + - The reason is that we path the absolute path of the image to the backend server, but the image file is stored in the android simulator. the backend server cannot access it as it runs in the host machine. + +If you're using ios simulator, you can skip above issues! + +## Details for issue#1 if you're interested +- we use `react-native-fast-image` which use okhttpclient behind +- we were using custom cert for https +- we fetch the custom cert through endpoint `ImageServerTLSCert` +- we fetched it through built-in status-go before, now we need to fetch it through status backend server +- we expect `OkHttpClientProvider.setOkHttpClientFactory(StatusOkHttpClientFactory())` to be invoked early(will trigger fetching wrong custom cert via built-in status-go since StatusBackendClient is not initialised yet!) in `MainApplication.kt` before executing clojure code, otherwise we will get black screen after `make run-android` . After deep research, I found there's no way to update the cert okhttpclient used to the correct one return from status backend server diff --git a/ios/StatusIm/AppDelegate.mm b/ios/StatusIm/AppDelegate.mm index 1bab746a97..d495507d97 100644 --- a/ios/StatusIm/AppDelegate.mm +++ b/ios/StatusIm/AppDelegate.mm @@ -29,6 +29,8 @@ #import #import +#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]; diff --git a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/AccountManager.kt b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/AccountManager.kt index 4ad0aee3b6..0a0bf47ce3 100644 --- a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/AccountManager.kt +++ b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/AccountManager.kt @@ -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 { diff --git a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/DatabaseManager.kt b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/DatabaseManager.kt index a0a21d2b3b..5188a260b0 100644 --- a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/DatabaseManager.kt +++ b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/DatabaseManager.kt @@ -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}") } } diff --git a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/EncryptionUtils.java b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/EncryptionUtils.java index 66990f1503..a49551771c 100644 --- a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/EncryptionUtils.java +++ b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/EncryptionUtils.java @@ -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 diff --git a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/LogManager.kt b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/LogManager.kt index 1bf10fdf94..69bb36d737 100644 --- a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/LogManager.kt +++ b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/LogManager.kt @@ -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) diff --git a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/NetworkManager.kt b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/NetworkManager.kt index c566fe78f5..2392632a95 100644 --- a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/NetworkManager.kt +++ b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/NetworkManager.kt @@ -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 ) } } diff --git a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusBackendClient.kt b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusBackendClient.kt new file mode 100644 index 0000000000..f282910010 --- /dev/null +++ b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusBackendClient.kt @@ -0,0 +1,209 @@ +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 + + 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 { + statusgoFunction() + } + } + } + + 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 = false + @Volatile private var statusGoEndpoint: String? = null + @Volatile private var signalEndpoint: String? = null + @Volatile var rootDataDir: String? = null + + @ReactMethod + fun configStatusBackendServer( + serverEnabled: Boolean, + statusGoEndpoint: String, + signalEndpoint: String, + rootDataDir: String + ) { + configure(serverEnabled, statusGoEndpoint, signalEndpoint, rootDataDir) + } + + 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 { + 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) + } + } +} diff --git a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.kt b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.kt index 7cbf56f30f..720e89f9a8 100644 --- a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.kt +++ b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.kt @@ -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? { diff --git a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusPackage.kt b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusPackage.kt index 4acb25b910..fb8220c0b6 100644 --- a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusPackage.kt +++ b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusPackage.kt @@ -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 { @@ -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 diff --git a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/Utils.kt b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/Utils.kt index 5333266fb4..ffcd560eba 100644 --- a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/Utils.kt +++ b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/Utils.kt @@ -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, callback: Callback) { + fun executeRunnableStatusGoMethod(method: Supplier, 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 { @@ -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") + } } } diff --git a/modules/react-native-status/ios/RCTStatus/AccountManager.m b/modules/react-native-status/ios/RCTStatus/AccountManager.m index da7ab6d5f4..2848f136d7 100644 --- a/modules/react-native-status/ios/RCTStatus/AccountManager.m +++ b/modules/react-native-status/ios/RCTStatus/AccountManager.m @@ -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 diff --git a/modules/react-native-status/ios/RCTStatus/DatabaseManager.m b/modules/react-native-status/ios/RCTStatus/DatabaseManager.m index 69581bee07..9a27215725 100644 --- a/modules/react-native-status/ios/RCTStatus/DatabaseManager.m +++ b/modules/react-native-status/ios/RCTStatus/DatabaseManager.m @@ -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); + }]; } diff --git a/modules/react-native-status/ios/RCTStatus/EncryptionUtils.m b/modules/react-native-status/ios/RCTStatus/EncryptionUtils.m index d580dea59f..2c3e336afb 100644 --- a/modules/react-native-status/ios/RCTStatus/EncryptionUtils.m +++ b/modules/react-native-status/ios/RCTStatus/EncryptionUtils.m @@ -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 diff --git a/modules/react-native-status/ios/RCTStatus/LogManager.m b/modules/react-native-status/ios/RCTStatus/LogManager.m index a4d26fe267..99ffa9eb00 100644 --- a/modules/react-native-status/ios/RCTStatus/LogManager.m +++ b/modules/react-native-status/ios/RCTStatus/LogManager.m @@ -4,6 +4,7 @@ #import "Statusgo.h" #import "Utils.h" #import "SSZipArchive.h" +#import "StatusBackendClient.h" @implementation LogManager @@ -19,9 +20,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]; @@ -64,37 +63,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 diff --git a/modules/react-native-status/ios/RCTStatus/NetworkManager.m b/modules/react-native-status/ios/RCTStatus/NetworkManager.m index 142ff15ba6..6b76182dc6 100644 --- a/modules/react-native-status/ios/RCTStatus/NetworkManager.m +++ b/modules/react-native-status/ios/RCTStatus/NetworkManager.m @@ -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 diff --git a/modules/react-native-status/ios/RCTStatus/RCTStatus.h b/modules/react-native-status/ios/RCTStatus/RCTStatus.h index 473689bee4..54b63e14ea 100644 --- a/modules/react-native-status/ios/RCTStatus/RCTStatus.h +++ b/modules/react-native-status/ios/RCTStatus/RCTStatus.h @@ -5,5 +5,6 @@ #import "RCTLog.h" @interface Status : NSObject ++ (Status *)sharedInstance; - (void)handleSignal:(NSString *)signal; @end diff --git a/modules/react-native-status/ios/RCTStatus/RCTStatus.m b/modules/react-native-status/ios/RCTStatus/RCTStatus.m index 7149440534..00842f9dea 100644 --- a/modules/react-native-status/ios/RCTStatus/RCTStatus.m +++ b/modules/react-native-status/ios/RCTStatus/RCTStatus.m @@ -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 diff --git a/modules/react-native-status/ios/RCTStatus/StatusBackendClient.h b/modules/react-native-status/ios/RCTStatus/StatusBackendClient.h new file mode 100644 index 0000000000..6a4bee04ea --- /dev/null +++ b/modules/react-native-status/ios/RCTStatus/StatusBackendClient.h @@ -0,0 +1,31 @@ +#import +#import + +@interface StatusBackendClient : NSObject + +@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 diff --git a/modules/react-native-status/ios/RCTStatus/StatusBackendClient.m b/modules/react-native-status/ios/RCTStatus/StatusBackendClient.m new file mode 100644 index 0000000000..38fd25b0ce --- /dev/null +++ b/modules/react-native-status/ios/RCTStatus/StatusBackendClient.m @@ -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 diff --git a/modules/react-native-status/ios/RCTStatus/Utils.h b/modules/react-native-status/ios/RCTStatus/Utils.h index c7cf5f9d9b..9bca89d9ff 100644 --- a/modules/react-native-status/ios/RCTStatus/Utils.h +++ b/modules/react-native-status/ios/RCTStatus/Utils.h @@ -6,11 +6,13 @@ @interface Utils : NSObject ++ (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 diff --git a/modules/react-native-status/ios/RCTStatus/Utils.m b/modules/react-native-status/ios/RCTStatus/Utils.m index 6f0f35e1fa..e64c6c38e4 100644 --- a/modules/react-native-status/ios/RCTStatus/Utils.m +++ b/modules/react-native-status/ios/RCTStatus/Utils.m @@ -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 diff --git a/scripts/lint/re-frame-in-quo-components.sh b/scripts/lint/re-frame-in-quo-components.sh index bdb098ef82..2b89fbb41a 100755 --- a/scripts/lint/re-frame-in-quo-components.sh +++ b/scripts/lint/re-frame-in-quo-components.sh @@ -9,7 +9,8 @@ if test -n "$INVALID_CHANGES"; then exit 1 fi -INVALID_CHANGES2=$(grep -E -r '(status-im\.)' --include '*.cljs' --include '*.clj' './src/utils') +# Add exception for status-im.config in the utils package check +INVALID_CHANGES2=$(grep -E -r '(status-im\.)' --include '*.cljs' --include '*.clj' './src/utils' | grep -v 'status-im.config') if test -n "$INVALID_CHANGES2"; then echo "WARNING: status-im are not allowed in utils package" diff --git a/shadow-cljs.edn b/shadow-cljs.edn index 67d6f64d01..584d9b3061 100644 --- a/shadow-cljs.edn +++ b/shadow-cljs.edn @@ -51,53 +51,59 @@ ;; 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_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_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_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"} + :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_IMAGE_SERVER_URI_PREFIX + #shadow/env "STATUS_BACKEND_SERVER_IMAGE_SERVER_URI_PREFIX" + 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_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_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_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 diff --git a/src/status_im/config.cljs b/src/status_im/config.cljs index 54573c0019..d5a6db73c3 100644 --- a/src/status_im/config.cljs +++ b/src/status_im/config.cljs @@ -128,3 +128,16 @@ ;; 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 "") +;; if you're using android simulator, I suggest set the env variable to "http://10.0.2.2:" +(goog-define STATUS_BACKEND_SERVER_IMAGE_SERVER_URI_PREFIX "https://localhost:") diff --git a/src/status_im/core.cljs b/src/status_im/core.cljs index 04f1465c60..83c2173444 100644 --- a/src/status_im/core.cljs +++ b/src/status_im/core.cljs @@ -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? diff --git a/src/status_im/setup/status_backend_client.cljs b/src/status_im/setup/status_backend_client.cljs new file mode 100644 index 0000000000..e901b78cab --- /dev/null +++ b/src/status_im/setup/status_backend_client.cljs @@ -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)) diff --git a/src/utils/image_server.cljs b/src/utils/image_server.cljs index 5a5f58fd9a..e9e452d3a1 100644 --- a/src/utils/image_server.cljs +++ b/src/utils/image_server.cljs @@ -1,12 +1,16 @@ +;; Exception here as referrenced status-im.config, the implementation related to the image +;; server and the qr components leave something to be desired. Added an exception to the lint +;; rule as quick fix. Feel free to improve it if you're a brave soul refactor (ns utils.image-server (:require [quo.foundations.colors :as colors] [react-native.fs :as utils.fs] [react-native.platform :as platform] [schema.core :as schema] + [status-im.config :as config] [utils.datetime :as datetime])) -(def ^:const image-server-uri-prefix "https://localhost:") +(def ^:const image-server-uri-prefix config/STATUS_BACKEND_SERVER_IMAGE_SERVER_URI_PREFIX) (def ^:const account-images-action "/accountImages") (def ^:const account-initials-action "/accountInitials") (def ^:const contact-images-action "/contactImages") diff --git a/status-go-version.json b/status-go-version.json index 59995be78f..0ca1ab73a7 100644 --- a/status-go-version.json +++ b/status-go-version.json @@ -3,7 +3,7 @@ "_comment": "Instead use: scripts/update-status-go.sh ", "owner": "status-im", "repo": "status-go", - "version": "v3.6.0", - "commit-sha1": "00a8a72ac2cc0933503cdf88cc5eafa45ab5e182", - "src-sha256": "0ka6c7bg8mnwqpyn8rv15cba537vp2fm8yxlyl5s17lwai5hqnk5" + "version": "v3.8.0", + "commit-sha1": "b3dbe91b5fab4fdab475ee94549629a6d2988ccc", + "src-sha256": "0p104qd799rwhiqxfdkd6gh4phsby6zyay98mv0sapjxim3nz4xi" }