diff --git a/README.md b/README.md index 50f9d08..b91f928 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,8 @@ const peerId //returns peerId as string bloxAddr: string, //leave empty for testing without a backend node exchange: 'noop'|'', //add noop for testing without a backend autoFlush: boolean, //Default to false. Always set to false unless you know what you are doing. explicitly write data to disk after each operation if set to true - useRelay: boolean //default to true. If true it forces the connection through relay + useRelay: boolean, //default to true. If true it forces the connection through relay + refresh: boolean? //forces the fula object to be recreated. default is false ) ``` @@ -41,7 +42,8 @@ await fula.init( bloxAddr: string, //leave empty for testing without a backend node exchange: 'noop'|'', //add noop for testing without a backend autoFlush: boolean, //Default to false. Always set to false unless you know what you are doing. explicitly write data to disk after each operation if set to true - useRelay: boolean //default to true. If true it forces the connection through relay + useRelay: boolean, //default to true. If true it forces the connection through relay + refresh: boolean? //forces the fula object to be recreated. default is false ); ``` @@ -131,7 +133,9 @@ await fula.isReady( //checks if client can reach server const result //returns true if it can, and false if it cannot = -await fula.checkConnection(); +await fula.checkConnection( + timeout: number? //default to 20. Maximum time in seconds that checkConnection waits before throwing an error +); ``` @@ -141,7 +145,19 @@ const result //returns true if there are, and false if everything is synced with = await fula.checkFailedActions( retry: boolean //if true, it tries to sync device with server, if not, it only checks + timeout: number? //default to 20. Maximum time in seconds that checkConnection waits before throwing an error +); +``` + +```js +//Gives access to the blox for a specific peerId. This call must be made from the authorizer only. +const result //returns true if succesful and false if fails += +await fula.setAuth( + peerId: string, //peer ID of the app that needs access to the blox + allow: boolean, // true to allow and false to remove access ); + ``` ```js diff --git a/android/build.gradle b/android/build.gradle index 1bda750..f2b59f9 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -62,8 +62,9 @@ repositories { dependencies { //noinspection GradleDynamicVersion implementation "com.facebook.react:react-native:+" // From node_modules - implementation 'com.github.functionland:fula-build-aar:v0.8.4' // From jitpack.io + implementation 'com.github.functionland:fula-build-aar:1.11.2' // From jitpack.io implementation 'com.github.functionland:wnfs-build-aar:v1.4.1' // From jitpack.io implementation 'commons-io:commons-io:20030203.000550' + implementation 'commons-codec:commons-codec:1.15' // implementation files('mobile.aar') } diff --git a/android/src/main/java/land/fx/fula/Cryptography.java b/android/src/main/java/land/fx/fula/Cryptography.java index a5291e0..60d9481 100644 --- a/android/src/main/java/land/fx/fula/Cryptography.java +++ b/android/src/main/java/land/fx/fula/Cryptography.java @@ -3,10 +3,13 @@ import android.util.Base64; import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; +import java.security.SecureRandom; +import java.nio.ByteBuffer; import java.security.spec.InvalidParameterSpecException; import javax.crypto.BadPaddingException; @@ -17,24 +20,34 @@ import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; +import javax.crypto.spec.GCMParameterSpec; public class Cryptography { public static String encryptMsg(String message, SecretKey secret) - throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidParameterSpecException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException { - Cipher cipher = null; - cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); - cipher.init(Cipher.ENCRYPT_MODE, secret); - byte[] cipherText = cipher.doFinal(message.getBytes("UTF-8")); - return Base64.encodeToString(cipherText, Base64.NO_WRAP); + throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException { + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + byte[] iv = new byte[12]; // Ensure this is randomly generated for each encryption. + new SecureRandom().nextBytes(iv); + GCMParameterSpec spec = new GCMParameterSpec(128, iv); + cipher.init(Cipher.ENCRYPT_MODE, secret, spec); + byte[] cipherText = cipher.doFinal(message.getBytes(StandardCharsets.UTF_8)); + ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + cipherText.length); + byteBuffer.put(iv); + byteBuffer.put(cipherText); + return Base64.encodeToString(byteBuffer.array(), Base64.NO_WRAP); } public static String decryptMsg(String cipherText, SecretKey secret) - throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidParameterSpecException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException { - Cipher cipher = null; - cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); - cipher.init(Cipher.DECRYPT_MODE, secret); - byte[] decode = Base64.decode(cipherText, Base64.NO_WRAP); - String decryptString = new String(cipher.doFinal(decode), "UTF-8"); + throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException { + ByteBuffer byteBuffer = ByteBuffer.wrap(Base64.decode(cipherText, Base64.NO_WRAP)); + byte[] iv = new byte[12]; + byteBuffer.get(iv); + byte[] cipherBytes = new byte[byteBuffer.remaining()]; + byteBuffer.get(cipherBytes); + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + GCMParameterSpec spec = new GCMParameterSpec(128, iv); + cipher.init(Cipher.DECRYPT_MODE, secret, spec); + String decryptString = new String(cipher.doFinal(cipherBytes), StandardCharsets.UTF_8); return decryptString; } diff --git a/android/src/main/java/land/fx/fula/FulaModule.java b/android/src/main/java/land/fx/fula/FulaModule.java index 62b13f0..1006a81 100755 --- a/android/src/main/java/land/fx/fula/FulaModule.java +++ b/android/src/main/java/land/fx/fula/FulaModule.java @@ -13,14 +13,31 @@ import com.facebook.react.module.annotations.ReactModule; import org.apache.commons.io.FileUtils; +import org.apache.commons.codec.binary.Base32; import org.jetbrains.annotations.Contract; import java.io.File; +import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; import java.util.Arrays; +import java.util.Random; +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.Future; +import java.util.concurrent.TimeoutException; + import org.json.JSONObject; import org.json.JSONArray; @@ -44,6 +61,7 @@ public void initialize() { fulamobile.Client fula; Client client; + Config fulaConfig; String appDir; String fulaStorePath; String privateForest; @@ -51,7 +69,7 @@ public void initialize() { SharedPreferenceHelper sharedPref; SecretKey secretKeyGlobal; String identityEncryptedGlobal; - static String PRIVATE_KEY_STORE_ID = "PRIVATE_KEY"; + static String PRIVATE_KEY_STORE_PEERID = "PRIVATE_KEY"; public static class Client implements land.fx.wnfslib.Datastore { @@ -117,9 +135,18 @@ private byte[] toByte(@NonNull String input) { return input.getBytes(StandardCharsets.UTF_8); } + private byte[] decToByte(@NonNull String input) { + String[] parts = input.split(","); + byte[] output = new byte[parts.length]; + for (int i = 0; i < parts.length; i++) { + output[i] = Byte.parseByte(parts[i]); + } + return output; + } + @NonNull @Contract("_ -> new") - private String toString(byte[] input) { + public String toString(byte[] input) { return new String(input, StandardCharsets.UTF_8); } @@ -152,33 +179,38 @@ private byte[] convertStringToByte(@NonNull String data) { } @ReactMethod - public void checkConnection(Promise promise) { + public void checkConnection(int timeout, Promise promise) { Log.d("ReactNative", "checkConnection started"); ThreadUtils.runOnExecutor(() -> { if (this.fula != null) { try { - boolean connectionStatus = this.checkConnectionInternal(); + boolean connectionStatus = this.checkConnectionInternal(timeout); + Log.d("ReactNative", "checkConnection ended " + connectionStatus); promise.resolve(connectionStatus); } catch (Exception e) { Log.d("ReactNative", "checkConnection failed with Error: " + e.getMessage()); promise.resolve(false); } + } else { + Log.d("ReactNative", "checkConnection failed with Error: " + "fula is null"); + promise.resolve(false); } }); } @ReactMethod - public void newClient(String identityString, String storePath, String bloxAddr, String exchange, boolean autoFlush, boolean useRelay, Promise promise) { + public void newClient(String identityString, String storePath, String bloxAddr, String exchange, boolean autoFlush, boolean useRelay, boolean refresh, Promise promise) { Log.d("ReactNative", "newClient started"); ThreadUtils.runOnExecutor(() -> { try { - Log.d("ReactNative", "newClient storePath= " + storePath); + Log.d("ReactNative", "newClient storePath= " + storePath + " bloxAddr= " + bloxAddr + " exchange= " + exchange + " autoFlush= " + autoFlush + " useRelay= " + useRelay + " refresh= " + refresh); byte[] identity = toByte(identityString); Log.d("ReactNative", "newClient identity= " + identityString); - this.newClientInternal(identity, storePath, bloxAddr, exchange, autoFlush, useRelay); + this.newClientInternal(identity, storePath, bloxAddr, exchange, autoFlush, useRelay, refresh); //String objString = Arrays.toString(obj); String peerId = this.fula.id(); + Log.d("ReactNative", "newClient peerId= " + peerId); promise.resolve(peerId); } catch (Exception e) { Log.d("ReactNative", "newClient failed with Error: " + e.getMessage()); @@ -197,8 +229,10 @@ public void isReady(boolean filesystemCheck, Promise promise) { if (filesystemCheck) { if (this.client != null && this.rootConfig != null && !this.rootConfig.getCid().isEmpty()) { initialized = true; + Log.d("ReactNative", "isReady is true with filesystem check"); } } else { + Log.d("ReactNative", "isReady is true without filesystem check"); initialized = true; } } @@ -211,7 +245,7 @@ public void isReady(boolean filesystemCheck, Promise promise) { } @ReactMethod - public void init(String identityString, String storePath, String bloxAddr, String exchange, boolean autoFlush, String rootConfig, boolean useRelay, Promise promise) { + public void init(String identityString, String storePath, String bloxAddr, String exchange, boolean autoFlush, String rootConfig, boolean useRelay, boolean refresh, Promise promise) { Log.d("ReactNative", "init started"); ThreadUtils.runOnExecutor(() -> { try { @@ -219,7 +253,7 @@ public void init(String identityString, String storePath, String bloxAddr, Strin Log.d("ReactNative", "init storePath= " + storePath); byte[] identity = toByte(identityString); Log.d("ReactNative", "init identity= " + identityString); - String[] obj = this.initInternal(identity, storePath, bloxAddr, exchange, autoFlush, rootConfig, useRelay); + String[] obj = this.initInternal(identity, storePath, bloxAddr, exchange, autoFlush, rootConfig, useRelay, refresh); Log.d("ReactNative", "init object created: [ " + obj[0] + ", " + obj[1] + ", " + obj[2] + " ]"); resultData.putString("peerId", obj[0]); resultData.putString("rootCid", obj[1]); @@ -248,16 +282,40 @@ public void logout(String identityString, String storePath, Promise promise) { }); } - private boolean checkConnectionInternal() throws Exception { + private boolean checkConnectionInternal(int timeout) throws Exception { try { - Log.d("ReactNative", "checkConnectionInternal fstarted"); + Log.d("ReactNative", "checkConnectionInternal started"); if (this.fula != null) { try { Log.d("ReactNative", "connectToBlox started"); - this.fula.connectToBlox(); - return true; - } - catch (Exception e) { + + AtomicBoolean connectionStatus = new AtomicBoolean(false); + ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + Future future = executor.submit(() -> { + try { + this.fula.connectToBlox(); + connectionStatus.set(true); + Log.d("ReactNative", "checkConnectionInternal succeeded "); + } catch (Exception e) { + Log.d("ReactNative", "checkConnectionInternal failed with Error: " + e.getMessage()); + } + }); + + try { + future.get(timeout, TimeUnit.SECONDS); + } catch (TimeoutException te) { + // If the timeout occurs, shut down the executor and return false + executor.shutdownNow(); + return false; + } finally { + // If the future task is done, we can shut down the executor + if (future.isDone()) { + executor.shutdown(); + } + } + + return connectionStatus.get(); + } catch (Exception e) { Log.d("ReactNative", "checkConnectionInternal failed with Error: " + e.getMessage()); return false; } @@ -272,7 +330,7 @@ private boolean checkConnectionInternal() throws Exception { } @ReactMethod - private void checkFailedActions(boolean retry, Promise promise) throws Exception { + private void checkFailedActions(boolean retry, int timeout, Promise promise) throws Exception { try { if (this.fula != null) { if (!retry) { @@ -286,7 +344,7 @@ private void checkFailedActions(boolean retry, Promise promise) throws Exception } } else { Log.d("ReactNative", "checkFailedActions with retry"); - boolean retryResults = this.retryFailedActionsInternal(); + boolean retryResults = this.retryFailedActionsInternal(timeout); promise.resolve(!retryResults); } } else { @@ -298,13 +356,13 @@ private void checkFailedActions(boolean retry, Promise promise) throws Exception } } - private boolean retryFailedActionsInternal() throws Exception { + private boolean retryFailedActionsInternal(int timeout) throws Exception { try { Log.d("ReactNative", "retryFailedActionsInternal started"); if (this.fula != null) { //Fula is initialized try { - boolean connectionCheck = this.checkConnectionInternal(); + boolean connectionCheck = this.checkConnectionInternal(timeout); if(connectionCheck) { try { Log.d("ReactNative", "retryFailedPushes started"); @@ -375,24 +433,50 @@ private boolean retryFailedActionsInternal() throws Exception { } @NonNull - private byte[] createPeerIdentity(byte[] privateKey) throws Exception { + private byte[] createPeerIdentity(byte[] identity) throws GeneralSecurityException, IOException { try { // 1: First: create public key from provided private key // 2: Should read the local keychain store (if it is key-value, key is public key above, // 3: if found, decrypt using the private key // 4: If not found or decryption not successful, generate an identity // 5: then encrypt and store in keychain + byte[] libp2pId; + String encryptedLibp2pId = sharedPref.getValue(PRIVATE_KEY_STORE_PEERID); + byte[] encryptionPair; + SecretKey encryptionSecretKey; + try { + encryptionSecretKey = Cryptography.generateKey(identity); + Log.d("ReactNative", "encryptionSecretKey generated from privateKey"); + } catch (Exception e) { + Log.d("ReactNative", "Failed to generate key for encryption: " + e.getMessage()); + throw new GeneralSecurityException("Failed to generate key encryption", e); + } + + if (encryptedLibp2pId == null || !encryptedLibp2pId.startsWith("FULA_ENC_V3:")) { + Log.d("ReactNative", "encryptedLibp2pId is not correct. creating new one " + encryptedLibp2pId); - String encryptedKey = sharedPref.getValue(PRIVATE_KEY_STORE_ID); - SecretKey secretKey = Cryptography.generateKey(privateKey); - if (encryptedKey == null) { - byte[] autoGeneratedIdentity = Fulamobile.generateEd25519Key(); - encryptedKey = Cryptography.encryptMsg(StaticHelper.bytesToBase64(autoGeneratedIdentity), secretKey); - sharedPref.add(PRIVATE_KEY_STORE_ID, encryptedKey); + try { + libp2pId = Fulamobile.generateEd25519KeyFromString(toString(identity)); + } catch (Exception e) { + Log.d("ReactNative", "Failed to generate libp2pId: " + e.getMessage()); + throw new GeneralSecurityException("Failed to generate libp2pId", e); + } + encryptedLibp2pId = "FULA_ENC_V3:" + Cryptography.encryptMsg(StaticHelper.bytesToBase64(libp2pId), encryptionSecretKey); + sharedPref.add(PRIVATE_KEY_STORE_PEERID, encryptedLibp2pId); + } else { + Log.d("ReactNative", "encryptedLibp2pId is correct. decrypting " + encryptedLibp2pId); } - return StaticHelper.base64ToBytes(Cryptography.decryptMsg(encryptedKey, secretKey)); - } catch (Exception e) { + try { + String decryptedLibp2pId = Cryptography.decryptMsg(encryptedLibp2pId.replace("FULA_ENC_V3:", ""), encryptionSecretKey); + + return StaticHelper.base64ToBytes(decryptedLibp2pId); + } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException e) { + Log.d("ReactNative", "createPeerIdentity decryptMsg failed with Error: " + e.getMessage()); + throw (e); + } + + } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException e) { Log.d("ReactNative", "createPeerIdentity failed with Error: " + e.getMessage()); throw (e); } @@ -423,8 +507,8 @@ private boolean encrypt_and_store_config() throws Exception { String cid_encrypted = Cryptography.encryptMsg(this.rootConfig.getCid(), this.secretKeyGlobal); String private_ref_encrypted = Cryptography.encryptMsg(this.rootConfig.getPrivate_ref(), this.secretKeyGlobal); - sharedPref.add("cid_encrypted_" + this.identityEncryptedGlobal, cid_encrypted); - sharedPref.add("private_ref_encrypted_" + this.identityEncryptedGlobal, private_ref_encrypted); + sharedPref.add("FULA_ENC_V3:cid_encrypted_" + this.identityEncryptedGlobal, cid_encrypted); + sharedPref.add("FULA_ENC_V3:private_ref_encrypted_" + this.identityEncryptedGlobal, private_ref_encrypted); return true; } else { return false; @@ -441,14 +525,15 @@ private boolean logoutInternal(byte[] identity, String storePath) throws Excepti this.fula.flush(); } SecretKey secretKey = Cryptography.generateKey(identity); + String identity_encrypted = Cryptography.encryptMsg(Arrays.toString(identity), secretKey); - sharedPref.remove("cid_encrypted_"+ identity_encrypted); - sharedPref.remove("private_ref_encrypted_"+identity_encrypted); + sharedPref.remove("FULA_ENC_V3:cid_encrypted_"+ identity_encrypted); + sharedPref.remove("FULA_ENC_V3:private_ref_encrypted_"+identity_encrypted); //TODO: Should also remove peerid @Mahdi - sharedPref.remove("cid_encrypted_"+ identity_encrypted); - sharedPref.remove("private_ref_encrypted_"+ identity_encrypted); + sharedPref.remove("FULA_ENC_V3:cid_encrypted_"+ identity_encrypted); + sharedPref.remove("FULA_ENC_V3:private_ref_encrypted_"+ identity_encrypted); this.rootConfig = null; this.secretKeyGlobal = null; @@ -468,49 +553,61 @@ private boolean logoutInternal(byte[] identity, String storePath) throws Excepti } } + public fulamobile.Client getFulaClient() { + return this.fula; + } + @NonNull - private byte[] newClientInternal(byte[] identity, String storePath, String bloxAddr, String exchange, boolean autoFlush, boolean useRelay) throws Exception { + private byte[] newClientInternal(byte[] identity, String storePath, String bloxAddr, String exchange, boolean autoFlush, boolean useRelay, boolean refresh) throws GeneralSecurityException, IOException { + byte[] peerIdentity = null; try { - Config config_ext = new Config(); + fulaConfig = new Config(); if (storePath == null || storePath.trim().isEmpty()) { - config_ext.setStorePath(this.fulaStorePath); + fulaConfig.setStorePath(this.fulaStorePath); } else { - config_ext.setStorePath(storePath); + fulaConfig.setStorePath(storePath); } - Log.d("ReactNative", "storePath is set: " + config_ext.getStorePath()); - - byte[] peerIdentity = this.createPeerIdentity(identity); - config_ext.setIdentity(peerIdentity); - Log.d("ReactNative", "peerIdentity is set: " + toString(config_ext.getIdentity())); - config_ext.setBloxAddr(bloxAddr); - Log.d("ReactNative", "bloxAddr is set: " + config_ext.getBloxAddr()); - config_ext.setExchange(exchange); - config_ext.setSyncWrites(autoFlush); + Log.d("ReactNative", "newClientInternal storePath is set: " + fulaConfig.getStorePath()); + + peerIdentity = this.createPeerIdentity(identity); + fulaConfig.setIdentity(peerIdentity); + Log.d("ReactNative", "peerIdentity is set: " + toString(fulaConfig.getIdentity())); + fulaConfig.setBloxAddr(bloxAddr); + Log.d("ReactNative", "bloxAddr is set: " + fulaConfig.getBloxAddr()); + fulaConfig.setExchange(exchange); + fulaConfig.setSyncWrites(autoFlush); if (useRelay) { - config_ext.setAllowTransientConnection(true); - config_ext.setForceReachabilityPrivate(true); + fulaConfig.setAllowTransientConnection(true); + fulaConfig.setForceReachabilityPrivate(true); } - if (this.fula == null) { + if (this.fula == null || refresh) { Log.d("ReactNative", "Creating a new Fula instance"); - this.fula = Fulamobile.newClient(config_ext); - } - if (this.fula != null) { - this.fula.flush(); + try { + shutdownInternal(); + this.fula = Fulamobile.newClient(fulaConfig); + if (this.fula != null) { + this.fula.flush(); + } + } catch (Exception e) { + Log.d("ReactNative", "Failed to create new Fula instance: " + e.getMessage()); + throw new IOException("Failed to create new Fula instance", e); + } } - return peerIdentity; - } catch (Exception e) { + } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException e) { Log.d("ReactNative", "newclientInternal failed with Error: " + e.getMessage()); throw (e); } + return peerIdentity; } + @NonNull - private String[] initInternal(byte[] identity, String storePath, String bloxAddr, String exchange, boolean autoFlush, String rootCid, boolean useRelay) throws Exception { + private String[] initInternal(byte[] identity, String storePath, String bloxAddr, String exchange, boolean autoFlush, String rootCid, boolean useRelay, boolean refresh) throws Exception { try { - if (this.fula == null) { - this.newClientInternal(identity, storePath, bloxAddr, exchange, autoFlush, useRelay); + if (this.fula == null || refresh) { + this.newClientInternal(identity, storePath, bloxAddr, exchange, autoFlush, useRelay, refresh); } - if(this.client == null) { + if(this.client == null || refresh) { this.client = new Client(this.fula); Log.d("ReactNative", "fula initialized: " + this.fula.id()); } @@ -524,8 +621,8 @@ private String[] initInternal(byte[] identity, String storePath, String bloxAddr Log.d("ReactNative", "this.rootCid is empty."); //Load from keystore - String cid_encrypted_fetched = sharedPref.getValue("cid_encrypted_"+ identity_encrypted); - String private_ref_encrypted_fetched = sharedPref.getValue("private_ref_encrypted_"+identity_encrypted); + String cid_encrypted_fetched = sharedPref.getValue("FULA_ENC_V3:cid_encrypted_"+ identity_encrypted); + String private_ref_encrypted_fetched = sharedPref.getValue("FULA_ENC_V3:private_ref_encrypted_"+identity_encrypted); Log.d("ReactNative", "Here1"); String cid = ""; String private_ref = ""; @@ -644,17 +741,26 @@ public void writeFile(String fulaTargetFilename, String localFilename, Promise p ThreadUtils.runOnExecutor(() -> { Log.d("ReactNative", "writeFile to : path = " + fulaTargetFilename + ", from: " + localFilename); try { - land.fx.wnfslib.Config config = Fs.writeFileFromPath(this.client, this.rootConfig.getCid(), this.rootConfig.getPrivate_ref(), fulaTargetFilename, localFilename); - if(config != null) { - this.rootConfig = config; - this.encrypt_and_store_config(); - if (this.fula != null) { - this.fula.flush(); + if (this.client != null) { + Log.d("ReactNative", "writeFileFromPath started: this.rootConfig.getCid=" + this.rootConfig.getCid()+ ", fulaTargetFilename="+fulaTargetFilename + ", localFilename="+localFilename); + land.fx.wnfslib.Config config = Fs.writeFileFromPath(this.client, this.rootConfig.getCid(), this.rootConfig.getPrivate_ref(), fulaTargetFilename, localFilename); + if(config != null) { + this.rootConfig = config; + this.encrypt_and_store_config(); + if (this.fula != null) { + this.fula.flush(); + promise.resolve(config.getCid()); + } else { + Log.d("ReactNative", "writeFile Error: fula is null"); + promise.reject(new Exception("writeFile Error: fula is null")); + } + } else { + Log.d("ReactNative", "writeFile Error: config is null"); + promise.reject(new Exception("writeFile Error: config is null")); } - promise.resolve(config.getCid()); } else { - Log.d("ReactNative", "writeFile Error: config is null"); - promise.reject(new Exception("writeFile Error: config is null")); + Log.d("ReactNative", "writeFile Error: client is null"); + promise.reject(new Exception("writeFile Error: client is null")); } } catch (Exception e) { Log.d("get", e.getMessage()); @@ -946,21 +1052,375 @@ private byte[] putInternal(byte[] value, long codec) throws Exception { } @ReactMethod - public void shutdown(Promise promise) { + public void setAuth(String peerIdString, boolean allow, Promise promise) { ThreadUtils.runOnExecutor(() -> { + Log.d("ReactNative", "setAuth: peerIdString = " + peerIdString); try { - if(this.fula != null) { - this.fula.shutdown(); - this.fula = null; - this.client = null; + if (this.fula != null && this.fula.id() != null && this.fulaConfig != null && this.fulaConfig.getBloxAddr() != null) { + String bloxAddr = this.fulaConfig.getBloxAddr(); + Log.d("ReactNative", "setAuth: bloxAddr = '" + bloxAddr+"'"+ " peerIdString = '" + peerIdString+"'"); + int index = bloxAddr.lastIndexOf("/"); + String bloxPeerId = bloxAddr.substring(index + 1); + this.fula.setAuth(bloxPeerId, peerIdString, allow); + promise.resolve(true); + } else { + Log.d("ReactNative", "setAuth error: fula is not initialized"); + throw new Exception("fula is not initialized"); } + promise.resolve(false); + } catch (Exception e) { + Log.d("get", e.getMessage()); + promise.reject(e); + } + }); + } + + private void shutdownInternal() throws Exception { + try { + if(this.fula != null) { + this.fula.shutdown(); + this.fula = null; + this.client = null; + } + } catch (Exception e) { + Log.d("ReactNative", "shutdownInternal"+ e.getMessage()); + throw (e); + } + } + + @ReactMethod + public void shutdown(Promise promise) { + ThreadUtils.runOnExecutor(() -> { + try { + shutdownInternal(); promise.resolve(true); } catch (Exception e) { promise.reject(e); Log.d("ReactNative", "shutdown"+ e.getMessage()); } }); + } + + /////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////// + //////////////////////ANYTHING BELOW IS FOR BLOCKCHAIN///// + /////////////////////////////////////////////////////////// + @ReactMethod + public void createAccount(String seedString, Promise promise) { + ThreadUtils.runOnExecutor(() -> { + Log.d("ReactNative", "createAccount: seedString = " + seedString); + try { + if (this.fula == null || this.fula.id() == null || this.fula.id().isEmpty()) { + promise.reject(new Error("Fula client is not initialized")); + } else { + + if (!seedString.startsWith("/")) { + promise.reject(new Error("seed should start with /")); + } + byte[] result = this.fula.seeded(seedString); + String resultString = toString(result); + promise.resolve(resultString); + } + } catch (Exception e) { + Log.d("get", e.getMessage()); + promise.reject(e); + } + }); + } + @ReactMethod + public void checkAccountExists(String accountString, Promise promise) { + ThreadUtils.runOnExecutor(() -> { + Log.d("ReactNative", "checkAccountExists: accountString = " + accountString); + try { + byte[] result = this.fula.accountExists(accountString); + String resultString = toString(result); + promise.resolve(resultString); + } catch (Exception e) { + Log.d("get", e.getMessage()); + promise.reject(e); + } + }); + } + + @ReactMethod + public void createPool(String seedString, String poolName, Promise promise) { + ThreadUtils.runOnExecutor(() -> { + Log.d("ReactNative", "createPool: seedString = " + seedString + "; poolName = " + poolName); + try { + byte[] result = this.fula.poolCreate(seedString, poolName); + String resultString = toString(result); + promise.resolve(resultString); + } catch (Exception e) { + Log.d("get", e.getMessage()); + promise.reject(e); + } + }); + } + + @ReactMethod + public void listPools(Promise promise) { + ThreadUtils.runOnExecutor(() -> { + Log.d("ReactNative", "listPools"); + try { + byte[] result = this.fula.poolList(); + String resultString = toString(result); + promise.resolve(resultString); + } catch (Exception e) { + Log.d("get", e.getMessage()); + promise.reject(e); + } + }); + } + + @ReactMethod + public void joinPool(String seedString, long poolID, Promise promise) { + ThreadUtils.runOnExecutor(() -> { + Log.d("ReactNative", "joinPool: seedString = " + seedString + "; poolID = " + poolID); + try { + byte[] result = this.fula.poolJoin(seedString, poolID); + String resultString = toString(result); + promise.resolve(resultString); + } catch (Exception e) { + Log.d("get", e.getMessage()); + promise.reject(e); + } + }); + } + + @ReactMethod + public void cancelPoolJoin(String seedString, long poolID, Promise promise) { + ThreadUtils.runOnExecutor(() -> { + Log.d("ReactNative", "cancelPoolJoin: seedString = " + seedString + "; poolID = " + poolID); + try { + byte[] result = this.fula.poolCancelJoin(seedString, poolID); + String resultString = toString(result); + promise.resolve(resultString); + } catch (Exception e) { + Log.d("get", e.getMessage()); + promise.reject(e); + } + }); + } + + @ReactMethod + public void listPoolJoinRequests(long poolID, Promise promise) { + ThreadUtils.runOnExecutor(() -> { + Log.d("ReactNative", "listPoolJoinRequests: poolID = " + poolID); + try { + byte[] result = this.fula.poolRequests(poolID); + String resultString = toString(result); + promise.resolve(resultString); + } catch (Exception e) { + Log.d("get", e.getMessage()); + promise.reject(e); + } + }); + } + + @ReactMethod + public void votePoolJoinRequest(String seedString, long poolID, String accountString, boolean accept, Promise promise) { + ThreadUtils.runOnExecutor(() -> { + Log.d("ReactNative", "votePoolJoinRequest: seedString = " + seedString + "; poolID = " + poolID + "; accountString = " + accountString + "; accept = " + accept); + try { + byte[] result = this.fula.poolVote(seedString, poolID, accountString, accept); + String resultString = toString(result); + promise.resolve(resultString); + } catch (Exception e) { + Log.d("get", e.getMessage()); + promise.reject(e); + } + }); + } + + @ReactMethod + public void leavePool(String seedString, long poolID, Promise promise) { + ThreadUtils.runOnExecutor(() -> { + Log.d("ReactNative", "leavePool: seedString = " + seedString + "; poolID = " + poolID); + try { + byte[] result = this.fula.poolLeave(seedString, poolID); + String resultString = toString(result); + promise.resolve(resultString); + } catch (Exception e) { + Log.d("get", e.getMessage()); + promise.reject(e); + } + }); + } + + @ReactMethod + public void newReplicationRequest(String seedString, long poolID, long replicationFactor, String cid, Promise promise) { + ThreadUtils.runOnExecutor(() -> { + Log.d("ReactNative", "newReplicationRequest: seedString = " + seedString + "; poolID = " + poolID + "; replicationFactor = " + replicationFactor + "; cid = " + cid); + try { + byte[] result = this.fula.manifestUpload(seedString, poolID, replicationFactor, cid); + String resultString = toString(result); + promise.resolve(resultString); + } catch (Exception e) { + Log.d("get", e.getMessage()); + promise.reject(e); + } + }); + } + + @ReactMethod + public void newStoreRequest(String seedString, long poolID, String uploader, String cid, Promise promise) { + ThreadUtils.runOnExecutor(() -> { + Log.d("ReactNative", "newStoreRequest: seedString = " + seedString + "; poolID = " + poolID + "; uploader = " + uploader + "; cid = " + cid); + try { + byte[] result = this.fula.manifestStore(seedString, poolID, uploader, cid); + String resultString = toString(result); + promise.resolve(resultString); + } catch (Exception e) { + Log.d("get", e.getMessage()); + promise.reject(e); + } + }); + } + + @ReactMethod + public void listAvailableReplicationRequests(long poolID, Promise promise) { + ThreadUtils.runOnExecutor(() -> { + Log.d("ReactNative", "listAvailableReplicationRequests: poolID = " + poolID); + try { + byte[] result = this.fula.manifestAvailable(poolID); + String resultString = toString(result); + promise.resolve(resultString); + } catch (Exception e) { + Log.d("get", e.getMessage()); + promise.reject(e); + } + }); + } + + @ReactMethod + public void removeReplicationRequest(String seedString, long poolID, String cid, Promise promise) { + ThreadUtils.runOnExecutor(() -> { + Log.d("ReactNative", "newReplicationRequest: seedString = " + seedString + "; poolID = " + poolID + "; cid = " + cid); + try { + byte[] result = this.fula.manifestRemove(seedString, poolID, cid); + String resultString = toString(result); + promise.resolve(resultString); + } catch (Exception e) { + Log.d("get", e.getMessage()); + promise.reject(e); + } + }); + } + + @ReactMethod + public void removeStorer(String seedString, String storage, long poolID, String cid, Promise promise) { + ThreadUtils.runOnExecutor(() -> { + Log.d("ReactNative", "removeStorer: seedString = " + seedString + "; storage = " + storage + "; poolID = " + poolID + "; cid = " + cid); + try { + byte[] result = this.fula.manifestRemoveStorer(seedString, storage, poolID, cid); + String resultString = toString(result); + promise.resolve(resultString); + } catch (Exception e) { + Log.d("get", e.getMessage()); + promise.reject(e); + } + }); + } + + @ReactMethod + public void removeStoredReplication(String seedString, String uploader, long poolID, String cid, Promise promise) { + ThreadUtils.runOnExecutor(() -> { + Log.d("ReactNative", "removeStoredReplication: seedString = " + seedString + "; uploader = " + uploader + "; poolID = " + poolID + "; cid = " + cid); + try { + byte[] result = this.fula.manifestRemoveStored(seedString, uploader, poolID, cid); + String resultString = toString(result); + promise.resolve(resultString); + } catch (Exception e) { + Log.d("get", e.getMessage()); + promise.reject(e); + } + }); + } + + //////////////////////////////////////////////////////////////// + ///////////////// Blox Hardware Methods //////////////////////// + //////////////////////////////////////////////////////////////// + + @ReactMethod + public void bloxFreeSpace(Promise promise) { + ThreadUtils.runOnExecutor(() -> { + Log.d("ReactNative", "bloxFreeSpace"); + try { + byte[] result = this.fula.bloxFreeSpace(); + String resultString = toString(result); + Log.d("ReactNative", "result string="+resultString); + promise.resolve(resultString); + } catch (Exception e) { + Log.d("ReactNative", e.getMessage()); + promise.reject(e); + } + }); + } + + @ReactMethod + public void wifiRemoveall(Promise promise) { + ThreadUtils.runOnExecutor(() -> { + Log.d("ReactNative", "wifiRemoveall"); + try { + byte[] result = this.fula.wifiRemoveall(); + String resultString = toString(result); + Log.d("ReactNative", "result string="+resultString); + promise.resolve(resultString); + } catch (Exception e) { + Log.d("ReactNative", e.getMessage()); + promise.reject(e); + } + }); + } + + @ReactMethod + public void reboot(Promise promise) { + ThreadUtils.runOnExecutor(() -> { + Log.d("ReactNative", "reboot"); + try { + byte[] result = this.fula.reboot(); + String resultString = toString(result); + Log.d("ReactNative", "result string="+resultString); + promise.resolve(resultString); + } catch (Exception e) { + Log.d("ReactNative", e.getMessage()); + promise.reject(e); + } + }); + } + + @ReactMethod + public void testData(String identityString, String bloxAddr, Promise promise) { + try { + byte[] identity = toByte(identityString); + byte[] peerIdByte = this.newClientInternal(identity, "", bloxAddr, "", true, true, true); + + String peerIdReturned = Arrays.toString(peerIdByte); + Log.d("ReactNative", "newClient peerIdReturned= " + peerIdReturned); + String peerId = this.fula.id(); + Log.d("ReactNative", "newClient peerId= " + peerId); + byte[] bytes = {1, 85, 18, 32, 11, -31, 75, -78, -109, 11, -111, 97, -47, -78, -22, 84, 39, -117, -64, -70, -91, 55, -23, -80, 116, -123, -97, -26, 126, -70, -76, 35, 54, -106, 55, -9}; + + byte[] key = this.fula.put(bytes, 85); + Log.d("ReactNative", "testData put result string="+Arrays.toString(key)); + + byte[] value = this.fula.get(key); + Log.d("ReactNative", "testData get result string="+Arrays.toString(value)); + + this.fula.push(key); + //this.fula.flush(); + + byte[] fetchedVal = this.fula.get(key); + this.fula.pull(key); + + promise.resolve(Arrays.toString(fetchedVal)); + } catch (Exception e) { + Log.d("ReactNative", "ERROR:" + e.getMessage()); + promise.reject(e); + } } } diff --git a/android/src/main/java/land/fx/fula/FulaPackage.java b/android/src/main/java/land/fx/fula/FulaPackage.java index da4a4d8..6256d2d 100644 --- a/android/src/main/java/land/fx/fula/FulaPackage.java +++ b/android/src/main/java/land/fx/fula/FulaPackage.java @@ -17,9 +17,9 @@ public class FulaPackage implements ReactPackage { public List createNativeModules(@NonNull ReactApplicationContext reactContext) { List modules = new ArrayList<>(); try { - modules.add(new FulaModule(reactContext)); + modules.add(new FulaModule(reactContext)); } catch (Exception e) { - e.printStackTrace(); + e.printStackTrace(); } return modules; } diff --git a/example/android/build.gradle b/example/android/build.gradle index 91b6d80..8431b66 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -2,7 +2,7 @@ buildscript { ext { - minSdkVersion = 26 + minSdkVersion = 30 compileSdkVersion = 31 targetSdkVersion = 31 ndkVersion = '25.1.8937393' diff --git a/example/src/App.tsx b/example/src/App.tsx index 643c23e..75963ce 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { StyleSheet, Text, View, Button, TextInput } from 'react-native'; -import { fula } from '@functionland/react-native-fula'; +import { fula, blockchain, chainApi, fxblox } from '@functionland/react-native-fula'; const App = () => { const [key, setKey] = React.useState(''); @@ -31,13 +31,15 @@ const App = () => { console.log(err.message, err.code); }); }; + //Key for peerId: 12D3KooWFi2PK36Rzi4Bmosj1np2t6i9v3QnbBiNY9hQWuJajnmZ const privateKey = [ 183, 7, 117, 9, 159, 132, 170, 235, 215, 34, 145, 181, 60, 207, 4, 27, 27, 17, 17, 167, 100, 89, 157, 218, 73, 200, 183, 145, 104, 151, 204, 142, 241, 94, 225, 7, 153, 168, 239, 94, 7, 187, 123, 158, 149, 149, 227, 170, 32, 54, 203, 243, 211, 78, 120, 114, 199, 1, 197, 134, 6, 91, 87, 152, ]; - const bloxAddr = '/dns/relay.dev.fx.land/tcp/4001/p2p/12D3KooWDRrBaAfPwsGJivBoUw5fE7ZpDiyfUjqgiURq2DEcL835/p2p-circuit/p2p/12D3KooWR2EiA8DbULqDAJZCcN2V2Nasmh756R1aLe5t3NniCnAS'; + const privateKeyString = "\\test"; + const bloxAddr = '/dns/relay.dev.fx.land/tcp/4001/p2p/12D3KooWDRrBaAfPwsGJivBoUw5fE7ZpDiyfUjqgiURq2DEcL835/p2p-circuit/p2p/12D3KooWLGatFxDzMrKd4S6UC4GAtuM4zcFJW8RPuMR9SH7j46A8'; const newClient = async () => { try { return fula.newClient( @@ -65,8 +67,21 @@ const App = () => { } }; React.useEffect(() => { + /*chainApi.init().then((api) => { + chainApi.listPools(api).then((pools) => { + console.log('pools', pools); + }).catch((e) => { + console.log('error', e); + }); + + chainApi.checkJoinRequest(api, 1, "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty").then((poolReq) => { + console.log('poolReq', poolReq); + }).catch((err) => { + console.log('error', err); + }); + });*/ //fula.logout(privateKey.toString(),'').then((a) => { - initFula() + /*initFula() .then((res) => { console.log('OK', res); setInitComplete(res); @@ -89,7 +104,7 @@ const App = () => { }) .catch((e) => { console.log('error', e); - }); + });*/ //}); }, []); @@ -99,11 +114,46 @@ const App = () => { Put & Get