diff --git a/.gitmodules b/.gitmodules index 1d764850a..e69de29bb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "tor"] - path = tor - url = https://github.com/thaliproject/Tor_Onion_Proxy_Library diff --git a/android/app/build.gradle b/android/app/build.gradle index 29243356c..189167980 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -154,15 +154,17 @@ dependencies { implementation "com.github.mmin18:realtimeblurview:1.2.1" - // Tor - // implementation "com.msopentech.thali:universal:0.0.3" - // implementation "org.torproject:tor-android-binary:0.4.2.7a" - // implementation "com.msopentech.thali.toronionproxy.android:android:0.0.3@aar" - // implementation "org.slf4j:slf4j-android:1.7.30" - // implementation "net.freehaven.tor.control:jtorctl:0.2" // controlling tor instance via its control port - implementation "com.jakewharton:process-phoenix:2.0.0" implementation 'org.brotli:dec:0.1.2' + + implementation "com.jakewharton:process-phoenix:2.0.0" + + implementation 'info.guardianproject:tor-android:0.4.7.8' + implementation 'info.guardianproject:jtorctl:0.4.5.7' + + implementation('dev.doubledot.doki:library:0.0.1@aar') { + transitive = true + } } apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro index 3ff1cebad..6457feed7 100644 --- a/android/app/proguard-rules.pro +++ b/android/app/proguard-rules.pro @@ -32,3 +32,5 @@ -keep class lnrpc.** { *; } -keep class com.facebook.react.turbomodule.** { *; } + +-keep class org.torproject.jni.** { *; } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 45ab123e9..eeca26e73 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -40,6 +40,7 @@ + diff --git a/android/app/src/main/java/com/blixtwallet/BlixtTor.java b/android/app/src/main/java/com/blixtwallet/BlixtTor.java new file mode 100644 index 000000000..d8a644501 --- /dev/null +++ b/android/app/src/main/java/com/blixtwallet/BlixtTor.java @@ -0,0 +1,165 @@ +package com.blixtwallet; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.database.sqlite.SQLiteDatabase; +import android.os.Build; +import android.os.IBinder; +import android.util.Log; +import android.widget.Toast; + +import java.util.Stack; + +import androidx.core.app.NotificationManagerCompat; + +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.reactnativecommunity.asyncstorage.AsyncLocalStorageUtil; +import com.reactnativecommunity.asyncstorage.ReactDatabaseSupplier; + +import org.torproject.jni.TorService; + + +public class BlixtTor extends ReactContextBaseJavaModule { + static private final String TAG = "BlixtTor"; + static TorService torService; + static String currentTorStatus = TorService.STATUS_OFF; + static Stack calleeResolvers = new Stack<>(); + static NotificationManagerCompat notificationManager; + + static private boolean getPersistentServicesEnabled(Context context) { + ReactDatabaseSupplier dbSupplier = ReactDatabaseSupplier.getInstance(context); + SQLiteDatabase db = dbSupplier.get(); + String persistentServicesEnabled = AsyncLocalStorageUtil.getItemImpl(db, "persistentServicesEnabled"); + if (persistentServicesEnabled != null) { + return persistentServicesEnabled.equals("true"); + } + Log.w(TAG, "Could not find persistentServicesEnabled in asyncStorage"); + return false; + } + + static private final ServiceConnection torServiceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName className, IBinder service) { + // We've bound to LocalService, cast the IBinder and get LocalService instance + TorService.LocalBinder binder = (TorService.LocalBinder) service; + torService = binder.getService(); + Log.i(TAG, "torService.getService()"); + boolean persistentServicesEnabled = getPersistentServicesEnabled(torService); + if (persistentServicesEnabled) { + torService.startForeground(0xc0feefee, getNotification(torService)); + } + Log.i(TAG, "onServiceConnected"); + } + + @Override + public void onServiceDisconnected(ComponentName arg0) { + Log.i(TAG, "onServiceDisconnected"); + } + + public Notification getNotification(Context context) { + Intent notificationIntent = new Intent (context, MainActivity.class); + PendingIntent pendingIntent = + PendingIntent.getActivity(context, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannel channel = new NotificationChannel(BuildConfig.APPLICATION_ID, "blixt", NotificationManager.IMPORTANCE_NONE); + channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE); + notificationManager = NotificationManagerCompat.from(context); + notificationManager.createNotificationChannel(channel); + notificationManager.createNotificationChannel(channel); + } + return new Notification.Builder(context, BuildConfig.APPLICATION_ID) + .setContentTitle("Tor") + .setContentText("Tor is running in the background") + .setSmallIcon(R.drawable.ic_stat_ic_notification) + .setContentIntent(pendingIntent) + .setTicker("Blixt Wallet") + .setOngoing(true) + .build(); + } + }; + + private final BroadcastReceiver torBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { +// Toast.makeText(context, intent.getStringExtra(TorService.EXTRA_STATUS), Toast.LENGTH_SHORT).show(); + String status = intent.getStringExtra(TorService.EXTRA_STATUS); + if (intent != null && intent.getAction() != null && intent.getAction().equals("org.torproject.android.intent.action.STOP")) { + torService.stopSelf(); + } + currentTorStatus = status; + Log.i(TAG, "onReceive " + status); + if (status.equals(TorService.STATUS_ON)) { + while (calleeResolvers.size() > 0) { + calleeResolvers.pop().resolve(TorService.socksPort); + } + } else if (status.equals(TorService.STATUS_OFF)) { + getReactApplicationContext().unregisterReceiver(torBroadcastReceiver); + } + } + }; + + public BlixtTor(ReactApplicationContext reactContext) { + super(reactContext); + } + + public String getName() { + return "BlixtTor"; + } + + @ReactMethod + public void startTor(Promise promise) { + Log.i(TAG, "startTor()"); +// if (calleeResolver != null) { +// Log.i(TAG, "calleeResolver != null"); +// promise.reject(TAG, "Tor already in progress starting"); +// return; +// } + if (currentTorStatus.equals(TorService.STATUS_ON)) { + Log.i(TAG, "socksPort = " + TorService.socksPort + ", currentTorStatus.equals(TorService.STATUS_ON) " + currentTorStatus.equals(TorService.STATUS_ON)); + promise.resolve(TorService.socksPort); + return; + } + calleeResolvers.add(promise); + + boolean persistentServicesEnabled = getPersistentServicesEnabled(getReactApplicationContext()); + getReactApplicationContext().registerReceiver(torBroadcastReceiver, new IntentFilter(TorService.ACTION_STATUS)); + Intent intent = new Intent(getReactApplicationContext(), TorService.class); + + if (persistentServicesEnabled) { + getReactApplicationContext().startForegroundService(intent); + } + getReactApplicationContext().bindService(intent, torServiceConnection, Context.BIND_AUTO_CREATE); + } + + @ReactMethod + public void stopTor(Promise promise) { + if (notificationManager != null) { + notificationManager.cancelAll(); + } + Log.i(TAG,"Unbinding TorService"); + try { + getReactApplicationContext().unbindService(torServiceConnection); + } catch (IllegalArgumentException e) { + Log.w(TAG, "Tried to unbindService on unbound service"); + } + promise.resolve(true); + }; + + @ReactMethod + public void showMsg(Promise promise) { + MainActivity.getActivity().showMsg(); + promise.resolve(true); + } +} diff --git a/android/app/src/main/java/com/blixtwallet/BlixtTorPackage.java b/android/app/src/main/java/com/blixtwallet/BlixtTorPackage.java new file mode 100644 index 000000000..3c766d7c2 --- /dev/null +++ b/android/app/src/main/java/com/blixtwallet/BlixtTorPackage.java @@ -0,0 +1,22 @@ +package com.blixtwallet; + +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class BlixtTorPackage implements ReactPackage { + @Override + public List createViewManagers(ReactApplicationContext reactContext) { + return Collections.emptyList(); + } + + @Override + public List createNativeModules(ReactApplicationContext reactContext) { + return Arrays.asList(new BlixtTor(reactContext)); + } +} diff --git a/android/app/src/main/java/com/blixtwallet/BlixtTorUtils.java b/android/app/src/main/java/com/blixtwallet/BlixtTorUtils.java new file mode 100644 index 000000000..177658ddc --- /dev/null +++ b/android/app/src/main/java/com/blixtwallet/BlixtTorUtils.java @@ -0,0 +1,16 @@ +package com.blixtwallet; + +import com.blixtwallet.BuildConfig; + +public class BlixtTorUtils { + public static int getListenPort() { + int listenPort = 9760; + if (BuildConfig.CHAIN.equals("testnet")) { + listenPort += 10; + } + if (BuildConfig.DEBUG) { + listenPort += 100; + } + return listenPort; + } +} diff --git a/android/app/src/main/java/com/blixtwallet/LndMobile.java b/android/app/src/main/java/com/blixtwallet/LndMobile.java index 4b069d94a..148e58012 100644 --- a/android/app/src/main/java/com/blixtwallet/LndMobile.java +++ b/android/app/src/main/java/com/blixtwallet/LndMobile.java @@ -1,7 +1,5 @@ package com.blixtwallet; -// import com.blixtwallet.tor.BlixtTorUtils; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; @@ -63,14 +61,13 @@ import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableType; import com.facebook.react.modules.core.DeviceEventManagerModule; -import com.facebook.react.modules.permissions.PermissionsModule; - import com.reactnativecommunity.asyncstorage.AsyncLocalStorageUtil; -import com.jakewharton.processphoenix.ProcessPhoenix; -import com.oblador.keychain.KeychainModule; +import com.reactnativecommunity.asyncstorage.ReactDatabaseSupplier; import com.hypertrack.hyperlog.HyperLog; +import org.torproject.jni.TorService; + // TODO break this class up class LndMobile extends ReactContextBaseJavaModule { private final String TAG = "LndMobile"; @@ -242,6 +239,17 @@ public void onServiceDisconnected(ComponentName className) { private LndMobileServiceConnection lndMobileServiceConnection; + private boolean getPersistentServicesEnabled(Context context) { + ReactDatabaseSupplier dbSupplier = ReactDatabaseSupplier.getInstance(context); + SQLiteDatabase db = dbSupplier.get(); + String persistentServicesEnabled = AsyncLocalStorageUtil.getItemImpl(db, "persistentServicesEnabled"); + if (persistentServicesEnabled != null) { + return persistentServicesEnabled.equals("true"); + } + HyperLog.w(TAG, "Could not find persistentServicesEnabled in asyncStorage"); + return false; + } + public LndMobile(ReactApplicationContext reactContext) { super(reactContext); } @@ -284,9 +292,13 @@ public void initialize(Promise promise) { lndMobileServiceConnection = new LndMobileServiceConnection(req); messenger = new Messenger(new IncomingHandler()); // me - + Intent intent = new Intent(getReactApplicationContext(), LndMobileService.class); + if (getPersistentServicesEnabled(getReactApplicationContext())) { + getReactApplicationContext().startForegroundService(intent); + } + // else rely on bindService to start LND getReactApplicationContext().bindService( - new Intent(getReactApplicationContext(), LndMobileService.class), + intent, lndMobileServiceConnection, Context.BIND_AUTO_CREATE ); @@ -353,13 +365,11 @@ public void startLnd(boolean torEnabled, String args, Promise promise) { String params = "--lnddir=" + getReactApplicationContext().getFilesDir().getPath(); if (torEnabled) { - // int listenPort = BlixtTorUtils.getListenPort(); - // int socksPort = BlixtTorUtils.getSocksPort(); - // int controlPort = BlixtTorUtils.getControlPort(); - // params += " --tor.active --tor.socks=127.0.0.1:" + socksPort + " --tor.control=127.0.0.1:" + controlPort; - // params += " --tor.v3 --listen=localhost:" + listenPort; - } - else { + int listenPort = BlixtTorUtils.getListenPort(); + String controlSocket = "unix://" + getReactApplicationContext().getDir(TorService.class.getSimpleName(), Context.MODE_PRIVATE).getAbsolutePath() + "/data/ControlSocket"; + params += " --tor.active --tor.control=" + controlSocket; + params += " --tor.v3 --listen=localhost:" + listenPort; + } else { // If Tor isn't active, make sure we aren't // listening at all params += " --nolisten"; diff --git a/android/app/src/main/java/com/blixtwallet/LndMobileScheduledSync.java b/android/app/src/main/java/com/blixtwallet/LndMobileScheduledSync.java index f9989a0e9..75479ed75 100644 --- a/android/app/src/main/java/com/blixtwallet/LndMobileScheduledSync.java +++ b/android/app/src/main/java/com/blixtwallet/LndMobileScheduledSync.java @@ -1,34 +1,19 @@ package com.blixtwallet; -import android.util.Log; -import android.content.ComponentName; -import android.content.Context; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteStatement; - import androidx.work.Constraints; import androidx.work.ExistingPeriodicWorkPolicy; import androidx.work.PeriodicWorkRequest; import androidx.work.NetworkType; import androidx.work.WorkInfo; import androidx.work.WorkManager; -import androidx.lifecycle.Observer; -import androidx.annotation.Nullable; -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.Promise; -import com.reactnativecommunity.asyncstorage.ReactDatabaseSupplier; -import com.reactnativecommunity.asyncstorage.AsyncLocalStorageUtil; import com.google.common.util.concurrent.ListenableFuture; import java.util.List; -import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import com.hypertrack.hyperlog.HyperLog; diff --git a/android/app/src/main/java/com/blixtwallet/LndMobileScheduledSyncWorker.java b/android/app/src/main/java/com/blixtwallet/LndMobileScheduledSyncWorker.java index f220063da..d2047a9ba 100644 --- a/android/app/src/main/java/com/blixtwallet/LndMobileScheduledSyncWorker.java +++ b/android/app/src/main/java/com/blixtwallet/LndMobileScheduledSyncWorker.java @@ -1,8 +1,5 @@ package com.blixtwallet; -// import com.blixtwallet.tor.BlixtTor; -// import com.blixtwallet.tor.BlixtTorUtils; - import android.annotation.SuppressLint; import android.app.ActivityManager; import android.content.ComponentName; @@ -18,19 +15,16 @@ import android.os.Process; import android.os.RemoteException; import android.util.Log; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteStatement; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.concurrent.futures.ResolvableFuture; +import androidx.concurrent.futures.CallbackToFutureAdapter; +import com.google.common.util.concurrent.ListenableFuture; import androidx.work.ListenableWorker; import androidx.work.WorkerParameters; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.protobuf.ByteString; - -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteStatement; - import com.facebook.react.bridge.ReactApplicationContext; import com.reactnativecommunity.asyncstorage.ReactDatabaseSupplier; import com.reactnativecommunity.asyncstorage.AsyncLocalStorageUtil; @@ -39,13 +33,14 @@ import com.facebook.react.bridge.ReadableMap; import com.oblador.keychain.KeychainModule; - +import com.google.protobuf.ByteString; import com.hypertrack.hyperlog.HyperLog; +import org.torproject.jni.TorService; + public class LndMobileScheduledSyncWorker extends ListenableWorker { private final String TAG = "LndScheduledSyncWorker"; private final String HANDLERTHREAD_NAME = "blixt_lndmobile_sync"; - private ResolvableFuture future = ResolvableFuture.create(); private Handler incomingHandler; private boolean lndMobileServiceBound = false; private Messenger messengerService; // The service @@ -53,13 +48,15 @@ public class LndMobileScheduledSyncWorker extends ListenableWorker { private ReactDatabaseSupplier dbSupplier; private boolean lndStarted = false; private boolean torEnabled = false; + private int torSocksPort = -1; private boolean torStarted = false; + private boolean persistentServicesEnabled = false; // Keeps track of how many times we've tried to get info // If this keeps going without `syncedToChain` flipping to `true` // we'll close down lnd and the worker private int numGetInfoCalls = 0; - // BlixtTor blixtTor; + BlixtTor blixtTor; // private enum WorkState { // NOT_STARTED, BOUND, WALLET_UNLOCKED, WAITING_FOR_SYNC, DONE; @@ -75,70 +72,99 @@ public class LndMobileScheduledSyncWorker extends ListenableWorker { public LndMobileScheduledSyncWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { super(context, workerParams); dbSupplier = ReactDatabaseSupplier.getInstance(getApplicationContext()); - // blixtTor = new BlixtTor(new ReactApplicationContext(getApplicationContext())); + blixtTor = new BlixtTor(new ReactApplicationContext(getApplicationContext())); } @Override public ListenableFuture startWork() { - HyperLog.i(TAG, "------------------------------------"); - HyperLog.i(TAG, "Starting scheduled sync work"); - HyperLog.i(TAG, "I am " + getApplicationContext().getPackageName()); - writeLastScheduledSyncAttemptToDb(); - - HyperLog.i(TAG, "MainActivity.started = " + MainActivity.started); - if (MainActivity.started) { - HyperLog.i(TAG, "MainActivity is started, quitting job"); - future.set(Result.success()); - return future; - } - torEnabled = getTorEnabled(); + persistentServicesEnabled = getPersistentServicesEnabled(); + + return CallbackToFutureAdapter.getFuture(completer -> { + HyperLog.i(TAG, "------------------------------------"); + HyperLog.i(TAG, "Starting scheduled sync work"); + HyperLog.i(TAG, "I am " + getApplicationContext().getPackageName()); + writeLastScheduledSyncAttemptToDb(); + + HyperLog.i(TAG, "MainActivity.started = " + MainActivity.started); + if (persistentServicesEnabled || MainActivity.started) { + HyperLog.i(TAG, "MainActivity is started or persistentServicesEnabled = " + persistentServicesEnabled + ", quitting job"); + completer.set(Result.success()); + return null; + } - KeychainModule keychain = new KeychainModule(new ReactApplicationContext(getApplicationContext())); - - WritableMap keychainOptions = Arguments.createMap(); - WritableMap keychainOptionsAuthenticationPrompt = Arguments.createMap(); - keychainOptionsAuthenticationPrompt.putString("title", "Authenticate to retrieve secret"); - keychainOptionsAuthenticationPrompt.putString("cancel", "Cancel"); - keychainOptions.putMap("authenticationPrompt", keychainOptionsAuthenticationPrompt); + KeychainModule keychain = new KeychainModule(new ReactApplicationContext(getApplicationContext())); + + WritableMap keychainOptions = Arguments.createMap(); + WritableMap keychainOptionsAuthenticationPrompt = Arguments.createMap(); + keychainOptionsAuthenticationPrompt.putString("title", "Authenticate to retrieve secret"); + keychainOptionsAuthenticationPrompt.putString("cancel", "Cancel"); + keychainOptions.putMap("authenticationPrompt", keychainOptionsAuthenticationPrompt); + + keychain.getInternetCredentialsForServer("password", keychainOptions, new PromiseWrapper() { + @Override + public void onSuccess(@Nullable Object value) { + HyperLog.d(TAG, "onSuccess"); + + if (value == null) { + HyperLog.e(TAG, "Failed to get wallet password, got null from keychain provider"); + completer.set(Result.failure()); + return; + } else { + HyperLog.d(TAG, "Password data retrieved from keychain"); + final String password = ((ReadableMap) value).getString("password"); + HyperLog.d(TAG, "Password retrieved"); + + if (torEnabled) { + // boolean startTorResult = startTor(); + // if (!startTorResult) { + // Log.e(TAG, "Could not start Tor"); + // future.set(Result.failure()); + // return; + // } + blixtTor.startTor(new PromiseWrapper() { + @Override + void onSuccess(@Nullable Object value) { + HyperLog.i(TAG, "Tor started"); + HyperLog.i(TAG, "torSocksPort: " + (int) value); + torStarted = true; + torSocksPort = (int) value; + + startLndWorkThread(completer, password); + } - keychain.getInternetCredentialsForServer("password", keychainOptions, new PromiseWrapper() { - @Override - public void onSuccess(@Nullable Object value) { - HyperLog.d(TAG, "onSuccess"); + @Override + void onFail(Throwable throwable) { + HyperLog.e(TAG, "Failed to start Tor", throwable); + blixtTor.stopTor(new PromiseWrapper() { + @Override + void onSuccess(@Nullable Object value) { + } - if (value == null) { - HyperLog.e(TAG, "Failed to get wallet password, got null from keychain provider"); - future.set(Result.failure()); - return; - } - else { - HyperLog.d(TAG, "Password data retrieved from keychain"); - final String password = ((ReadableMap) value).getString("password"); - HyperLog.d(TAG, "Password retrieved"); - - if (torEnabled) { - boolean startTorResult = startTor(); - if (!startTorResult) { - future.set(Result.failure()); - return; + @Override + void onFail(Throwable throwable) { + } + }); + completer.set(Result.failure()); + } + }); + } else { + startLndWorkThread(completer, password); } } - startLndWorkThread(future, password); } - } - @Override - public void onFail(Throwable throwable) { - HyperLog.d(TAG, "Failed to get wallet password " + throwable.getMessage(), throwable); - future.set(Result.failure()); - } + @Override + public void onFail(Throwable throwable) { + HyperLog.d(TAG, "Failed to get wallet password " + throwable.getMessage(), throwable); + completer.set(Result.failure()); + } + }); + return null; }); - - return future; } - private void startLndWorkThread(ResolvableFuture future, String password) { + private void startLndWorkThread(CallbackToFutureAdapter.Completer completer, String password) { // Make sure we don't attempt to start lnd twice. // A better fix in the future would be to actually // use MSG_CHECKSTATUS in the future @@ -164,7 +190,7 @@ public void handleMessage(Message msg) { // Just exit if we reach this scenario HyperLog.w(TAG, "WARNING, Got MSG_REGISTER_CLIENT_ACK when lnd should already be started, quitting work."); unbindLndMobileService(); - future.set(Result.success()); + completer.set(Result.success()); return; } } catch (Throwable t) { @@ -198,7 +224,10 @@ public void handleMessage(Message msg) { HyperLog.i(TAG, "Got WalletState.RPC_ACTIVE"); HyperLog.i(TAG, "LndMobileService reports RPC server ready. Sending GetInfo request"); getInfoRequest(); - } else { + } else if (currentState == lnrpc.Stateservice.WalletState.SERVER_ACTIVE) { + HyperLog.i(TAG, "Got WalletState.SERVER_ACTIVE"); + HyperLog.i(TAG, "We do not care about that."); + } else { HyperLog.w(TAG, "SubscribeState got unknown state " + currentState); } } catch (Throwable t) { @@ -232,14 +261,14 @@ public void handleMessage(Message msg) { Handler handler = new Handler(); handler.postDelayed(new Runnable() { public void run() { - stopWorker(true); + stopWorker(true, completer); } }, 10000); } else { if (++numGetInfoCalls == 20) { HyperLog.e(TAG, "GetInfo was called " + numGetInfoCalls + " times and still no syncedToChain = true. shutting down worker."); - stopWorker(false); + stopWorker(false, completer); } else{ HyperLog.i(TAG, "Sleeping 10s then checking again"); Handler handler = new Handler(); @@ -250,7 +279,7 @@ public void run() { } catch (Throwable t) { HyperLog.e(TAG, "Job handler got an exception, shutting down worker.", t); - stopWorker(false); + stopWorker(false, completer); } } }, 10000); @@ -271,7 +300,7 @@ public void run() { } } catch (Throwable t) { HyperLog.e(TAG, "Job handler got an exception, shutting down worker.", t); - stopWorker(false); + stopWorker(false, completer); } } }; @@ -288,70 +317,76 @@ public void run() { thread.run(); } - private void stopWorker(boolean success) { + private void stopWorker(boolean success, CallbackToFutureAdapter.Completer completer) { HyperLog.i(TAG, "Job is done. Quitting"); unbindLndMobileService(); if (torStarted) { - if (!MainActivity.started) { +// if (!MainActivity.started) { HyperLog.i(TAG, "Stopping Tor"); - // blixtTor.stopTor(new PromiseWrapper() { - // @Override - // void onSuccess(@Nullable Object value) { - // HyperLog.i(TAG, "Tor stopped"); - // } - - // @Override - // void onFail(Throwable throwable) { - // HyperLog.e(TAG, "Fail while stopping Tor", throwable); - // } - // }); - future.set(success ? Result.success() : Result.failure()); - killLndProcess(); - return; - } else { - HyperLog.w(TAG, "MainActivity was started when shutting down sync work. I will not stop Tor"); - } + blixtTor.stopTor(new PromiseWrapper() { + @Override + void onSuccess(@Nullable Object value) { + HyperLog.i(TAG,"Tor stopped"); + } + + @Override + void onFail(Throwable throwable) { + HyperLog.e(TAG, "Fail while stopping Tor", throwable); + } + }); +// } else { +// HyperLog.w(TAG, "MainActivity was started when shutting down sync work. I will not stop Tor"); +// } } new Handler().postDelayed(new Runnable() { @Override public void run() { HyperLog.i(TAG, "Calling future.set(Result.success());"); - future.set(success ? Result.success() : Result.failure()); + completer.set(success ? Result.success() : Result.failure()); } }, 1500); } - private boolean startTor() { + private boolean startTor(CallbackToFutureAdapter.Completer completer) { HyperLog.i(TAG, "Starting Tor"); - // blixtTor.startTor(new PromiseWrapper() { - // @Override - // void onSuccess(@Nullable Object value) { - // HyperLog.i(TAG, "Tor started"); - // torStarted = true; - // } - - // @Override - // void onFail(Throwable throwable) { - // HyperLog.e(TAG, "Failed to start Tor", throwable); - // future.set(Result.failure()); - // } - // }); - // int torTries = 0; - // while (!torStarted) { - // if (torTries++ > 30) { - // HyperLog.e(TAG, "Couldn't start Tor."); - // future.set(Result.failure()); - // return false; - // } - // HyperLog.i(TAG, "Waiting for Tor to start"); - // try { - // Thread.sleep(1500); - // } catch (InterruptedException e) { - // e.printStackTrace(); - // } - // } + blixtTor.startTor(new PromiseWrapper() { + @Override + void onSuccess(@Nullable Object value) { + HyperLog.i(TAG, "Tor started"); + HyperLog.i(TAG, "torSocksPort: " + (int) value); + torStarted = true; + torSocksPort = (int) value; + } + + @Override + void onFail(Throwable throwable) { + HyperLog.e(TAG, "Failed to start Tor", throwable); + blixtTor.stopTor(new PromiseWrapper() { + @Override + void onSuccess(@Nullable Object value) {} + + @Override + void onFail(Throwable throwable) {} + }); + completer.set(Result.failure()); + } + }); + int torTries = 0; + while (!torStarted) { + if (torTries++ > 40) { + HyperLog.e(TAG, "Couldn't start Tor."); + completer.set(Result.failure()); + return false; + } + HyperLog.i(TAG, "Waiting for Tor to start"); + try { + Thread.sleep(1500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } return true; } @@ -361,12 +396,10 @@ private void startLnd() throws RemoteException { Bundle bundle = new Bundle(); String params = "--lnddir=" + getApplicationContext().getFilesDir().getPath(); if (torEnabled) { - // HyperLog.d(TAG, "Adding Tor params for starting lnd"); - // int socksPort = BlixtTorUtils.getSocksPort(); - // int controlPort = BlixtTorUtils.getControlPort(); - // params += " --tor.active --tor.socks=127.0.0.1:" + socksPort + " --tor.control=127.0.0.1:" + controlPort; - // // params += " --tor.v3 --listen=localhost"; - // params += " --nolisten"; + String controlSocket = "unix://" + getApplicationContext().getDir(TorService.class.getSimpleName(), Context.MODE_PRIVATE).getAbsolutePath() + "/data/ControlSocket"; + HyperLog.d(TAG, "Adding Tor params for starting lnd, torSocksPort: " + torSocksPort + ", controlSocket: " + controlSocket); + params += " --tor.active --tor.socks=127.0.0.1:" + torSocksPort + " --tor.control=" + controlSocket; + params += " --nolisten"; } else { // If Tor isn't active, make sure we aren't @@ -435,6 +468,16 @@ private void unbindLndMobileService() { } } + private boolean getPersistentServicesEnabled() { + SQLiteDatabase db = dbSupplier.get(); + String persistentServicesEnabled = AsyncLocalStorageUtil.getItemImpl(db, "persistentServicesEnabled"); + if (persistentServicesEnabled != null) { + return persistentServicesEnabled.equals("true"); + } + HyperLog.w(TAG, "Could not find persistentServicesEnabled in asyncStorage"); + return false; + } + private boolean getTorEnabled() { SQLiteDatabase db = dbSupplier.get(); String torEnabled = AsyncLocalStorageUtil.getItemImpl(db, "torEnabled"); diff --git a/android/app/src/main/java/com/blixtwallet/LndMobileService.java b/android/app/src/main/java/com/blixtwallet/LndMobileService.java index 3bdf9c9f3..75f1e9e0f 100644 --- a/android/app/src/main/java/com/blixtwallet/LndMobileService.java +++ b/android/app/src/main/java/com/blixtwallet/LndMobileService.java @@ -1,10 +1,16 @@ package com.blixtwallet; import android.app.ActivityManager; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; +import android.database.sqlite.SQLiteDatabase; import android.os.IBinder; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -14,7 +20,6 @@ import android.util.Base64; import android.util.Log; - import lndmobile.Callback; import lndmobile.Lndmobile; import lndmobile.RecvStream; @@ -27,6 +32,8 @@ import java.util.Map; import java.util.Set; import java.util.HashSet; +import com.reactnativecommunity.asyncstorage.AsyncLocalStorageUtil; +import com.reactnativecommunity.asyncstorage.ReactDatabaseSupplier; import com.google.protobuf.ByteString; @@ -34,6 +41,7 @@ public class LndMobileService extends Service { private static final String TAG = "LndMobileService"; + private final int ONGOING_NOTIFICATION_ID = 1; boolean lndStarted = false; boolean subscribeInvoicesStreamActive = false; Set streamsStarted = new HashSet(); @@ -70,6 +78,10 @@ public class LndMobileService extends Service { private Map streamMethods = new HashMap<>(); private Map writeStreams = new HashMap<>(); + + + private NotificationManager notificationManager; + private static boolean isReceiveStream(Method m) { return m.toString().contains("RecvStream"); } @@ -521,6 +533,55 @@ void sendToClients(Message msg) { } } + private boolean getPersistentServicesEnabled(Context context) { + ReactDatabaseSupplier dbSupplier = ReactDatabaseSupplier.getInstance(context); + SQLiteDatabase db = dbSupplier.get(); + String persistentServicesEnabled = AsyncLocalStorageUtil.getItemImpl(db, "persistentServicesEnabled"); + if (persistentServicesEnabled != null) { + return persistentServicesEnabled.equals("true"); + } + HyperLog.w(TAG, "Could not find persistentServicesEnabled in asyncStorage"); + return false; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startid) { + HyperLog.v(TAG, "onStartCommand()"); + if (intent != null && intent.getAction() != null && intent.getAction().equals("com.blixtwallet.android.intent.action.STOP")) { + Log.i(TAG, "Received stopForeground Intent"); + stopForeground(true); + stopSelf(); + return START_NOT_STICKY; + } else { + boolean persistentServicesEnabled = getPersistentServicesEnabled(this); + // persistent services on, start service as foreground-svc + if (persistentServicesEnabled) { + Intent notificationIntent = new Intent (this, MainActivity.class); + PendingIntent pendingIntent = + PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannel chan = new NotificationChannel(BuildConfig.APPLICATION_ID, "blixt", NotificationManager.IMPORTANCE_NONE); + chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE); + notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + assert notificationManager != null; + notificationManager.createNotificationChannel(chan); + } + Notification notification = new Notification.Builder(this, BuildConfig.APPLICATION_ID) + .setContentTitle("LND") + .setContentText("LND is running in the background") + .setSmallIcon(R.drawable.ic_stat_ic_notification) + .setContentIntent(pendingIntent) + .setTicker("Blixt Wallet") + .setOngoing(true) + .build(); + startForeground(ONGOING_NOTIFICATION_ID, notification); + } + } + + // else noop, instead of calling startService, start will be handled by binding + return startid; + } + @Override public IBinder onBind(Intent intent) { HyperLog.v(TAG, "onBind()"); @@ -596,6 +657,9 @@ private boolean killLndProcess() { } private void stopLnd(Messenger recipient, int request) { + if (notificationManager != null) { + notificationManager.cancelAll(); + } Lndmobile.stopDaemon( lnrpc.LightningOuterClass.StopRequest.newBuilder().build().toByteArray(), new Callback() { diff --git a/android/app/src/main/java/com/blixtwallet/LndMobileTools.java b/android/app/src/main/java/com/blixtwallet/LndMobileTools.java index 93e9c7c51..1d212333d 100644 --- a/android/app/src/main/java/com/blixtwallet/LndMobileTools.java +++ b/android/app/src/main/java/com/blixtwallet/LndMobileTools.java @@ -1,7 +1,5 @@ package com.blixtwallet; -// import com.blixtwallet.tor.BlixtTorUtils; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; @@ -70,6 +68,8 @@ import com.hypertrack.hyperlog.HyperLog; +import org.torproject.jni.TorService; + // TODO break this class up class LndMobileTools extends ReactContextBaseJavaModule { final String TAG = "LndMobileTools"; @@ -78,6 +78,17 @@ public LndMobileTools(ReactApplicationContext reactContext) { super(reactContext); } + private boolean getPersistentServicesEnabled(Context context) { + ReactDatabaseSupplier dbSupplier = ReactDatabaseSupplier.getInstance(context); + SQLiteDatabase db = dbSupplier.get(); + String persistentServicesEnabled = AsyncLocalStorageUtil.getItemImpl(db, "persistentServicesEnabled"); + if (persistentServicesEnabled != null) { + return persistentServicesEnabled.equals("true"); + } + HyperLog.w(TAG, "Could not find persistentServicesEnabled in asyncStorage"); + return false; + } + @Override public String getName() { return "LndMobileTools"; @@ -631,6 +642,12 @@ public void deleteTLSCerts(Promise promise) { @ReactMethod public void restartApp() { + Intent stopTorIntent = new Intent(getReactApplicationContext(), TorService.class); + stopTorIntent.setAction("org.torproject.android.intent.action.STOP"); + getReactApplicationContext().stopService(stopTorIntent); + Intent stopLndIntent = new Intent(getReactApplicationContext(), LndMobileService.class); + stopLndIntent.setAction("com.blixtwallet.android.intent.action.STOP"); + getReactApplicationContext().startService(stopLndIntent); ProcessPhoenix.triggerRebirth(getReactApplicationContext()); } diff --git a/android/app/src/main/java/com/blixtwallet/MainActivity.java b/android/app/src/main/java/com/blixtwallet/MainActivity.java index f5d89e072..d94b6153d 100644 --- a/android/app/src/main/java/com/blixtwallet/MainActivity.java +++ b/android/app/src/main/java/com/blixtwallet/MainActivity.java @@ -6,6 +6,7 @@ import com.facebook.react.defaults.DefaultReactActivityDelegate; import android.os.Bundle; +import androidx.core.content.ContextCompat; /* * Blixt imports here: @@ -20,6 +21,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.lang.ref.WeakReference; + +import dev.doubledot.doki.ui.DokiActivity; public class MainActivity extends ReactActivity { @@ -59,11 +63,13 @@ protected ReactActivityDelegate createReactActivityDelegate() { static byte[] tmpChanBackup; + public static WeakReference currentActivity; // react-native-screens override @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(null); + currentActivity = new WeakReference<>(MainActivity.this); started = true; } @@ -132,4 +138,12 @@ else if (requestCode == INTENT_EXPORTCHANBACKUPFILE && resultCode == Activity.RE } } } + + public void showMsg() { + startActivity(new Intent(MainActivity.this, DokiActivity.class)); + } + + public static MainActivity getActivity() { + return currentActivity.get(); + } } diff --git a/android/app/src/main/java/com/blixtwallet/MainApplication.java b/android/app/src/main/java/com/blixtwallet/MainApplication.java index 47c25e6ba..d3fc769b2 100644 --- a/android/app/src/main/java/com/blixtwallet/MainApplication.java +++ b/android/app/src/main/java/com/blixtwallet/MainApplication.java @@ -39,7 +39,7 @@ protected List getPackages() { packages.add(new LndMobileToolsPackage()); packages.add(new GossipFileScheduledSyncPackage()); packages.add(new LndMobileScheduledSyncPackage()); - // packages.add(new BlixtTorPackage()); + packages.add(new BlixtTorPackage()); packages.add(new RealTimeBlurPackage()); return packages; diff --git a/android/app/src/main/java/com/msopentech/thali/android/installer/AndroidTorInstaller.java b/android/app/src/main/java/com/msopentech/thali/android/installer/AndroidTorInstaller.java new file mode 100644 index 000000000..8a13f7a99 --- /dev/null +++ b/android/app/src/main/java/com/msopentech/thali/android/installer/AndroidTorInstaller.java @@ -0,0 +1,89 @@ +package com.msopentech.thali.android.installer;/* +Copyright (c) Microsoft Open Technologies, Inc. +All Rights Reserved +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, +INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +MERCHANTABLITY OR NON-INFRINGEMENT. +See the Apache 2 License for the specific language governing permissions and limitations under the License. +*/ +//package com.msopentech.thali.android.installer; + + +import android.content.Context; +import android.util.Log; +//import com.msopentech.thali.toronionproxy.TorInstaller; +//import org.torproject.android.binary.TorResourceInstaller; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.concurrent.TimeoutException; + +/** + * Installs Tor for an Android app. This is a wrapper around the TorResourceInstaller. + * + * Since this class only deals with installing Tor, it is up to the developer to implement + * the openBridgesStream which will give the bridges for pluggable transports. A + * typical implementation looks like: + * + * + * public InputStream openBridgesStream() throws IOException { + * return context.getResources().openRawResource(R.raw.bridges); + * } + * + */ +public class AndroidTorInstaller { + +// private final TorResourceInstaller resourceInstaller; + + private static final String TAG = "AndroidTorInstaller"; + + protected final Context context; + + protected File torrcFile; + + /** + * The configDir will be the location of tor configuration files. It contains the files, geoip, geoip6, + * bridges.txt and the default torrc file. + * + * The location of tor executable will be in the Android native library directory for the app. + */ + public AndroidTorInstaller(Context context, File configDir) { +// this.resourceInstaller = new TorResourceInstaller(context, configDir); + this.context = context; + } + + public void updateTorConfigCustom(String content) throws IOException, TimeoutException { + if(torrcFile == null) { + throw new FileNotFoundException("Unable to find torrc file. Have you installed Tor resources?"); + } +// resourceInstaller.updateTorConfigCustom(torrcFile, content); + } + +// @Override + public void setup() throws IOException { +// try { +// File torFile = resourceInstaller.installResources(); +// if(torFile != null) { +// Log.d("AndroidTorInstaller", "tor executable = " + torFile.getAbsolutePath()); +// } else { +// Log.w(TAG, "Failed to setup tor. No tor executable installed"); +// throw new IOException("Failed to Failed to setup tor. No tor executable installed"); +// } + +// this.torrcFile = resourceInstaller.getTorrcFile(); +// if(torrcFile != null) { +// Log.d("AndroidTorInstaller", "torrc = " + torrcFile.getAbsolutePath()); +// } else { +// Log.w(TAG, "Failed to setup tor. No torrc file installed"); +// throw new IOException("Failed to Failed to setup tor. No torrc file installed"); +// } + +// } catch (TimeoutException e) { +// Log.w(TAG, "Failed to setup tor: " + e.getMessage()); +// throw new IOException(e); +// } + } +} \ No newline at end of file diff --git a/android/app/src/main/res/drawable-hdpi/ic_stat_ic_notification.png b/android/app/src/main/res/drawable-hdpi/ic_stat_ic_notification.png new file mode 100644 index 000000000..f73549062 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/ic_stat_ic_notification.png differ diff --git a/android/app/src/main/res/drawable-mdpi/ic_stat_ic_notification.png b/android/app/src/main/res/drawable-mdpi/ic_stat_ic_notification.png new file mode 100644 index 000000000..7681d8777 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_stat_ic_notification.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/ic_stat_ic_notification.png b/android/app/src/main/res/drawable-xhdpi/ic_stat_ic_notification.png new file mode 100644 index 000000000..3f341f60d Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/ic_stat_ic_notification.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_stat_ic_notification.png b/android/app/src/main/res/drawable-xxhdpi/ic_stat_ic_notification.png new file mode 100644 index 000000000..59ac95b9f Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/ic_stat_ic_notification.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/ic_stat_ic_notification.png b/android/app/src/main/res/drawable-xxxhdpi/ic_stat_ic_notification.png new file mode 100644 index 000000000..e2093af54 Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/ic_stat_ic_notification.png differ diff --git a/locales/en.json b/locales/en.json index 745fec29d..41e3e4f0f 100644 --- a/locales/en.json +++ b/locales/en.json @@ -985,6 +985,9 @@ }, "enforceSpeedloaderOnStartup": { "title": "Enforce gossip sync on startup" + }, + "persistentServices": { + "title": "Enable persistent LND and Tor (if enabled)" } } }, diff --git a/src/migration/app-migration.ts b/src/migration/app-migration.ts index 3ca4b335d..02efd77cf 100644 --- a/src/migration/app-migration.ts +++ b/src/migration/app-migration.ts @@ -290,6 +290,12 @@ export const appMigration: IAppMigration[] = [ { async beforeLnd(db, i) { await db.executeSql("ALTER TABLE contact ADD label TEXT NULL"); + } + }, + // Version 35 + { + async beforeLnd(db, i) { + setItemObject(StorageItem.persistentServicesEnabled, false); }, }, // Version 35 @@ -299,5 +305,11 @@ export const appMigration: IAppMigration[] = [ await setItemObject(StorageItem.lastScheduledGossipSync, 0); await setItemObject(StorageItem.lastScheduledGossipSyncAttempt, 0); } - } + }, + // Version 34 + { + async beforeLnd(db, i) { + setItemObject(StorageItem.persistentServicesWarningShown, false); + }, + }, ]; diff --git a/src/state/NotificationManager.ts b/src/state/NotificationManager.ts index 86ffe20f4..fd99a4023 100644 --- a/src/state/NotificationManager.ts +++ b/src/state/NotificationManager.ts @@ -41,7 +41,7 @@ export const notificationManager: INotificationManagerModel = { } } else if (PLATFORM === "android") { const granted = await PermissionsAndroid.request( - "android.permission.POST_NOTIFICATIONS" // FIXME PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS needs newer react-native version + PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS ); if (granted === "denied" || granted === "never_ask_again") { log.w("Post notification permission was denied", [granted]); diff --git a/src/state/Settings.ts b/src/state/Settings.ts index 5b8035b82..d272fdb87 100644 --- a/src/state/Settings.ts +++ b/src/state/Settings.ts @@ -81,6 +81,8 @@ export interface ISettingsModel { changeLndLogLevel: Thunk; changeLndCompactDb: Thunk; changeEnforceSpeedloaderOnStartup: Thunk; + changePersistentServicesEnabled: Thunk; + changePersistentServicesWarningShown: Thunk; setBitcoinUnit: Action; setFiatUnit: Action; @@ -121,6 +123,8 @@ export interface ISettingsModel { setLndLogLevel: Action; setLndCompactDb: Action; setEnforceSpeedloaderOnStartup: Action; + setPersistentServicesEnabled: Action; + setPersistentServicesWarningShown: Action; bitcoinUnit: keyof IBitcoinUnits; fiatUnit: keyof IFiatRates; @@ -161,6 +165,8 @@ export interface ISettingsModel { lndCompactDb: boolean; zeroConfPeers: string[]; enforceSpeedloaderOnStartup: boolean; + persistentServicesEnabled: boolean; + persistentServicesWarningShown: boolean; } export const settings: ISettingsModel = { @@ -235,6 +241,12 @@ export const settings: ISettingsModel = { actions.setEnforceSpeedloaderOnStartup( await getItemObject(StorageItem.enforceSpeedloaderOnStartup || false), ); + actions.setPersistentServicesEnabled( + (await getItemObject(StorageItem.persistentServicesEnabled)) ?? false, + ); + actions.setPersistentServicesWarningShown( + (await getItemObject(StorageItem.persistentServicesWarningShown)) ?? false, + ); log.d("Done"); }), @@ -439,6 +451,16 @@ export const settings: ISettingsModel = { actions.setEnforceSpeedloaderOnStartup(payload); }), + changePersistentServicesEnabled: thunk(async (actions, payload) => { + await setItemObject(StorageItem.persistentServicesEnabled, payload); + actions.setPersistentServicesEnabled(payload); + }), + + changePersistentServicesWarningShown: thunk(async (actions, payload) => { + await setItemObject(StorageItem.persistentServicesWarningShown, payload); + actions.setPersistentServicesWarningShown(payload); + }), + setBitcoinUnit: action((state, payload) => { state.bitcoinUnit = payload; }), @@ -556,6 +578,12 @@ export const settings: ISettingsModel = { setEnforceSpeedloaderOnStartup: action((state, payload) => { state.enforceSpeedloaderOnStartup = payload; }), + setPersistentServicesEnabled: action((state, payload) => { + state.persistentServicesEnabled = payload; + }), + setPersistentServicesWarningShown: action((state, payload) => { + state.persistentServicesWarningShown = payload; + }), bitcoinUnit: "bitcoin", fiatUnit: "USD", @@ -596,4 +624,6 @@ export const settings: ISettingsModel = { lndLogLevel: DEFAULT_LND_LOG_LEVEL, lndCompactDb: false, enforceSpeedloaderOnStartup: false, + persistentServicesEnabled: false, + persistentServicesWarningShown: false, }; diff --git a/src/state/index.ts b/src/state/index.ts index 4dd3ffdf2..14fc9e49c 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -240,13 +240,13 @@ export const model: IStoreModel = { try { actions.setTorLoading(true); if (PLATFORM === "android") { - await NativeModules.BlixtTor.startTor(); + socksPort = await NativeModules.BlixtTor.startTor(); } else if (PLATFORM === "ios") { const tor = Tor({ stopDaemonOnBackground: false, startDaemonOnActive: true, }); - socksPort = await tor.startIfNotStarted(); + await tor.startIfNotStarted(); } log.i("socksPort", [socksPort]); if (socksPort === 0 && PLATFORM === "ios") { @@ -285,7 +285,12 @@ export const model: IStoreModel = { } } } - + let persistentServicesEnabled = await getItemObjectAsyncStorage(StorageItem.persistentServicesEnabled) ?? false; + let persistentServicesWarningShown = await getItemObjectAsyncStorage(StorageItem.persistentServicesWarningShown) ?? false; + if (persistentServicesEnabled && !persistentServicesWarningShown) { + await setItemObject(StorageItem.persistentServicesWarningShown, true); + await NativeModules.BlixtTor.showMsg(); + } log.v("Running LndMobile.initialize()"); const initReturn = await initialize(); log.i("initialize done", [initReturn]); diff --git a/src/storage/app.ts b/src/storage/app.ts index 4ee528aa4..b587ff773 100644 --- a/src/storage/app.ts +++ b/src/storage/app.ts @@ -81,6 +81,8 @@ export enum StorageItem { // const enums not supported in Babel 7... lndCompactDb = "lndCompactDb", zeroConfPeers = "zeroConfPeers", enforceSpeedloaderOnStartup = "enforceSpeedloaderOnStartup", + persistentServicesEnabled = "persistentServicesEnabled", + persistentServicesWarningShown = "persistentServicesWarningShown", } export const setItem = async (key: StorageItem, value: string) => @@ -181,6 +183,8 @@ export const clearApp = async () => { removeItem(StorageItem.lndLogLevel), removeItem(StorageItem.lndCompactDb), removeItem(StorageItem.enforceSpeedloaderOnStartup), + removeItem(StorageItem.persistentServicesEnabled), + removeItem(StorageItem.persistentServicesWarningShown), ]); }; @@ -264,5 +268,7 @@ export const setupApp = async () => { setItem(StorageItem.lndLogLevel, DEFAULT_LND_LOG_LEVEL), setItemObject(StorageItem.lndCompactDb, false), setItemObject(StorageItem.enforceSpeedloaderOnStartup, false), + setItemObject(StorageItem.persistentServicesEnabled, false), + setItemObject(StorageItem.persistentServicesWarningShown, false), ]); }; diff --git a/src/windows/InitProcess/DEV_Commands.tsx b/src/windows/InitProcess/DEV_Commands.tsx index 22b879f71..4dfdeabeb 100644 --- a/src/windows/InitProcess/DEV_Commands.tsx +++ b/src/windows/InitProcess/DEV_Commands.tsx @@ -107,6 +107,9 @@ export default function DEV_Commands({ navigation, continueCallback }: IProps) { toast(e.message); } }}>contact label fix + @@ -514,6 +517,7 @@ export default function DEV_Commands({ navigation, continueCallback }: IProps) { + diff --git a/src/windows/Settings/Settings.tsx b/src/windows/Settings/Settings.tsx index 3b12fc344..c12229095 100644 --- a/src/windows/Settings/Settings.tsx +++ b/src/windows/Settings/Settings.tsx @@ -697,6 +697,7 @@ ${t("LN.inbound.dialog.msg3")}`; text: t("buttons.yes", { ns: namespaces.common }), onPress: async () => { try { + await NativeModules.BlixtTor.stopTor(); await NativeModules.LndMobile.stopLnd(); await NativeModules.LndMobileTools.killLnd(); } catch (e) { @@ -1363,6 +1364,14 @@ ${t("experimental.tor.disabled.msg2")}`; await changeEnforceSpeedloaderOnStartup(!enforceSpeedloaderOnStartup); }; + // Persistent services + const persistentServicesEnabled = useStoreState((store) => store.settings.persistentServicesEnabled); + const changePersistentServicesEnabled = useStoreActions((store) => store.settings.changePersistentServicesEnabled); + const changePersistentServicesEnabledPress = async () => { + await changePersistentServicesEnabled(!persistentServicesEnabled); + restartNeeded(); + }; + return ( @@ -2070,6 +2079,13 @@ ${t("experimental.tor.disabled.msg2")}`; } + + + + {t("debug.persistentServices.title")} + + + {t("debug.title")} @@ -2339,7 +2355,13 @@ ${t("experimental.tor.disabled.msg2")}`; /> - + + + + {t("debug.bimodalPathFinding.title")} + + +