From 1840c4288e92d63cb04c25e80ad53f365f2de7f9 Mon Sep 17 00:00:00 2001 From: frank Date: Thu, 31 Oct 2024 21:50:07 +0800 Subject: [PATCH 1/4] feat: use status backend server --- doc/use-status-backend-server.md | 53 ++++ ios/StatusIm/AppDelegate.mm | 9 +- .../status/ethereum/module/AccountManager.kt | 173 +++++++----- .../status/ethereum/module/DatabaseManager.kt | 30 ++- .../ethereum/module/EncryptionUtils.java | 118 +++++++-- .../im/status/ethereum/module/LogManager.kt | 7 +- .../status/ethereum/module/NetworkManager.kt | 74 ++++-- .../ethereum/module/StatusBackendClient.kt | 209 +++++++++++++++ .../im/status/ethereum/module/StatusModule.kt | 59 ++++- .../status/ethereum/module/StatusPackage.kt | 8 +- .../java/im/status/ethereum/module/Utils.kt | 70 ++++- .../ios/RCTStatus/AccountManager.m | 225 +++++++++++++--- .../ios/RCTStatus/DatabaseManager.m | 22 +- .../ios/RCTStatus/EncryptionUtils.m | 169 ++++++++---- .../ios/RCTStatus/LogManager.m | 50 ++-- .../ios/RCTStatus/NetworkManager.m | 97 ++++--- .../ios/RCTStatus/RCTStatus.h | 1 + .../ios/RCTStatus/RCTStatus.m | 130 ++++----- .../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 | 54 +++- shadow-cljs.edn | 98 +++---- src/status_im/config.cljs | 11 + src/status_im/core.cljs | 4 +- .../setup/status_backend_client.cljs | 22 ++ status-go-version.json | 6 +- 27 files changed, 1552 insertions(+), 426 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 00000000000..287c39cceb1 --- /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 update `(def ^:const image-server-uri-prefix "http://10.0.2.2:")` manually in `image_server.cljs`, 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#3 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 1bab746a97b..d495507d97a 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 @@ + (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didRece 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 6dc78e637ba..0a0bf47ce3c 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 @@ -229,8 +213,10 @@ class AccountManager(private val reactContext: ReactApplicationContext) : ReactC jsonParams.put("address", address) jsonParams.put("password", password) - utils.executeRunnableStatusGoMethod( - { Statusgo.verifyAccountPasswordV2(jsonParams.toString()) }, + StatusBackendClient.executeStatusGoRequestWithCallback( + endpoint = "VerifyAccountPasswordV2", + requestBody = jsonParams.toString(), + statusgoFunction = { Statusgo.verifyAccountPasswordV2(jsonParams.toString()) }, callback ) } @@ -241,8 +227,12 @@ class AccountManager(private val reactContext: ReactApplicationContext) : ReactC jsonParams.put("keyUID", keyUID) jsonParams.put("password", password) - utils.executeRunnableStatusGoMethod( - { Statusgo.verifyDatabasePasswordV2(jsonParams.toString()) }, + val jsonString = jsonParams.toString() + + StatusBackendClient.executeStatusGoRequestWithCallback( + endpoint = "VerifyDatabasePasswordV2", + requestBody = jsonString, + statusgoFunction = { Statusgo.verifyDatabasePasswordV2(jsonString) }, callback ) } @@ -258,63 +248,103 @@ 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 @@ -325,17 +355,29 @@ class AccountManager(private val reactContext: ReactApplicationContext) : ReactC put("keyStoreDir", keyStoreDir) } val jsonString = params.toString() - utils.executeRunnableStatusGoMethod({ Statusgo.deleteMultiaccountV2(jsonString) }, callback) + 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 ) @@ -343,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 737edea60a5..5188a260b0f 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 @@ -19,6 +19,11 @@ 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) } @@ -41,14 +46,12 @@ class DatabaseManager(private val reactContext: ReactApplicationContext) : React } val jsonParams = params.toString() - - val result = Statusgo.exportUnencryptedDatabaseV2(jsonParams) - if (result.startsWith("{\"error\":\"\"")) { - Log.d(TAG, "Export result: $result") - } else { - Log.e(TAG, "Export failed: $result") - } - + StatusBackendClient.executeStatusGoRequestWithCallback( + endpoint = "ExportUnencryptedDatabaseV2", + requestBody = jsonParams, + statusgoFunction = { Statusgo.exportUnencryptedDatabaseV2(jsonParams) }, + callback = null + ) callback.invoke(newFile.absolutePath) } catch (e: JSONException) { @@ -75,12 +78,11 @@ class DatabaseManager(private val reactContext: ReactApplicationContext) : React val jsonParams = params.toString() - val result = Statusgo.importUnencryptedDatabaseV2(jsonParams) - if (result.startsWith("{\"error\":\"\"")) { - Log.d(TAG, "Import result: $result") - } else { - Log.e(TAG, "Import failed: $result") - } + 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 024f26b1c6d..a49551771c6 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 @@ -40,7 +40,12 @@ private void initKeystore(final String keyUID, final Callback callback) throws J 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 @@ -50,7 +55,12 @@ public void reEncryptDbAndKeystore(final String keyUID, final String password, f params.put("oldPassword", password); params.put("newPassword", newPassword); String jsonParams = params.toString(); - this.utils.executeRunnableStatusGoMethod(() -> Statusgo.changeDatabasePasswordV2(jsonParams), callback); + StatusBackendClient.executeStatusGoRequestWithCallback( + "ChangeDatabasePasswordV2", + jsonParams, + () -> Statusgo.changeDatabasePasswordV2(jsonParams), + callback + ); } @ReactMethod @@ -65,10 +75,17 @@ public void convertToKeycardAccount(final String keyUID, final String accountDat params.put("oldPassword", password); params.put("newPassword", newPassword); final String jsonParams = params.toString(); - this.utils.executeRunnableStatusGoMethod(() -> { - Statusgo.initKeystore(keyStoreDir); - return Statusgo.convertToKeycardAccountV2(jsonParams); - }, callback); + StatusBackendClient.executeStatusGoRequest( + "InitKeystore", + keyStoreDir, + () -> Statusgo.initKeystore(keyStoreDir) + ); + StatusBackendClient.executeStatusGoRequestWithCallback( + "ConvertToKeycardAccountV2", + jsonParams, + () -> Statusgo.convertToKeycardAccountV2(jsonParams), + callback + ); } @ReactMethod(isBlockingSynchronousMethod = true) @@ -78,7 +95,11 @@ public String encodeTransfer(final String to, final String value) { params.put("to", to); params.put("value", value); String jsonParams = params.toString(); - return Statusgo.encodeTransferV2(jsonParams); + return StatusBackendClient.executeStatusGoRequestWithResult( + "EncodeTransferV2", + jsonParams, + () -> Statusgo.encodeTransferV2(jsonParams) + ); } catch (JSONException e) { Log.e(TAG, "Error creating JSON for encodeTransfer: " + e.getMessage()); return null; @@ -92,7 +113,11 @@ public String encodeFunctionCall(final String method, final String paramsJSON) { params.put("method", method); params.put("paramsJSON", new JSONObject(paramsJSON)); String jsonString = params.toString(); - return Statusgo.encodeFunctionCallV2(jsonString); + return StatusBackendClient.executeStatusGoRequestWithResult( + "EncodeFunctionCallV2", + jsonString, + () -> Statusgo.encodeFunctionCallV2(jsonString) + ); } catch (JSONException e) { Log.e(TAG, "Error creating JSON for encodeFunctionCall: " + e.getMessage()); return null; @@ -101,37 +126,65 @@ public String encodeFunctionCall(final String method, final String paramsJSON) { @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 @@ -162,12 +215,22 @@ public void run() { @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 @@ -176,12 +239,22 @@ public void multiformatDeserializePublicKey(final String multiCodecKey, final St params.put("key", multiCodecKey); params.put("outBase", base58btc); String jsonParams = params.toString(); - this.utils.executeRunnableStatusGoMethod(() -> Statusgo.multiformatDeserializePublicKeyV2(jsonParams), callback); + 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 @@ -196,7 +269,12 @@ public void hashTypedDataV4(final String data, final Callback callback) throws J @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 1bf10fdf94a..69bb36d7372 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 484e2ac0bc5..2392632a958 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,9 +31,12 @@ 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 ) } @@ -41,16 +49,20 @@ class NetworkManager(private val reactContext: ReactApplicationContext) : ReactC put("receiverClientConfig", receiverClientConfig) } val jsonString = params.toString() - utils.executeRunnableStatusGoMethod( - { Statusgo.inputConnectionStringForBootstrappingV2(jsonString) }, + 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 ) } @@ -62,22 +74,42 @@ class NetworkManager(private val reactContext: ReactApplicationContext) : ReactC put("password", password) } val jsonString = jsonParams.toString() - utils.executeRunnableStatusGoMethod({ Statusgo.sendTransactionV2(jsonString) }, callback) + 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 @@ -87,10 +119,14 @@ 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 @@ -104,10 +140,12 @@ class NetworkManager(private val reactContext: ReactApplicationContext) : ReactC put("connectionString", connectionString) put("keystoreFilesReceiverClientConfig", keystoreFilesReceiverClientConfig) } - - utils.executeRunnableStatusGoMethod( - { Statusgo.inputConnectionStringForImportingKeypairsKeystoresV2(params.toString()) }, - callback + 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 00000000000..f2829100100 --- /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 ddd8e77bdf6..720e89f9a80 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 @@ -16,6 +16,10 @@ class StatusModule(private val reactContext: ReactApplicationContext, private va companion object { private const val TAG = "StatusModule" private var module: StatusModule? = null + + fun getInstance(): StatusModule? { + return module + } } private val utils: Utils = Utils(reactContext) @@ -61,7 +65,13 @@ class StatusModule(private val reactContext: ReactApplicationContext, private va put("type", type) put("expensive", isExpensive) } - Statusgo.connectionChangeV2(params.toString()) + + val jsonString = params.toString() + StatusBackendClient.executeStatusGoRequest( + endpoint = "ConnectionChangeV2", + requestBody = jsonString, + statusgoFunction = { Statusgo.connectionChangeV2(jsonString) } + ) } @ReactMethod @@ -70,28 +80,52 @@ class StatusModule(private val reactContext: ReactApplicationContext, private va val params = JSONObject().apply { put("state", state) } - Statusgo.appStateChangeV2(params.toString()) + 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 @@ -103,12 +137,21 @@ class StatusModule(private val reactContext: ReactApplicationContext, private va put("keyStoreDir", keyStoreDir) } val jsonString = params.toString() - utils.executeRunnableStatusGoMethod({ Statusgo.deleteImportedKeyV2(jsonString) }, callback) + 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 4acb25b9107..fb8220c0b66 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 94689f3cd47..ffcd560ebad 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) @@ -74,8 +84,17 @@ class Utils(private val reactContext: ReactApplicationContext) : ReactContextBas jsonParams.put("password", password) jsonParams.put("oldDir", commonKeydir) jsonParams.put("newDir", keydir) - Statusgo.migrateKeyStoreDirV2(jsonParams.toString()) - Statusgo.initKeystore(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}") @@ -104,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) @@ -122,7 +141,13 @@ class Utils(private val reactContext: ReactApplicationContext) : ReactContextBas fun validateMnemonic(seed: String, callback: Callback) { val jsonParams = JSONObject() jsonParams.put("mnemonic", seed) - executeRunnableStatusGoMethod({ Statusgo.validateMnemonicV2(jsonParams.toString()) }, callback) + val jsonString = jsonParams.toString() + StatusBackendClient.executeStatusGoRequestWithCallback( + endpoint = "ValidateMnemonicV2", + requestBody = jsonString, + statusgoFunction = { Statusgo.validateMnemonicV2(jsonString) }, + callback + ) } fun is24Hour(): Boolean { @@ -131,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 { @@ -157,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 fc0ab3cd0be..2848f136d76 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 @@ @implementation AccountManager #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]) @@ -113,8 +120,13 @@ -(NSString *) prepareDirAndUpdateConfig:(NSString *)config return; } NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; - NSString *result = StatusgoDeleteMultiaccountV2(jsonString); - callback(@[result]); + + [StatusBackendClient executeStatusGoRequestWithCallback:@"DeleteMultiaccountV2" + body:jsonString + statusgoFunction:^NSString *{ + return StatusgoDeleteMultiaccountV2(jsonString); + } + callback:callback]; } RCT_EXPORT_METHOD(prepareDirAndUpdateConfig:(NSString *)keyUID @@ -174,10 +186,13 @@ -(NSString *) prepareDirAndUpdateConfig:(NSString *)config 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 @@ -186,22 +201,28 @@ -(NSString *) prepareDirAndUpdateConfig:(NSString *)config #if DEBUG NSLog(@"VerifyAccountPasswordV2() method called"); #endif - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSURL *rootUrl =[[fileManager - URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask] - lastObject]; - NSURL *absKeystoreUrl = [rootUrl URLByAppendingPathComponent:@"keystore"]; + NSURL *rootUrl = [Utils getRootUrl]; + NSString *keystorePath = [rootUrl.path stringByAppendingPathComponent:@"keystore"]; NSDictionary *params = @{ - @"keyStoreDir": absKeystoreUrl.path, + @"keyStoreDir": keystorePath, @"address": address, @"password": password }; - NSData *jsonData = [NSJSONSerialization dataWithJSONObject:params options:0 error:nil]; + 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 = StatusgoVerifyAccountPasswordV2(jsonString); - callback(@[result]); + [StatusBackendClient executeStatusGoRequestWithCallback:@"VerifyAccountPasswordV2" + body:jsonString + statusgoFunction:^NSString *{ + return StatusgoVerifyAccountPasswordV2(jsonString); + } + callback:callback]; } RCT_EXPORT_METHOD(verifyDatabasePassword:(NSString *)keyUID @@ -214,11 +235,20 @@ -(NSString *) prepareDirAndUpdateConfig:(NSString *)config @"keyUID": keyUID, @"password": password }; - NSData *jsonData = [NSJSONSerialization dataWithJSONObject:params options:0 error:nil]; + 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 = StatusgoVerifyDatabasePasswordV2(jsonString); - callback(@[result]); + [StatusBackendClient executeStatusGoRequestWithCallback:@"VerifyDatabasePasswordV2" + body:jsonString + statusgoFunction:^NSString *{ + return StatusgoVerifyDatabasePasswordV2(jsonString); + } + callback:callback]; } RCT_EXPORT_METHOD(initializeApplication:(NSString *)request @@ -226,62 +256,173 @@ -(NSString *) prepareDirAndUpdateConfig:(NSString *)config #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 + [StatusBackendClient executeStatusGoRequest:@"Logout" + body:@"" + statusgoFunction:^NSString *{ + return StatusgoLogout(); + }]; +} + +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 - NSString *result = StatusgoLogout(); + [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]; +} - NSLog(@"%@", result); +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 c815212c4c3..9a27215725f 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(); @@ -34,7 +34,12 @@ @implementation DatabaseManager NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; - StatusgoExportUnencryptedDatabaseV2(jsonString); + [StatusBackendClient executeStatusGoRequestWithCallback:@"ExportUnencryptedDatabaseV2" + body:jsonString + statusgoFunction:^NSString *{ + return StatusgoExportUnencryptedDatabaseV2(jsonString); + } + callback:nil]; callback(@[filePath]); } @@ -44,9 +49,14 @@ @implementation DatabaseManager #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 + @"password": password, + @"databasePath": filePath }; NSError *error; @@ -59,7 +69,11 @@ @implementation DatabaseManager NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; - StatusgoImportUnencryptedDatabaseV2(jsonString); + [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 d100c908667..2c3e336afb3 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 @@ @implementation EncryptionUtils #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 @@ -53,9 +47,12 @@ @implementation EncryptionUtils } NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; - // Call ChangeDatabasePasswordV2 with JSON string param - NSString *result = StatusgoChangeDatabasePasswordV2(jsonString); - callback(@[result]); + [StatusBackendClient executeStatusGoRequestWithCallback:@"ChangeDatabasePasswordV2" + body:jsonString + statusgoFunction:^NSString *{ + return StatusgoChangeDatabasePasswordV2(jsonString); + } + callback:callback]; } RCT_EXPORT_METHOD(convertToKeycardAccount:(NSString *)keyUID @@ -69,12 +66,23 @@ @implementation EncryptionUtils NSLog(@"convertToKeycardAccount() method called"); #endif NSURL *multiaccountKeystoreDir = [Utils getKeyStoreDirForKeyUID:keyUID]; - StatusgoInitKeystore(multiaccountKeystoreDir.path); + // 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], + @"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 @@ -82,7 +90,6 @@ @implementation EncryptionUtils NSError *error; NSData *jsonData = [NSJSONSerialization dataWithJSONObject:params options:0 error:&error]; - if (error) { NSLog(@"Error creating JSON: %@", [error localizedDescription]); return; @@ -90,12 +97,16 @@ @implementation EncryptionUtils NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; - NSString *result = StatusgoConvertToKeycardAccountV2(jsonString); - callback(@[result]); + [StatusBackendClient executeStatusGoRequestWithCallback:@"ConvertToKeycardAccountV2" + body:jsonString + statusgoFunction:^NSString *{ + return StatusgoConvertToKeycardAccountV2(jsonString); + } + callback:callback]; } RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(encodeTransfer:(NSString *)to - value:(NSString *)value) { + value:(NSString *)value) { NSDictionary *params = @{ @"to": to, @"value": value @@ -107,11 +118,16 @@ @implementation EncryptionUtils return nil; } NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; - return StatusgoEncodeTransferV2(jsonString); + + return [StatusBackendClient executeStatusGoRequestWithResult:@"EncodeTransferV2" + body:jsonString + statusgoFunction:^NSString *{ + return StatusgoEncodeTransferV2(jsonString); + }]; } RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(encodeFunctionCall:(NSString *)method - paramsJSON:(NSString *)paramsJSON) { + paramsJSON:(NSString *)paramsJSON) { NSDictionary *params = @{ @"method": method, @"paramsJSON": paramsJSON @@ -123,35 +139,68 @@ @implementation EncryptionUtils return nil; } NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; - return StatusgoEncodeFunctionCallV2(jsonString); + + 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) @@ -166,10 +215,14 @@ @implementation EncryptionUtils 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 @@ -177,8 +230,12 @@ @implementation EncryptionUtils #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) { @@ -203,14 +260,23 @@ @implementation EncryptionUtils return; } NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; - NSString *result = StatusgoMultiformatDeserializePublicKeyV2(jsonString); - callback(@[result]); + + [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 @@ -233,13 +299,14 @@ @implementation EncryptionUtils #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 a4d26fe2671..99ffa9eb000 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 @@ @implementation LogManager #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 @@ @implementation LogManager 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 a9b653277ab..6b76182dc6b 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 @@ @implementation NetworkManager 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,8 +36,12 @@ @implementation NetworkManager [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 @@ -54,8 +67,12 @@ @implementation NetworkManager return; } NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; - NSString *result = StatusgoInputConnectionStringForBootstrappingV2(jsonString); - callback(@[result]); + [StatusBackendClient executeStatusGoRequestWithCallback:@"InputConnectionStringForBootstrappingV2" + body:jsonString + statusgoFunction:^NSString *{ + return StatusgoInputConnectionStringForBootstrappingV2(jsonString); + } + callback:callback]; } RCT_EXPORT_METHOD(sendTransactionWithSignature:(NSString *)txArgsJSON @@ -64,8 +81,12 @@ @implementation NetworkManager #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 @@ -95,28 +116,32 @@ @implementation NetworkManager return; } NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; - NSString *result = StatusgoSendTransactionV2(jsonString); - callback(@[result]); + [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 @@ -126,8 +151,12 @@ @implementation NetworkManager #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 @@ -144,8 +173,12 @@ @implementation NetworkManager [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 @@ -157,7 +190,7 @@ @implementation NetworkManager 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; @@ -170,8 +203,12 @@ @implementation NetworkManager }; NSString *paramsJSON = [Utils jsonStringWithPrettyPrint:NO fromDictionary:params]; - NSString *result = StatusgoInputConnectionStringForImportingKeypairsKeystoresV2(paramsJSON); - callback(@[result]); + [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 473689bee47..54b63e14eaf 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 83d08d2ab25..00842f9dea1 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) { @@ -78,71 +87,12 @@ - (void)handleSignal:(NSString *)signal NSURL *multiaccountKeystoreDir = [Utils getKeyStoreDirForKeyUID:keyUID]; NSString *jsonParams = [NSString stringWithFormat:@"{\"address\":\"%@\",\"password\":\"%@\",\"keyStoreDir\":\"%@\"}", address, password, multiaccountKeystoreDir.path]; - NSString *result = StatusgoDeleteImportedKeyV2(jsonParams); - 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]); + [StatusBackendClient executeStatusGoRequestWithCallback:@"DeleteImportedKeyV2" + body:jsonParams + statusgoFunction:^NSString *{ + return StatusgoDeleteImportedKeyV2(jsonParams); + } + callback:callback]; } #pragma mark - GetNodeConfig @@ -151,12 +101,20 @@ - (void)handleSignal:(NSString *)signal #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) { @@ -183,7 +141,11 @@ - (void)handleSignal:(NSString *)signal if (jsonData) { NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; - StatusgoConnectionChangeV2(jsonString); + [StatusBackendClient executeStatusGoRequest:@"ConnectionChangeV2" + body:jsonString + statusgoFunction:^NSString *{ + return StatusgoConnectionChangeV2(jsonString); + }]; } else { NSLog(@"Failed to create JSON data"); } @@ -205,7 +167,11 @@ - (void)handleSignal:(NSString *)signal if (jsonData) { NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; - StatusgoAppStateChangeV2(jsonString); + [StatusBackendClient executeStatusGoRequest:@"AppStateChangeV2" + body:jsonString + statusgoFunction:^NSString *{ + return StatusgoAppStateChangeV2(jsonString); + }]; } else { NSLog(@"Failed to create JSON data"); } @@ -216,8 +182,12 @@ - (void)handleSignal:(NSString *)signal #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 @@ -225,15 +195,23 @@ - (void)handleSignal:(NSString *)signal #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 00000000000..6a4bee04ea9 --- /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 00000000000..38fd25b0ce1 --- /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 c7cf5f9d9ba..9bca89d9ff5 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 10ef504abfd..e64c6c38e46 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 @@ + (NSString *)jsonStringWithPrettyPrint:(BOOL)prettyPrint fromArray:(NSArray *)a + (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 @@ + (NSString *) getKeyUID:(NSString *)jsonString { } + (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,9 +92,7 @@ + (NSString *) getExportDbFilePath { + (void) migrateKeystore:(NSString *)accountData password:(NSString *)password { NSFileManager *fileManager = [NSFileManager defaultManager]; - NSURL *rootUrl =[[fileManager - URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask] - lastObject]; + NSURL *rootUrl =[self getRootUrl]; NSData *jsonData = [accountData dataUsingEncoding:NSUTF8StringEncoding]; NSError *error; @@ -110,18 +130,19 @@ + (void) migrateKeystore:(NSString *)accountData } 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"]; @@ -161,4 +182,17 @@ + (void) migrateKeystore:(NSString *)accountData 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/shadow-cljs.edn b/shadow-cljs.edn index 67d6f64d016..3ddb308be3d 100644 --- a/shadow-cljs.edn +++ b/shadow-cljs.edn @@ -51,53 +51,57 @@ ;; 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_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 54573c00193..cd75da465da 100644 --- a/src/status_im/config.cljs +++ b/src/status_im/config.cljs @@ -128,3 +128,14 @@ ;; Alert banners are disabled for debug builds because alert banners overlay ;; interfere with react-native debug tools, such as inspector and Perf monitor (def enable-alert-banner? (enabled? (get-config :ENABLE_ALERT_BANNER "0"))) + +;; enable using status backend server or not, otherwise it will use built-in status-go library +;; see doc/use-status-backend-server.md for more details +(goog-define STATUS_BACKEND_SERVER_ENABLED "0") +;; The host should contain an IP address and a port separated by a colon. +;; The port comes from your running status backend server. +;; If you run it by PORT=60000 make run-status-backend , then host will likely be 127.0.0.1:60000 +(goog-define STATUS_BACKEND_SERVER_HOST "") +;; /path/to/root/data/dir +;; make sure it exists, it should be in absolute path +(goog-define STATUS_BACKEND_SERVER_ROOT_DATA_DIR "") diff --git a/src/status_im/core.cljs b/src/status_im/core.cljs index 04f1465c609..83c21734443 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 00000000000..e901b78cab7 --- /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/status-go-version.json b/status-go-version.json index 7492b0af388..878fa8b9ab2 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": "chore/create_v2_endpoint", - "commit-sha1": "e255fb8b1d588f1030fc3db4f123081c58814984", - "src-sha256": "05h1hg3944wg1wnz0wkslvxs27sz9h7aqb882m4ilbkagklfs0nz" + "version": "feat/support-http-media-server", + "commit-sha1": "50cfb6d9305d917401bb95299494bd7e5679e118", + "src-sha256": "1kh96v39nx87z39i08h7mkvbfaih68cp5izhmn5ppg7qhrfqhmlc" } From 29b84a206822222a74762dbb5774690892b54142 Mon Sep 17 00:00:00 2001 From: frank Date: Tue, 12 Nov 2024 19:33:33 +0800 Subject: [PATCH 2/4] chore: add env variable STATUS_BACKEND_SERVER_IMAGE_SERVER_URI_PREFIX --- doc/use-status-backend-server.md | 2 +- shadow-cljs.edn | 36 +++++++++++++++++--------------- src/status_im/config.cljs | 2 ++ src/utils/image_server.cljs | 3 ++- 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/doc/use-status-backend-server.md b/doc/use-status-backend-server.md index 287c39cceb1..a855dec0e54 100644 --- a/doc/use-status-backend-server.md +++ b/doc/use-status-backend-server.md @@ -38,7 +38,7 @@ Basically, you don't have to run `make generate` again and again, just run it on ## 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 update `(def ^:const image-server-uri-prefix "http://10.0.2.2:")` manually in `image_server.cljs`, 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. + - 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. diff --git a/shadow-cljs.edn b/shadow-cljs.edn index 3ddb308be3d..584d9b30612 100644 --- a/shadow-cljs.edn +++ b/shadow-cljs.edn @@ -65,25 +65,27 @@ ;; 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/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"} + 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 diff --git a/src/status_im/config.cljs b/src/status_im/config.cljs index cd75da465da..d5a6db73c34 100644 --- a/src/status_im/config.cljs +++ b/src/status_im/config.cljs @@ -139,3 +139,5 @@ ;; /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/utils/image_server.cljs b/src/utils/image_server.cljs index 5a5f58fd9a1..715d9c2adce 100644 --- a/src/utils/image_server.cljs +++ b/src/utils/image_server.cljs @@ -4,9 +4,10 @@ [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") From 52be6bbd72a7cfb20523e18f98b2121dd2a38425 Mon Sep 17 00:00:00 2001 From: frank Date: Tue, 12 Nov 2024 21:54:34 +0800 Subject: [PATCH 3/4] update doc --- doc/use-status-backend-server.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/use-status-backend-server.md b/doc/use-status-backend-server.md index a855dec0e54..d89ab9bca89 100644 --- a/doc/use-status-backend-server.md +++ b/doc/use-status-backend-server.md @@ -45,7 +45,7 @@ Basically, you don't have to run `make generate` again and again, just run it on If you're using ios simulator, you can skip above issues! -## Details for issue#3 if you're interested +## 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` From 737c6cb0d2ec907f13c5cf2227b33e3f6bc26cd3 Mon Sep 17 00:00:00 2001 From: frank Date: Thu, 14 Nov 2024 17:36:29 +0800 Subject: [PATCH 4/4] fix_: image_server lint issue --- scripts/lint/re-frame-in-quo-components.sh | 3 ++- src/utils/image_server.cljs | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/lint/re-frame-in-quo-components.sh b/scripts/lint/re-frame-in-quo-components.sh index bdb098ef820..2b89fbb41a5 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/src/utils/image_server.cljs b/src/utils/image_server.cljs index 715d9c2adce..e9e452d3a19 100644 --- a/src/utils/image_server.cljs +++ b/src/utils/image_server.cljs @@ -1,3 +1,6 @@ +;; 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]