diff --git a/frontend/src/components/Notifications/index.tsx b/frontend/src/components/Notifications/index.tsx index 140cb821..711658c0 100644 --- a/frontend/src/components/Notifications/index.tsx +++ b/frontend/src/components/Notifications/index.tsx @@ -12,6 +12,7 @@ import { useNavigate } from 'react-router-dom'; import Close from '@mui/icons-material/Close'; import { type Page } from '../../basic/NavBar'; import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext'; +import { getSettings } from '../../contexts/AppContext'; interface NotificationsProps { rewards: number | undefined; @@ -30,9 +31,9 @@ interface NotificationMessage { } const path = - window.NativeRobosats === undefined - ? '/static/assets/sounds' - : 'file:///android_asset/Web.bundle/assets/sounds'; + getSettings().client == 'mobile' + ? 'file:///android_asset/Web.bundle/assets/sounds' + : '/static/assets/sounds'; const audio = { chat: new Audio(`${path}/chat-open.mp3`), diff --git a/frontend/src/components/TradeBox/EncryptedChat/EncryptedSocketChat/index.tsx b/frontend/src/components/TradeBox/EncryptedChat/EncryptedSocketChat/index.tsx index 29c15683..15c0bda6 100644 --- a/frontend/src/components/TradeBox/EncryptedChat/EncryptedSocketChat/index.tsx +++ b/frontend/src/components/TradeBox/EncryptedChat/EncryptedSocketChat/index.tsx @@ -20,11 +20,12 @@ import { type UseFederationStoreType, FederationContext, } from '../../../../contexts/FederationContext'; +import { getSettings } from '../../../../contexts/AppContext'; const audioPath = - window.NativeRobosats === undefined - ? '/static/assets/sounds' - : 'file:///android_asset/Web.bundle/assets/sounds'; + getSettings().client == 'mobile' + ? 'file:///android_asset/Web.bundle/assets/sounds' + : '/static/assets/sounds'; interface Props { order: Order; diff --git a/frontend/src/components/TradeBox/EncryptedChat/EncryptedTurtleChat/index.tsx b/frontend/src/components/TradeBox/EncryptedChat/EncryptedTurtleChat/index.tsx index 1121f47b..10f2cbf3 100644 --- a/frontend/src/components/TradeBox/EncryptedChat/EncryptedTurtleChat/index.tsx +++ b/frontend/src/components/TradeBox/EncryptedChat/EncryptedTurtleChat/index.tsx @@ -19,6 +19,7 @@ import { } from '../../../../contexts/FederationContext'; import { type UseGarageStoreType, GarageContext } from '../../../../contexts/GarageContext'; import { type Order } from '../../../../models'; +import { getSettings } from '../../../../contexts/AppContext'; interface Props { order: Order; @@ -35,9 +36,9 @@ interface Props { } const audioPath = - window.NativeRobosats === undefined - ? '/static/assets/sounds' - : 'file:///android_asset/Web.bundle/assets/sounds'; + getSettings().client == 'mobile' + ? 'file:///android_asset/Web.bundle/assets/sounds' + : '/static/assets/sounds'; const EncryptedTurtleChat: React.FC = ({ order, diff --git a/frontend/src/contexts/AppContext.tsx b/frontend/src/contexts/AppContext.tsx index a7b5690d..f051a2a7 100644 --- a/frontend/src/contexts/AppContext.tsx +++ b/frontend/src/contexts/AppContext.tsx @@ -36,13 +36,8 @@ export interface SlideDirection { export type TorStatus = 'ON' | 'STARTING' | 'STOPPING' | 'OFF'; -export const isNativeRoboSats = !(window.NativeRobosats === undefined); - const pageFromPath = window.location.pathname.split('/')[1]; const isPagePathEmpty = pageFromPath === ''; -const entryPage: Page = !isNativeRoboSats - ? ((isPagePathEmpty ? 'garage' : pageFromPath) as Page) - : 'garage'; export const closeAll: OpenDialogs = { more: false, @@ -56,6 +51,7 @@ export const closeAll: OpenDialogs = { update: false, profile: false, recovery: false, + thirdParty: '', }; const makeTheme = function (settings: Settings): Theme { @@ -108,7 +104,7 @@ const getOrigin = (network = 'mainnet'): Origin => { return origin; }; -const getSettings = (): Settings => { +export const getSettings = (): Settings => { let settings; const [client, view] = window.RobosatsSettings.split('-'); @@ -120,6 +116,11 @@ const getSettings = (): Settings => { return settings; }; +const entryPage: Page = + getSettings().client == 'mobile' + ? 'garage' + : ((isPagePathEmpty ? 'garage' : pageFromPath) as Page); + export interface WindowSize { width: number; height: number; @@ -159,7 +160,7 @@ export interface UseAppStoreType { export const initialAppContext: UseAppStoreType = { theme: undefined, - torStatus: 'STARTING', + torStatus: 'ON', settings: getSettings(), setSettings: () => {}, page: entryPage, diff --git a/frontend/src/models/Settings.model.ts b/frontend/src/models/Settings.model.ts index 90a9b764..0653a35c 100644 --- a/frontend/src/models/Settings.model.ts +++ b/frontend/src/models/Settings.model.ts @@ -51,6 +51,7 @@ class BaseSettings { this.host = getHost(); const [client] = window.RobosatsSettings.split('-'); + this.client = client; const stopNotifications = systemClient.getItem('settings_stop_notifications'); this.stopNotifications = client === 'mobile' && stopNotifications === 'true'; @@ -63,6 +64,7 @@ class BaseSettings { public frontend: 'basic' | 'pro' = 'basic'; public mode: 'light' | 'dark' = 'light'; + public client: 'web' | 'mobile' = 'web'; public fontSize: number = 14; public lightQRs: boolean = false; public language?: Language; diff --git a/frontend/webpack.config.ts b/frontend/webpack.config.ts index 38330bd9..cb1d02d9 100644 --- a/frontend/webpack.config.ts +++ b/frontend/webpack.config.ts @@ -236,6 +236,60 @@ const configMobile: Configuration = { }, }, }), + new HtmlWebpackPlugin({ + template: path.resolve(__dirname, 'templates/frontend/index.ejs'), + templateParameters: { + pro: false, + }, + filename: path.resolve(__dirname, '../mobile_new/app/src/main/assets/index.html'), + inject: 'body', + robosatsSettings: 'mobile-basic', + basePath: 'file:///android_asset/Web.bundle/', + }), + new FileManagerPlugin({ + events: { + onEnd: { + copy: [ + { + source: path.resolve(__dirname, 'static/css'), + destination: path.resolve( + __dirname, + '../mobile_new/app/src/main/assets/Web.bundle/static/css', + ), + }, + { + source: path.resolve(__dirname, 'static/assets/sounds'), + destination: path.resolve( + __dirname, + '../mobile_new/app/src/main/assets/Web.bundle/assets/sounds', + ), + }, + { + source: path.resolve(__dirname, 'static/federation'), + destination: path.resolve( + __dirname, + '../mobile_new/app/src/main/assets/Web.bundle/assets/federation', + ), + }, + ], + }, + }, + }), + new FileManagerPlugin({ + events: { + onEnd: { + copy: [ + { + source: path.resolve(__dirname, '../mobile/html/Web.bundle/static/frontend'), + destination: path.resolve( + __dirname, + '../mobile_new/app/src/main/assets/static/frontend', + ), + }, + ], + }, + }, + }), ], }; diff --git a/mobile_new/app/.gitignore b/mobile_new/app/.gitignore index 42afabfd..9de4b82c 100644 --- a/mobile_new/app/.gitignore +++ b/mobile_new/app/.gitignore @@ -1 +1,2 @@ -/build \ No newline at end of file +/build +/src/main/assets/* \ No newline at end of file diff --git a/mobile_new/app/build.gradle.kts b/mobile_new/app/build.gradle.kts index 99ad97cf..51013fc6 100644 --- a/mobile_new/app/build.gradle.kts +++ b/mobile_new/app/build.gradle.kts @@ -1,3 +1,5 @@ +import com.android.build.api.dsl.Packaging + plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) @@ -33,6 +35,34 @@ android { kotlinOptions { jvmTarget = "11" } + + splits { + + // Configures multiple APKs based on ABI. This helps keep the size + // down, since PT binaries can be large. + abi { + + // Enables building multiple APKs per ABI. + isEnable = true + + // By default, all ABIs are included, so use reset() and include to specify + // that we only want APKs for x86 and x86_64, armeabi-v7a, and arm64-v8a. + + // Resets the list of ABIs that Gradle should create APKs for to none. + reset() + + // Specifies a list of ABIs that Gradle should create APKs for. + include("x86", "armeabi-v7a", "arm64-v8a", "x86_64") + + // Specify whether you wish to also generate a universal APK that + // includes _all_ ABIs. + isUniversalApk = true + } + } + + fun Packaging.() { + jniLibs.useLegacyPackaging = true + } } dependencies { @@ -40,6 +70,9 @@ dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) implementation(libs.material) + implementation(libs.kmp.tor) + // Add the KMP Tor binary dependency (contains the native .so files) + implementation(libs.kmp.tor.binary) implementation(libs.androidx.activity) implementation(libs.androidx.constraintlayout) testImplementation(libs.junit) diff --git a/mobile_new/app/src/main/AndroidManifest.xml b/mobile_new/app/src/main/AndroidManifest.xml index c6722a31..e887e5fb 100644 --- a/mobile_new/app/src/main/AndroidManifest.xml +++ b/mobile_new/app/src/main/AndroidManifest.xml @@ -5,6 +5,7 @@ + connection.setRequestProperty(key, value) + } + + // Special handling for OPTIONS (CORS preflight) requests + if (request.method == "OPTIONS") { + // Handle preflight CORS request + connection.requestMethod = "OPTIONS" + connection.setRequestProperty("Access-Control-Request-Method", + request.requestHeaders["Access-Control-Request-Method"] ?: "GET, POST, OPTIONS") + connection.setRequestProperty("Access-Control-Request-Headers", + request.requestHeaders["Access-Control-Request-Headers"] ?: "") + } else { + // Set request method for non-OPTIONS requests + connection.requestMethod = request.method + } + + // Try to connect + connection.connect() + val responseCode = connection.responseCode + + // Get content type + val mimeType = connection.contentType ?: "text/plain" + val encoding = connection.contentEncoding ?: "UTF-8" + + Log.d("TorProxy", "Successfully proxied request to $url (HTTP ${connection.responseCode})") + + // Get the correct input stream based on response code + val inputStream = if (responseCode >= 400) { + connection.errorStream ?: java.io.ByteArrayInputStream(byteArrayOf()) + } else { + connection.inputStream + } + + // Create response headers map with CORS headers + val responseHeaders = HashMap() + + // Add CORS headers + responseHeaders["Access-Control-Allow-Origin"] = "*" + responseHeaders["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS" + responseHeaders["Access-Control-Allow-Headers"] = "Origin, X-Requested-With, Content-Type, Accept" + responseHeaders["Access-Control-Allow-Credentials"] = "true" + + // Copy original response headers + for (i in 0 until connection.headerFields.size) { + val key = connection.headerFields.keys.elementAtOrNull(i) + if (key != null) { + val value = connection.getHeaderField(key) + if (value != null) { + responseHeaders[key] = value + } + } + } + + // Return proxied response with CORS headers + return WebResourceResponse( + mimeType, + encoding, + responseCode, + "OK", + responseHeaders, + inputStream + ) + } else { + // For non-HTTP connections (rare) + val inputStream = connection.getInputStream() + Log.d("TorProxy", "Successfully established non-HTTP connection to $url") + return WebResourceResponse( + "application/octet-stream", + "UTF-8", + inputStream + ) + } + } catch (e: Exception) { + Log.e("TorProxy", "Error proxying request: $urlString - ${e.message}", e) + + // For non-onion domains, let the system handle it + return super.shouldInterceptRequest(view, request) + } + } + + override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean { + // Verify Tor is still connected before allowing any request + if (!torKmp.isConnected()) { + Log.e("SecurityError", "Tor disconnected during navigation") + return true // Block the request + } + return false // Let our proxied client handle it + } + + override fun onReceivedError(view: WebView, request: WebResourceRequest, error: WebResourceError) { + Log.e("WebViewError", "Error loading resource: ${error.description}") + super.onReceivedError(view, request, error) + } + + override fun onPageStarted(view: WebView, url: String, favicon: android.graphics.Bitmap?) { + // Verify Tor is connected when page starts loading + if (!torKmp.isConnected()) { + Log.e("SecurityError", "Tor disconnected as page started loading") + view.stopLoading() + return + } + super.onPageStarted(view, url, favicon) + } + + override fun onPageFinished(view: WebView, url: String) { + // Verify Tor is still connected when page finishes loading + if (!torKmp.isConnected()) { + Log.e("SecurityError", "Tor disconnected after page loaded") + return + } + + // No JavaScript injection - just log page load completion + Log.d("WebView", "Page finished loading: $url") + + super.onPageFinished(view, url) + } + } + + // Now it's safe to load the local HTML file + webView.loadUrl("file:///android_asset/index.html") + } + } catch (e: Exception) { + Log.e("WebViewSetup", "Security error in WebView setup: ${e.message}", e) + + // Show error and exit - DO NOT LOAD WEBVIEW + runOnUiThread { + // Show toast with error + android.widget.Toast.makeText( + this, + "SECURITY ERROR: Cannot set up secure browsing: ${e.message}", + android.widget.Toast.LENGTH_LONG + ).show() + } + } + }.start() + } + + private fun setupProxyForWebView() { + // Triple-check Tor is connected + if (!torKmp.isConnected()) { + throw SecurityException("Cannot set up proxy - Tor is not connected") + } + + try { + // Get the proxy from TorKmpManager, handling possible exceptions + val proxy = TorKmpManager.getTorKmpObject().proxy ?: + throw SecurityException("Tor proxy is null despite Tor being connected") + + val inetSocketAddress = proxy.address() as InetSocketAddress + val host = inetSocketAddress.hostName + val port = inetSocketAddress.port + + if (host.isBlank() || port <= 0) { + throw SecurityException("Invalid Tor proxy address: $host:$port") + } + + Log.d("WebViewProxy", "Setting up Tor proxy: $host:$port") + + // Set up the proxy + setWebViewProxy(applicationContext, host, port) + + // Verify proxy was set correctly + if (System.getProperty("http.proxyHost") != host || + System.getProperty("http.proxyPort") != port.toString()) { + throw SecurityException("Proxy verification failed - system properties don't match expected values") + } + + Log.d("WebViewProxy", "Proxy setup completed successfully") + } catch (e: Exception) { + Log.e("WebViewProxy", "Error setting up proxy: ${e.message}", e) + throw SecurityException("Failed to set up Tor proxy: ${e.message}", e) + } + } + + /** + * Sets the proxy for WebView using the most direct approach that's known to work with Tor + */ + private fun setWebViewProxy(context: Context, proxyHost: String, proxyPort: Int) { + try { + // First set system properties (required as a foundation) + System.setProperty("http.proxyHost", proxyHost) + System.setProperty("http.proxyPort", proxyPort.toString()) + System.setProperty("https.proxyHost", proxyHost) + System.setProperty("https.proxyPort", proxyPort.toString()) + System.setProperty("proxy.host", proxyHost) + System.setProperty("proxy.port", proxyPort.toString()) + + Log.d("WebViewProxy", "Set system proxy properties") + + // Create and apply a proxy at the application level + val proxyClass = Class.forName("android.net.ProxyInfo") + val proxyConstructor = proxyClass.getConstructor(String::class.java, Int::class.javaPrimitiveType, String::class.java) + val proxyInfo = proxyConstructor.newInstance(proxyHost, proxyPort, null) + + try { + // Try to set global proxy through ConnectivityManager + val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) + val setDefaultProxyMethod = connectivityManager.javaClass.getDeclaredMethod("setDefaultProxy", proxyClass) + setDefaultProxyMethod.isAccessible = true + setDefaultProxyMethod.invoke(connectivityManager, proxyInfo) + Log.d("WebViewProxy", "Set proxy via ConnectivityManager") + } catch (e: Exception) { + Log.w("WebViewProxy", "Could not set proxy via ConnectivityManager: ${e.message}") + } + + // WebView operations must be run on the UI thread + runOnUiThread { + try { + // Force WebView to use proxy via direct settings (must be on UI thread) + webView.settings.javaClass.getDeclaredMethod("setHttpProxy", String::class.java, Int::class.javaPrimitiveType) + ?.apply { isAccessible = true } + ?.invoke(webView.settings, proxyHost, proxyPort) + Log.d("WebViewProxy", "Applied proxy directly to WebView settings") + } catch (e: Exception) { + Log.w("WebViewProxy", "Could not set proxy directly on WebView settings: ${e.message}") + // Continue - we'll rely on system properties and connection-level proxying + } + } + + // Wait to ensure UI thread operations complete + // This prevents race conditions with WebView operations + Thread.sleep(500) + + Log.d("WebViewProxy", "Proxy setup completed") + } catch (e: Exception) { + Log.e("WebViewProxy", "Error setting WebView proxy", e) + throw SecurityException("Failed to set WebView proxy: ${e.message}", e) + } } } diff --git a/mobile_new/app/src/main/java/com/koalasat/robosats/tor/EnumTorState.kt b/mobile_new/app/src/main/java/com/koalasat/robosats/tor/EnumTorState.kt new file mode 100644 index 00000000..8ddc7da5 --- /dev/null +++ b/mobile_new/app/src/main/java/com/koalasat/robosats/tor/EnumTorState.kt @@ -0,0 +1,8 @@ +package com.robosats.tor + +enum class EnumTorState { + STARTING, + ON, + STOPPING, + OFF +} diff --git a/mobile_new/app/src/main/java/com/koalasat/robosats/tor/TorKmpManager.kt b/mobile_new/app/src/main/java/com/koalasat/robosats/tor/TorKmpManager.kt new file mode 100644 index 00000000..9accff0c --- /dev/null +++ b/mobile_new/app/src/main/java/com/koalasat/robosats/tor/TorKmpManager.kt @@ -0,0 +1,403 @@ +package com.robosats.tor + +import android.app.Application +import android.util.Log +import android.widget.Toast +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import io.matthewnelson.kmp.tor.KmpTorLoaderAndroid +import io.matthewnelson.kmp.tor.TorConfigProviderAndroid +import io.matthewnelson.kmp.tor.common.address.* +import io.matthewnelson.kmp.tor.controller.common.config.TorConfig +import io.matthewnelson.kmp.tor.controller.common.config.TorConfig.Option.* +import io.matthewnelson.kmp.tor.controller.common.config.TorConfig.Setting.* +import io.matthewnelson.kmp.tor.controller.common.control.usecase.TorControlInfoGet +import io.matthewnelson.kmp.tor.controller.common.control.usecase.TorControlSignal +import io.matthewnelson.kmp.tor.controller.common.events.TorEvent +import io.matthewnelson.kmp.tor.manager.TorManager +import io.matthewnelson.kmp.tor.manager.TorServiceConfig +import io.matthewnelson.kmp.tor.manager.common.TorControlManager +import io.matthewnelson.kmp.tor.manager.common.TorOperationManager +import io.matthewnelson.kmp.tor.manager.common.event.TorManagerEvent +import io.matthewnelson.kmp.tor.manager.common.state.isOff +import io.matthewnelson.kmp.tor.manager.common.state.isOn +import io.matthewnelson.kmp.tor.manager.common.state.isStarting +import io.matthewnelson.kmp.tor.manager.common.state.isStopping +import io.matthewnelson.kmp.tor.manager.R +import kotlinx.coroutines.* +import java.net.InetSocketAddress +import java.net.Proxy +import java.util.concurrent.ExecutionException + +class TorKmp(application : Application) { + + private val TAG = "TorListener" + + private val providerAndroid by lazy { + object : TorConfigProviderAndroid(context = application) { + override fun provide(): TorConfig { + return TorConfig.Builder { + // Set multiple ports for all of the things + val dns = Ports.Dns() + put(dns.set(AorDorPort.Value(PortProxy(9252)))) + put(dns.set(AorDorPort.Value(PortProxy(9253)))) + + val socks = Ports.Socks() + put(socks.set(AorDorPort.Value(PortProxy(9254)))) + put(socks.set(AorDorPort.Value(PortProxy(9255)))) + + val http = Ports.HttpTunnel() + put(http.set(AorDorPort.Value(PortProxy(9258)))) + put(http.set(AorDorPort.Value(PortProxy(9259)))) + + val trans = Ports.Trans() + put(trans.set(AorDorPort.Value(PortProxy(9262)))) + put(trans.set(AorDorPort.Value(PortProxy(9263)))) + + // If a port (9263) is already taken (by ^^^^ trans port above) + // this will take its place and "overwrite" the trans port entry + // because port 9263 is taken. + put(socks.set(AorDorPort.Value(PortProxy(9263)))) + + // Set Flags + socks.setFlags(setOf( + Ports.Socks.Flag.OnionTrafficOnly + )).setIsolationFlags(setOf( + Ports.IsolationFlag.IsolateClientAddr, + )).set(AorDorPort.Value(PortProxy(9264))) + put(socks) + + // reset our socks object to defaults + socks.setDefault() + + // Not necessary, as if ControlPort is missing it will be + // automatically added for you; but for demonstration purposes... +// put(Ports.Control().set(AorDorPort.Auto)) + + // Use a UnixSocket instead of TCP for the ControlPort. + // + // A unix domain socket will always be preferred on Android + // if neither Ports.Control or UnixSockets.Control are provided. + put(UnixSockets.Control().set(FileSystemFile( + workDir.builder { + + // Put the file in the "data" directory + // so that we avoid any directory permission + // issues. + // + // Note that DataDirectory is automatically added + // for you if it is not present in your provided + // config. If you set a custom Path for it, you + // should use it here. + addSegment(DataDirectory.DEFAULT_NAME) + + addSegment(UnixSockets.Control.DEFAULT_NAME) + } + ))) + + // Use a UnixSocket instead of TCP for the SocksPort. + put(UnixSockets.Socks().set(FileSystemFile( + workDir.builder { + + // Put the file in the "data" directory + // so that we avoid any directory permission + // issues. + // + // Note that DataDirectory is automatically added + // for you if it is not present in your provided + // config. If you set a custom Path for it, you + // should use it here. + addSegment(DataDirectory.DEFAULT_NAME) + + addSegment(UnixSockets.Socks.DEFAULT_NAME) + } + ))) + + // For Android, disabling & reducing connection padding is + // advisable to minimize mobile data usage. + put(ConnectionPadding().set(AorTorF.False)) + put(ConnectionPaddingReduced().set(TorF.True)) + + // Tor default is 24h. Reducing to 10 min helps mitigate + // unnecessary mobile data usage. + put(DormantClientTimeout().set(Time.Minutes(10))) + + // Tor defaults this setting to false which would mean if + // Tor goes dormant, the next time it is started it will still + // be in the dormant state and will not bootstrap until being + // set to "active". This ensures that if it is a fresh start, + // dormancy will be cancelled automatically. + put(DormantCanceledByStartup().set(TorF.True)) + + // If planning to use v3 Client Authentication in a persistent + // manner (where private keys are saved to disk via the "Persist" + // flag), this is needed to be set. + put(ClientOnionAuthDir().set(FileSystemDir( + workDir.builder { addSegment(ClientOnionAuthDir.DEFAULT_NAME) } + ))) + + val hsPath = workDir.builder { + addSegment(HiddenService.DEFAULT_PARENT_DIR_NAME) + addSegment("test_service") + } + // Add Hidden services + put(HiddenService() + .setPorts(ports = setOf( + // Use a unix domain socket to communicate via IPC instead of over TCP + HiddenService.UnixSocket(virtualPort = Port(80), targetUnixSocket = hsPath.builder { + addSegment(HiddenService.UnixSocket.DEFAULT_UNIX_SOCKET_NAME) + }), + )) + .setMaxStreams(maxStreams = HiddenService.MaxStreams(value = 2)) + .setMaxStreamsCloseCircuit(value = TorF.True) + .set(FileSystemDir(path = hsPath)) + ) + + put(HiddenService() + .setPorts(ports = setOf( + HiddenService.Ports(virtualPort = Port(80), targetPort = Port(1030)), // http + HiddenService.Ports(virtualPort = Port(443), targetPort = Port(1030)) // https + )) + .set(FileSystemDir(path = + workDir.builder { + addSegment(HiddenService.DEFAULT_PARENT_DIR_NAME) + addSegment("test_service_2") + } + )) + ) + }.build() + } + } + } + + private val loaderAndroid by lazy { + KmpTorLoaderAndroid(provider = providerAndroid) + } + + private val manager: TorManager by lazy { + TorManager.newInstance(application = application, loader = loaderAndroid, requiredEvents = null) + } + + // only expose necessary interfaces + val torOperationManager: TorOperationManager get() = manager + val torControlManager: TorControlManager get() = manager + + private val listener = TorListener() + + val events: LiveData get() = listener.eventLines + + private val appScope by lazy { + CoroutineScope(Dispatchers.Main.immediate + SupervisorJob()) + } + + val torStateLiveData: MutableLiveData = MutableLiveData() + get() = field + var torState: TorState = TorState() + get() = field + + var proxy: Proxy? = null + get() = field + + init { + manager.debug(true) + manager.addListener(listener) + listener.addLine(TorServiceConfig.getMetaData(application).toString()) + } + + fun isConnected(): Boolean { + return manager.state.isOn() && manager.state.bootstrap >= 100 + } + + fun isStarting(): Boolean { + return manager.state.isStarting() || + (manager.state.isOn() && manager.state.bootstrap < 100); + } + + + fun newIdentity(appContext: Application) { + appScope.launch { + val result = manager.signal(TorControlSignal.Signal.NewNym) + result.onSuccess { + if (it !is String) { + listener.addLine(TorControlSignal.NEW_NYM_SUCCESS) + Toast.makeText(appContext, TorControlSignal.NEW_NYM_SUCCESS, Toast.LENGTH_SHORT).show() + return@onSuccess + } + + val post: String? = when { + it.startsWith(TorControlSignal.NEW_NYM_RATE_LIMITED) -> { + // Rate limiting NEWNYM request: delaying by 8 second(s) + val seconds: Int? = it.drop(TorControlSignal.NEW_NYM_RATE_LIMITED.length) + .substringBefore(' ') + .toIntOrNull() + + if (seconds == null) { + it + } else { + appContext.getString( + R.string.kmp_tor_newnym_rate_limited, + seconds + ) + } + } + it == TorControlSignal.NEW_NYM_SUCCESS -> { + appContext.getString(R.string.kmp_tor_newnym_success) + } + else -> { + null + } + } + + if (post != null) { + listener.addLine(post) + Toast.makeText(appContext, post, Toast.LENGTH_SHORT).show() + } + } + result.onFailure { + val msg = "Tor identity change failed" + listener.addLine(msg) + Toast.makeText(appContext, msg, Toast.LENGTH_SHORT).show() + } + } + } + + + private inner class TorListener: TorManagerEvent.Listener() { + private val _eventLines: MutableLiveData = MutableLiveData("") + val eventLines: LiveData = _eventLines + private val events: MutableList = ArrayList(50) + fun addLine(line: String) { + synchronized(this) { + if (events.size > 49) { + events.removeAt(0) + } + events.add(line) + //Log.i(TAG, line) + //_eventLines.value = events.joinToString("\n") + _eventLines.postValue(events.joinToString("\n")) + } + } + + override fun onEvent(event: TorManagerEvent) { + + if (event is TorManagerEvent.State) { + val stateEvent: TorManagerEvent.State = event + val state = stateEvent.torState + torState.progressIndicator = state.bootstrap + val liveTorState = TorState() + liveTorState.progressIndicator = state.bootstrap + + if (state.isOn()) { + if (state.bootstrap >= 100) { + torState.state = EnumTorState.ON + liveTorState.state = EnumTorState.ON + } else { + torState.state = EnumTorState.STARTING + liveTorState.state = EnumTorState.STARTING + } + } else if (state.isStarting()) { + torState.state = EnumTorState.STARTING + liveTorState.state = EnumTorState.STARTING + } else if (state.isOff()) { + torState.state = EnumTorState.OFF + liveTorState.state = EnumTorState.OFF + } else if (state.isStopping()) { + torState.state = EnumTorState.STOPPING + liveTorState.state = EnumTorState.STOPPING + } + torStateLiveData.postValue(liveTorState) + } + addLine(event.toString()) + super.onEvent(event) + } + + override fun onEvent(event: TorEvent.Type.SingleLineEvent, output: String) { + addLine("$event - $output") + + super.onEvent(event, output) + } + + override fun onEvent(event: TorEvent.Type.MultiLineEvent, output: List) { + addLine("multi-line event: $event. See Logs.") + + // these events are many many many lines and should be moved + // off the main thread if ever needed to be dealt with. + val enabled = false + if (enabled) { + appScope.launch(Dispatchers.IO) { + Log.d(TAG, "-------------- multi-line event START: $event --------------") + for (line in output) { + Log.d(TAG, line) + } + Log.d(TAG, "--------------- multi-line event END: $event ---------------") + } + } + + super.onEvent(event, output) + } + + override fun managerEventError(t: Throwable) { + t.printStackTrace() + } + + override fun managerEventAddressInfo(info: TorManagerEvent.AddressInfo) { + if (info.isNull) { + // Tear down HttpClient + } else { + info.socksInfoToProxyAddressOrNull()?.firstOrNull()?.let { proxyAddress -> + @Suppress("UNUSED_VARIABLE") + val socket = InetSocketAddress(proxyAddress.address.value, proxyAddress.port.value) + proxy = Proxy(Proxy.Type.SOCKS, socket) + } + } + } + + override fun managerEventStartUpCompleteForTorInstance() { + // Do one-time things after we're bootstrapped + + appScope.launch { + torControlManager.onionAddNew( + type = OnionAddress.PrivateKey.Type.ED25519_V3, + hsPorts = setOf(HiddenService.Ports(virtualPort = Port(443))), + flags = null, + maxStreams = null, + ).onSuccess { hsEntry -> + addLine( + "New HiddenService: " + + "\n - Address: https://${hsEntry.address.canonicalHostname()}" + + "\n - PrivateKey: ${hsEntry.privateKey}" + ) + + torControlManager.onionDel(hsEntry.address).onSuccess { + addLine("Aaaaaaaaand it's gone...") + }.onFailure { t -> + t.printStackTrace() + } + }.onFailure { t -> + t.printStackTrace() + } + + delay(20_000L) + + torControlManager.infoGet(TorControlInfoGet.KeyWord.Uptime()).onSuccess { uptime -> + addLine("Uptime - $uptime") + }.onFailure { t -> + t.printStackTrace() + } + } + } + } +} + +object TorKmpManager { + private lateinit var torKmp: TorKmp + + @Throws(UninitializedPropertyAccessException::class) + fun getTorKmpObject(): TorKmp { + return torKmp + } + + fun updateTorKmpObject(newKmpObject: TorKmp) { + torKmp = newKmpObject + } +} diff --git a/mobile_new/app/src/main/java/com/koalasat/robosats/tor/TorState.kt b/mobile_new/app/src/main/java/com/koalasat/robosats/tor/TorState.kt new file mode 100644 index 00000000..51052571 --- /dev/null +++ b/mobile_new/app/src/main/java/com/koalasat/robosats/tor/TorState.kt @@ -0,0 +1,14 @@ +package com.robosats.tor + +class TorState { + var state : EnumTorState = EnumTorState.OFF + get() = field + set(value) { + field = value + } + var progressIndicator : Int = 0 + get() = field + set(value) { + field = value + } +} diff --git a/mobile_new/gradle/libs.versions.toml b/mobile_new/gradle/libs.versions.toml index 3b6fa123..68f7a0cb 100644 --- a/mobile_new/gradle/libs.versions.toml +++ b/mobile_new/gradle/libs.versions.toml @@ -9,6 +9,8 @@ appcompat = "1.7.1" material = "1.12.0" activity = "1.10.1" constraintlayout = "2.2.1" +kmpTor= "4.8.10-0-1.4.5" +kmpTorBinary= "4.8.10-0" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -19,8 +21,9 @@ androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version material = { group = "com.google.android.material", name = "material", version.ref = "material" } androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" } androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } +kmp-tor = { group = "io.matthewnelson.kotlin-components", name = "kmp-tor", version.ref = "kmpTor" } +kmp-tor-binary = { group = "io.matthewnelson.kotlin-components", name = "kmp-tor-binary-extract-android", version.ref = "kmpTorBinary" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } - diff --git a/mobile_new/html/Web.bundle/assets/federation/avatars/bazaar.small.webp b/mobile_new/html/Web.bundle/assets/federation/avatars/bazaar.small.webp new file mode 100644 index 00000000..cbdaa1e1 Binary files /dev/null and b/mobile_new/html/Web.bundle/assets/federation/avatars/bazaar.small.webp differ diff --git a/mobile_new/html/Web.bundle/assets/federation/avatars/bazaar.webp b/mobile_new/html/Web.bundle/assets/federation/avatars/bazaar.webp new file mode 100644 index 00000000..816a7c84 Binary files /dev/null and b/mobile_new/html/Web.bundle/assets/federation/avatars/bazaar.webp differ diff --git a/mobile_new/html/Web.bundle/assets/federation/avatars/exp.small.webp b/mobile_new/html/Web.bundle/assets/federation/avatars/exp.small.webp new file mode 100644 index 00000000..09e3b447 Binary files /dev/null and b/mobile_new/html/Web.bundle/assets/federation/avatars/exp.small.webp differ diff --git a/mobile_new/html/Web.bundle/assets/federation/avatars/exp.webp b/mobile_new/html/Web.bundle/assets/federation/avatars/exp.webp new file mode 100644 index 00000000..53e080ad Binary files /dev/null and b/mobile_new/html/Web.bundle/assets/federation/avatars/exp.webp differ diff --git a/mobile_new/html/Web.bundle/assets/federation/avatars/freedomsats.small.webp b/mobile_new/html/Web.bundle/assets/federation/avatars/freedomsats.small.webp new file mode 100644 index 00000000..93fb1b9b Binary files /dev/null and b/mobile_new/html/Web.bundle/assets/federation/avatars/freedomsats.small.webp differ diff --git a/mobile_new/html/Web.bundle/assets/federation/avatars/freedomsats.webp b/mobile_new/html/Web.bundle/assets/federation/avatars/freedomsats.webp new file mode 100644 index 00000000..5ba1c65f Binary files /dev/null and b/mobile_new/html/Web.bundle/assets/federation/avatars/freedomsats.webp differ diff --git a/mobile_new/html/Web.bundle/assets/federation/avatars/lake.small.webp b/mobile_new/html/Web.bundle/assets/federation/avatars/lake.small.webp new file mode 100644 index 00000000..c90d069f Binary files /dev/null and b/mobile_new/html/Web.bundle/assets/federation/avatars/lake.small.webp differ diff --git a/mobile_new/html/Web.bundle/assets/federation/avatars/lake.webp b/mobile_new/html/Web.bundle/assets/federation/avatars/lake.webp new file mode 100644 index 00000000..8936c1cd Binary files /dev/null and b/mobile_new/html/Web.bundle/assets/federation/avatars/lake.webp differ diff --git a/mobile_new/html/Web.bundle/assets/federation/avatars/local.small.webp b/mobile_new/html/Web.bundle/assets/federation/avatars/local.small.webp new file mode 100644 index 00000000..a3807b43 Binary files /dev/null and b/mobile_new/html/Web.bundle/assets/federation/avatars/local.small.webp differ diff --git a/mobile_new/html/Web.bundle/assets/federation/avatars/local.webp b/mobile_new/html/Web.bundle/assets/federation/avatars/local.webp new file mode 100644 index 00000000..ad3552d3 Binary files /dev/null and b/mobile_new/html/Web.bundle/assets/federation/avatars/local.webp differ diff --git a/mobile_new/html/Web.bundle/assets/federation/avatars/moon.small.webp b/mobile_new/html/Web.bundle/assets/federation/avatars/moon.small.webp new file mode 100644 index 00000000..3e3b51a7 Binary files /dev/null and b/mobile_new/html/Web.bundle/assets/federation/avatars/moon.small.webp differ diff --git a/mobile_new/html/Web.bundle/assets/federation/avatars/moon.webp b/mobile_new/html/Web.bundle/assets/federation/avatars/moon.webp new file mode 100644 index 00000000..3d7fad7b Binary files /dev/null and b/mobile_new/html/Web.bundle/assets/federation/avatars/moon.webp differ diff --git a/mobile_new/html/Web.bundle/assets/federation/avatars/mostro.small.webp b/mobile_new/html/Web.bundle/assets/federation/avatars/mostro.small.webp new file mode 100644 index 00000000..2af35322 Binary files /dev/null and b/mobile_new/html/Web.bundle/assets/federation/avatars/mostro.small.webp differ diff --git a/mobile_new/html/Web.bundle/assets/federation/avatars/mostro.webp b/mobile_new/html/Web.bundle/assets/federation/avatars/mostro.webp new file mode 100644 index 00000000..f260ca8a Binary files /dev/null and b/mobile_new/html/Web.bundle/assets/federation/avatars/mostro.webp differ diff --git a/mobile_new/html/Web.bundle/assets/federation/avatars/p2plightning.small.webp b/mobile_new/html/Web.bundle/assets/federation/avatars/p2plightning.small.webp new file mode 100644 index 00000000..20203fa0 Binary files /dev/null and b/mobile_new/html/Web.bundle/assets/federation/avatars/p2plightning.small.webp differ diff --git a/mobile_new/html/Web.bundle/assets/federation/avatars/p2plightning.webp b/mobile_new/html/Web.bundle/assets/federation/avatars/p2plightning.webp new file mode 100644 index 00000000..4b722975 Binary files /dev/null and b/mobile_new/html/Web.bundle/assets/federation/avatars/p2plightning.webp differ diff --git a/mobile_new/html/Web.bundle/assets/federation/avatars/peach.small.webp b/mobile_new/html/Web.bundle/assets/federation/avatars/peach.small.webp new file mode 100644 index 00000000..0af26243 Binary files /dev/null and b/mobile_new/html/Web.bundle/assets/federation/avatars/peach.small.webp differ diff --git a/mobile_new/html/Web.bundle/assets/federation/avatars/peach.webp b/mobile_new/html/Web.bundle/assets/federation/avatars/peach.webp new file mode 100644 index 00000000..5bf97c44 Binary files /dev/null and b/mobile_new/html/Web.bundle/assets/federation/avatars/peach.webp differ diff --git a/mobile_new/html/Web.bundle/assets/federation/avatars/satstralia.small.webp b/mobile_new/html/Web.bundle/assets/federation/avatars/satstralia.small.webp new file mode 100644 index 00000000..58192743 Binary files /dev/null and b/mobile_new/html/Web.bundle/assets/federation/avatars/satstralia.small.webp differ diff --git a/mobile_new/html/Web.bundle/assets/federation/avatars/satstralia.webp b/mobile_new/html/Web.bundle/assets/federation/avatars/satstralia.webp new file mode 100644 index 00000000..8ad3ee2f Binary files /dev/null and b/mobile_new/html/Web.bundle/assets/federation/avatars/satstralia.webp differ diff --git a/mobile_new/html/Web.bundle/assets/federation/avatars/temple.small.webp b/mobile_new/html/Web.bundle/assets/federation/avatars/temple.small.webp new file mode 100644 index 00000000..166ad7ed Binary files /dev/null and b/mobile_new/html/Web.bundle/assets/federation/avatars/temple.small.webp differ diff --git a/mobile_new/html/Web.bundle/assets/federation/avatars/temple.webp b/mobile_new/html/Web.bundle/assets/federation/avatars/temple.webp new file mode 100644 index 00000000..01172a37 Binary files /dev/null and b/mobile_new/html/Web.bundle/assets/federation/avatars/temple.webp differ diff --git a/mobile_new/html/Web.bundle/assets/federation/avatars/veneto.small.webp b/mobile_new/html/Web.bundle/assets/federation/avatars/veneto.small.webp new file mode 100644 index 00000000..daf1677c Binary files /dev/null and b/mobile_new/html/Web.bundle/assets/federation/avatars/veneto.small.webp differ diff --git a/mobile_new/html/Web.bundle/assets/federation/avatars/veneto.webp b/mobile_new/html/Web.bundle/assets/federation/avatars/veneto.webp new file mode 100644 index 00000000..b4a7a6df Binary files /dev/null and b/mobile_new/html/Web.bundle/assets/federation/avatars/veneto.webp differ diff --git a/mobile_new/html/Web.bundle/assets/federation/pgp/19D833CB2258715B4E627291942B7D51A8533742.asc b/mobile_new/html/Web.bundle/assets/federation/pgp/19D833CB2258715B4E627291942B7D51A8533742.asc new file mode 100644 index 00000000..db985782 --- /dev/null +++ b/mobile_new/html/Web.bundle/assets/federation/pgp/19D833CB2258715B4E627291942B7D51A8533742.asc @@ -0,0 +1,54 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: 19D8 33CB 2258 715B 4E62 7291 942B 7D51 A853 3742 +Comment: Over the Moon + +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 +wfIn16AyVN4= +=sjet +-----END PGP PUBLIC KEY BLOCK----- diff --git a/mobile_new/html/Web.bundle/assets/federation/pgp/25791752E9661C1DE118A8C6F78CD3D6471B6789.asc b/mobile_new/html/Web.bundle/assets/federation/pgp/25791752E9661C1DE118A8C6F78CD3D6471B6789.asc new file mode 100644 index 00000000..6beeccd7 --- /dev/null +++ b/mobile_new/html/Web.bundle/assets/federation/pgp/25791752E9661C1DE118A8C6F78CD3D6471B6789.asc @@ -0,0 +1,43 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: 2579 1752 E966 1C1D E118 A8C6 F78C D3D6 471B 6789 +Comment: cosmos178ftmm4edlahqzj376gwgqzd9re5x0x9h0tfsc@keplr.xyz + +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 +=0i79 +-----END PGP PUBLIC KEY BLOCK----- diff --git a/mobile_new/html/Web.bundle/assets/federation/pgp/48339A19645E2E53488E0E5479E1B270FACD1BD2.asc b/mobile_new/html/Web.bundle/assets/federation/pgp/48339A19645E2E53488E0E5479E1B270FACD1BD2.asc new file mode 100644 index 00000000..c4085155 --- /dev/null +++ b/mobile_new/html/Web.bundle/assets/federation/pgp/48339A19645E2E53488E0E5479E1B270FACD1BD2.asc @@ -0,0 +1,15 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: 4833 9A19 645E 2E53 488E 0E54 79E1 B270 FACD 1BD2 +Comment: hello@peachbitcoin.com + +xjMEYiOtWhYJKwYBBAHaRw8BAQdAsOKDD90QG9Fsr2TQomq1plxf0QGlQdL8OXCS +TTjE+vjNL2hlbGxvQHBlYWNoYml0Y29pbi5jb20gPGhlbGxvQHBlYWNoYml0Y29p +bi5jb20+wo8EEBYKACAFAmIjrVoGCwkHCAMCBBUICgIEFgIBAAIZAQIbAwIeAQAh +CRB54bJw+s0b0hYhBEgzmhlkXi5TSI4OVHnhsnD6zRvSDwIA/A2Z1td84Fos0L8Y +180evwOWDdbbI+8N0Y7GgkoU6iUqAQCgqMyBknoPYF9pvE2RLsYYjh52tWrV9mSI +zEMoH38JAc44BGIjrVoSCisGAQQBl1UBBQEBB0AMyWxwd2kF+8Kn5A6OuYCt8OQv +YbzwKJN3Jvnr4Z+ARgMBCAfCeAQYFggACQUCYiOtWgIbDAAhCRB54bJw+s0b0hYh +BEgzmhlkXi5TSI4OVHnhsnD6zRvSw7UBAIb3PAWG2iIXEapRxLVDkEuQ+RRVn/FU +rSwNRLsCJBsqAQDI9SNIkJuqT2RcP7qeQMj0tcZk9dBV+M48OL9XqPTEDA== +=HMiR +-----END PGP PUBLIC KEY BLOCK----- diff --git a/mobile_new/html/Web.bundle/assets/federation/pgp/498E269DDDF63969A215FED32A2C489EAF23D549.asc b/mobile_new/html/Web.bundle/assets/federation/pgp/498E269DDDF63969A215FED32A2C489EAF23D549.asc new file mode 100644 index 00000000..1607ad9c --- /dev/null +++ b/mobile_new/html/Web.bundle/assets/federation/pgp/498E269DDDF63969A215FED32A2C489EAF23D549.asc @@ -0,0 +1,10 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: 498E 269D DDF6 3969 A215 FED3 2A2C 489E AF23 D549 + +xjMEZcaRuxYJKwYBBAHaRw8BAQdAQmYhwsAC+4DcyCCUkbDK1ibB85Pa3C5mMziF +yZdKFmLOOARlxpG7EgorBgEEAZdVAQUBAQdAQSqqJzxqPXQ92M4X/T7y0L8QYvi2 +9e7S7o4LD28FzgwDAQgHwn4EGBYKACYWIQRJjiad3fY5aaIV/tMqLEieryPVSQUC +ZcaRuwIbDAUJBaRf9QAKCRAqLEieryPVSavtAP96luNWpsOevaFz+VQMjd2LcQqa +KQ5+Q3vInF1cq02YUQD8DqAf+r7qnnZIZye/9Ra/fN6qMKFCdj55iBGGZF2wYw0= +=5JHR +-----END PGP PUBLIC KEY BLOCK----- diff --git a/mobile_new/html/Web.bundle/assets/federation/pgp/9D3D3BAF4744305EE3F0B837331AB575DD78D930.asc b/mobile_new/html/Web.bundle/assets/federation/pgp/9D3D3BAF4744305EE3F0B837331AB575DD78D930.asc new file mode 100644 index 00000000..19c128b9 --- /dev/null +++ b/mobile_new/html/Web.bundle/assets/federation/pgp/9D3D3BAF4744305EE3F0B837331AB575DD78D930.asc @@ -0,0 +1,39 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: 9D3D 3BAF 4744 305E E3F0 B837 331A B575 DD78 D930 + +xsFNBGhQqZQBEADEoBrD1cOk2X4YL8a7Jt+fjUe5WP9WvtIHLzXF4CtqYLj4Pppz +vEElZATlhd9t4h/rzoSYsM4soTlkHSxWNHb3fOw+xbm+zRKhEN5El8XYSCU2ZYyB +Bo0QU5UlhUCFSwDWGYiT4JVtt5V9X8y8SzwUVavjMbwo/ynCOkabgpLJpB/5fbV+ +IcQV08JayEfG6Uzu50DQTO7Xcu5VDPwUp81sA9uqgA1BiU/VnaVzwxMENP2ZRfpI +DqQrwfrj3gskz6ygHBoj+BgcU/BRkAAONOZvleSSRrobl6i5raf8prJCUb5o+Bo7 +YBgCYmZmUdtghvfa8cluh6Ujb34uxlkVANbjFRSl6yHg3T+pRBdIK15Mxm/TYE5F +SPSDA7XmavFi9IfBuPfmyeEUk7rYQjzQSUhikf+1gGTZjFxKf+g5Y5eTUkwHhqzK +HbBC8p3BBIoTjryPFfh/C77q3CPkowVpAxlIn8bGoYOnChfUM/tLMgmy0kNwe0XB +CJuy5PwSDSwh/euB2e9gCI/8yC0xSCBuUro07kKrxtIOfb3xFOrwEJjp1YQtISp7 +mfm1fAeAZQDkH2UYR+dHZCV/olgUk6dzxm9T7a9yoNHM3lLWB/4m8jwwRG00JxhF +jW2QhRhYbByINqMER/IDgFgpA3MMgGIGPjwK17eyu9gzHaSWc/Yefk1BRwARAQAB +zsFNBGhQqZQBEADE1mdreARhnypc5TTdLBbGBa7ny3NHYv5fwokp6MrgYvkU0MwA +U/+mC/ZUwvCRpYExQaoSsbMN0rc9O97EUXaU1agjTXN6p5XslXkwNj57ZyPtSrqM +U7VH8LSp7OrlT/D1zHXtn31d6V1YWV97brw3LIZXV+35XMSmT0xz3mZMoNiyMKcj +K0809JEe4/IJQMq/KS41I5PBUsrKs4p80y9x36e17Fjl59hTuh41qzyvzWRbuEMe +W3qbmPsIXjtN2EMY9kPLIdw1UPm3/FdCMC8rPHjrLLpfVY6ix4ysNEJk3Fr8Hqxp +BNDhQg55ZE1YVvNRyZZJ23MOPsu5tnpe5B5phulK7hCY+2ZFmF9aPCCjVCQNAggd +h9suY+dHDBDLBUy/LD4rwZStG7Jby8/K+HkD9OwN448HOjLB8PyaNJzJv8aOg3LG +vn4z3PyLrVqaWIbWfECojZQI7m+7MzkG+3LqDH6asyRyII23Omq021foW4/quYPP +RDy+tj4g+oeiw5iOd6xZ21X8SVQyCg73Meu/KIND+dLPu6oT3ORcnTqAmR9Jq9EK +gH7Ht/zxluHh80vmnYqA9syGaD7r3rynWfOKVDmQuoETCy+mNTw72COJ15mQucdg +ZLee7cJAkooSra7ui5kzK9E3UFucKe5mM/OUUmy/BaBqDT6vfLN7iOBlQQARAQAB +wsF2BBgBCgAgFiEEnT07r0dEMF7j8Lg3Mxq1dd142TAFAmhQqZQCGwwACgkQMxq1 +dd142TAVKw//ZnbSBweWIC9MMnefeVsaFk1pcQ4GuihAi+zKLphArFFxm4IfDAa5 +Wd8wlJmphy7h2Z17KbYWEq/YBmsfCu6/d5C/FvF7tMT4TFNCQfyG4+0Ve3E4Azid +yCvwtSz0kyPwCK7kbEqUTWS7a41q/lfXBQI6ow42maqYUXbJ9Qka+xzAifqEgqcN +pa+EZvzoJUElOmthuN0i2jhodtnKc4bkfdNEVZ0GRv+h+N11ae5YpHAlLzGi6NRs +LQG3lCJrmowHdlpo7Cr4l/0klM8S/9xd4UEzVDmu5uYXNDmtrhGQnA4xq9Fj4rC+ +co/NCGoLgVICpdZ/Fv52lA0bGFN3eyu2KoemjqovCs0abRylkfpzGWODGMogP880 +VE4BH1fb5k82JVAD2Et7QtVudwCxDigg1cVzMF+eQKNCY3YjVFYi57NpqXsHNQqC +lxqu4/EyUF+zOjXYxUOWo2XJkF5h/rRjTFeKrWHSDOTPMAyc3/cQmDtq8YDh+vy4 +AzxcUg2fnlRAse5IGBhoXmqtjcxmZ6crdDm4etNDWA/mJQRYaDpRdPGlpzlHK0yX +uBDlfk3yxeMmEra/gkTu2Dpmp5e88NyHhhwi16ZIZU0mGJ6s3AWaEpLCfarUy5zd +lkh7RrNwSoVKPxjcRHmKV0rXjiVP8C3ex36BtjRvDiyAHlP0wdT0L3c= +=b2MY +-----END PGP PUBLIC KEY BLOCK----- diff --git a/mobile_new/html/Web.bundle/assets/federation/pgp/B4AB5F19113D4125DDF217739C4585B561315571.asc b/mobile_new/html/Web.bundle/assets/federation/pgp/B4AB5F19113D4125DDF217739C4585B561315571.asc new file mode 100644 index 00000000..5283365e --- /dev/null +++ b/mobile_new/html/Web.bundle/assets/federation/pgp/B4AB5F19113D4125DDF217739C4585B561315571.asc @@ -0,0 +1,63 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: B4AB 5F19 113D 4125 DDF2 1773 9C45 85B5 6131 5571 +Comment: Reckless_Satoshi + +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 +=WLFQ +-----END PGP PUBLIC KEY BLOCK----- diff --git a/mobile_new/html/Web.bundle/assets/federation/pgp/C6894FC235E06C4FC2FE77916FA7713E574EDEC3.asc b/mobile_new/html/Web.bundle/assets/federation/pgp/C6894FC235E06C4FC2FE77916FA7713E574EDEC3.asc new file mode 100644 index 00000000..2da9bdcb --- /dev/null +++ b/mobile_new/html/Web.bundle/assets/federation/pgp/C6894FC235E06C4FC2FE77916FA7713E574EDEC3.asc @@ -0,0 +1,82 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: C689 4FC2 35E0 6C4F C2FE 7791 6FA7 713E 574E DEC3 +Comment: jerry +Comment: jerry +Comment: librebazaar + +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 +6h09yj4O9E0f1sU= +=kiaU +-----END PGP PUBLIC KEY BLOCK----- diff --git a/mobile_new/html/Web.bundle/assets/federation/pgp/D1894C9862A9D02D47D96C84AE30B6904210DA14.asc b/mobile_new/html/Web.bundle/assets/federation/pgp/D1894C9862A9D02D47D96C84AE30B6904210DA14.asc new file mode 100644 index 00000000..0aa54b62 --- /dev/null +++ b/mobile_new/html/Web.bundle/assets/federation/pgp/D1894C9862A9D02D47D96C84AE30B6904210DA14.asc @@ -0,0 +1,43 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: D189 4C98 62A9 D02D 47D9 6C84 AE30 B690 4210 DA14 +Comment: Satstralia (http://satstralia.com) + +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 +iexNUI0gb/kJUazdBgFbIF1usQ== +=nST/ +-----END PGP PUBLIC KEY BLOCK----- diff --git a/mobile_new/settings.gradle.kts b/mobile_new/settings.gradle.kts index 0e233d5f..e9d22a78 100644 --- a/mobile_new/settings.gradle.kts +++ b/mobile_new/settings.gradle.kts @@ -3,12 +3,16 @@ pluginManagement { google () mavenCentral() gradlePluginPortal() + jcenter() + maven("https://mvnrepository.com") } } dependencyResolutionManagement { repositories { google() mavenCentral() + jcenter() + maven("https://mvnrepository.com") } }