Loading App
@ -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`),
|
||||
|
@ -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;
|
||||
|
@ -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<Props> = ({
|
||||
order,
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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',
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
|
3
mobile_new/app/.gitignore
vendored
@ -1 +1,2 @@
|
||||
/build
|
||||
/build
|
||||
/src/main/assets/*
|
@ -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)
|
||||
|
@ -5,6 +5,7 @@
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:extractNativeLibs="true"
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
|
@ -1,13 +1,18 @@
|
||||
package com.koalasat.robosats
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.webkit.WebSettings
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import android.util.Log
|
||||
import android.webkit.*
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.robosats.tor.TorKmp
|
||||
import com.robosats.tor.TorKmpManager
|
||||
import java.net.InetSocketAddress
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
private lateinit var webView: WebView
|
||||
private lateinit var torKmp: TorKmp
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@ -15,15 +20,139 @@ class MainActivity : AppCompatActivity() {
|
||||
// We don't need edge-to-edge since we're using fitsSystemWindows
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
// Set up the WebView
|
||||
// Set up the WebView reference
|
||||
webView = findViewById(R.id.webView)
|
||||
setupWebView()
|
||||
|
||||
// Initialize Tor and setup WebView only after Tor is properly connected
|
||||
initializeTor()
|
||||
}
|
||||
|
||||
private fun initializeTor() {
|
||||
// Initialize TorKmp if it's not already initialized
|
||||
try {
|
||||
try {
|
||||
torKmp = TorKmpManager.getTorKmpObject()
|
||||
} catch (e: UninitializedPropertyAccessException) {
|
||||
torKmp = TorKmp(application as Application)
|
||||
TorKmpManager.updateTorKmpObject(torKmp)
|
||||
torKmp.torOperationManager.startQuietly()
|
||||
}
|
||||
|
||||
// Run Tor connection check on a background thread
|
||||
Thread {
|
||||
waitForTorConnection()
|
||||
}.start()
|
||||
|
||||
} catch (e: Exception) {
|
||||
// Log the error and show a critical error message
|
||||
Log.e("TorInitialization", "Failed to initialize Tor: ${e.message}", e)
|
||||
|
||||
// Show a toast notification about the critical error
|
||||
runOnUiThread {
|
||||
android.widget.Toast.makeText(
|
||||
this,
|
||||
"Critical error: Tor initialization failed. App cannot proceed securely.",
|
||||
android.widget.Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun waitForTorConnection() {
|
||||
var retries = 0
|
||||
val maxRetries = 15
|
||||
|
||||
try {
|
||||
// Display connecting message
|
||||
runOnUiThread {
|
||||
android.widget.Toast.makeText(
|
||||
this,
|
||||
"Connecting to Tor network...",
|
||||
android.widget.Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
// Wait for Tor to connect with retry mechanism
|
||||
while (!torKmp.isConnected() && retries < maxRetries) {
|
||||
if (!torKmp.isStarting()) {
|
||||
torKmp.torOperationManager.startQuietly()
|
||||
}
|
||||
Thread.sleep(2000)
|
||||
retries += 1
|
||||
|
||||
// Update status on UI thread every few retries
|
||||
if (retries % 3 == 0) {
|
||||
runOnUiThread {
|
||||
android.widget.Toast.makeText(
|
||||
this,
|
||||
"Still connecting to Tor (attempt $retries/$maxRetries)...",
|
||||
android.widget.Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if Tor connected successfully
|
||||
if (torKmp.isConnected()) {
|
||||
Log.d("TorInitialization", "Tor connected successfully after $retries retries")
|
||||
|
||||
// Show success message
|
||||
runOnUiThread {
|
||||
android.widget.Toast.makeText(
|
||||
this,
|
||||
"Tor connected successfully",
|
||||
android.widget.Toast.LENGTH_SHORT
|
||||
).show()
|
||||
|
||||
// Now that Tor is connected, set up the WebView
|
||||
setupWebView()
|
||||
}
|
||||
} else {
|
||||
// If we've exhausted retries and still not connected
|
||||
Log.e("TorInitialization", "Failed to connect to Tor after $maxRetries retries")
|
||||
|
||||
runOnUiThread {
|
||||
android.widget.Toast.makeText(
|
||||
this,
|
||||
"Failed to connect to Tor after multiple attempts. App cannot proceed securely.",
|
||||
android.widget.Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("TorInitialization", "Error during Tor connection: ${e.message}", e)
|
||||
|
||||
runOnUiThread {
|
||||
android.widget.Toast.makeText(
|
||||
this,
|
||||
"Error connecting to Tor: ${e.message}",
|
||||
android.widget.Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupWebView() {
|
||||
// Set WebViewClient to handle page navigation
|
||||
webView.webViewClient = WebViewClient()
|
||||
// Double-check Tor is connected before proceeding
|
||||
if (!torKmp.isConnected()) {
|
||||
Log.e("SecurityError", "Attempted to set up WebView without Tor connection")
|
||||
return
|
||||
}
|
||||
|
||||
// IMMEDIATELY set a blocking WebViewClient to prevent ANY network access
|
||||
webView.webViewClient = object : WebViewClient() {
|
||||
override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? {
|
||||
// Block ALL requests until we're sure Tor proxy is correctly set up
|
||||
return WebResourceResponse("text/plain", "UTF-8", null)
|
||||
}
|
||||
|
||||
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
|
||||
// Block ALL URL loading attempts until proxy is properly configured
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Configure WebView settings on UI thread
|
||||
val webSettings = webView.settings
|
||||
|
||||
// Enable JavaScript
|
||||
@ -32,8 +161,12 @@ class MainActivity : AppCompatActivity() {
|
||||
// Enable DOM storage for HTML5 apps
|
||||
webSettings.domStorageEnabled = true
|
||||
|
||||
// Set cache mode
|
||||
webSettings.cacheMode = WebSettings.LOAD_DEFAULT
|
||||
// Enable CORS and cross-origin requests
|
||||
webSettings.allowUniversalAccessFromFileURLs = true
|
||||
webSettings.allowFileAccessFromFileURLs = true
|
||||
|
||||
// Disable cache completely to prevent leaks
|
||||
webSettings.cacheMode = WebSettings.LOAD_NO_CACHE
|
||||
|
||||
// Enable mixed content (http in https)
|
||||
webSettings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
|
||||
@ -54,7 +187,322 @@ class MainActivity : AppCompatActivity() {
|
||||
// Improve display for better Android integration
|
||||
webSettings.textZoom = 100 // Normal text zoom
|
||||
|
||||
// Load the website
|
||||
webView.loadUrl("http://check.torproject.org")
|
||||
// Show message that we're setting up secure browsing
|
||||
runOnUiThread {
|
||||
android.widget.Toast.makeText(
|
||||
this,
|
||||
"Setting up secure Tor browsing...",
|
||||
android.widget.Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
// Configure proxy for WebView in a background thread to avoid NetworkOnMainThreadException
|
||||
Thread {
|
||||
try {
|
||||
// First verify Tor is still connected
|
||||
if (!torKmp.isConnected()) {
|
||||
throw SecurityException("Tor disconnected during proxy setup")
|
||||
}
|
||||
|
||||
// Try to set up the proxy
|
||||
setupProxyForWebView()
|
||||
|
||||
// If we get here, proxy setup was successful
|
||||
// Perform one final Tor connection check
|
||||
if (!torKmp.isConnected()) {
|
||||
throw SecurityException("Tor disconnected after proxy setup")
|
||||
}
|
||||
|
||||
// Now get the proxy information that we previously verified in setupProxyForWebView
|
||||
// Use system properties that we've already set up and verified
|
||||
val proxyHost = System.getProperty("http.proxyHost")
|
||||
?: throw SecurityException("Missing proxy host in system properties")
|
||||
val proxyPort = System.getProperty("http.proxyPort")?.toIntOrNull()
|
||||
?: throw SecurityException("Missing or invalid proxy port in system properties")
|
||||
|
||||
Log.d("TorProxy", "Using proxy settings: $proxyHost:$proxyPort")
|
||||
|
||||
// Success - now configure WebViewClient and load URL on UI thread
|
||||
runOnUiThread {
|
||||
android.widget.Toast.makeText(
|
||||
this,
|
||||
"Secure connection established",
|
||||
android.widget.Toast.LENGTH_SHORT
|
||||
).show()
|
||||
|
||||
// Create a custom WebViewClient that forces all traffic through Tor
|
||||
webView.webViewClient = object : WebViewClient() {
|
||||
override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? {
|
||||
// Verify Tor is connected before allowing any resource request
|
||||
if (!torKmp.isConnected()) {
|
||||
Log.e("SecurityError", "Tor disconnected during resource request")
|
||||
return WebResourceResponse("text/plain", "UTF-8", null)
|
||||
}
|
||||
|
||||
val urlString = request.url.toString()
|
||||
Log.d("TorProxy", "Intercepting request: $urlString")
|
||||
|
||||
try {
|
||||
// Special handling for .onion domains
|
||||
val isOnionDomain = urlString.contains(".onion")
|
||||
|
||||
// For .onion domains, we must use SOCKS proxy type
|
||||
val proxyType = if (isOnionDomain)
|
||||
java.net.Proxy.Type.SOCKS
|
||||
else
|
||||
java.net.Proxy.Type.HTTP
|
||||
|
||||
// Create a proxy instance for Tor with the appropriate type
|
||||
val torProxy = java.net.Proxy(
|
||||
proxyType,
|
||||
java.net.InetSocketAddress(proxyHost, proxyPort)
|
||||
)
|
||||
|
||||
if (isOnionDomain) {
|
||||
Log.d("TorProxy", "Handling .onion domain with SOCKS proxy: $urlString")
|
||||
}
|
||||
|
||||
// Create connection with proxy already configured
|
||||
val url = java.net.URL(urlString)
|
||||
val connection = url.openConnection(torProxy)
|
||||
|
||||
// Configure basic connection properties
|
||||
connection.connectTimeout = 60000 // Longer timeout for onion domains
|
||||
connection.readTimeout = 60000
|
||||
|
||||
if (connection is java.net.HttpURLConnection) {
|
||||
// Ensure no connection reuse to prevent proxy leaks
|
||||
connection.setRequestProperty("Connection", "close")
|
||||
|
||||
// Copy request headers
|
||||
request.requestHeaders.forEach { (key, value) ->
|
||||
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<String, String>()
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,8 @@
|
||||
package com.robosats.tor
|
||||
|
||||
enum class EnumTorState {
|
||||
STARTING,
|
||||
ON,
|
||||
STOPPING,
|
||||
OFF
|
||||
}
|
@ -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<String> get() = listener.eventLines
|
||||
|
||||
private val appScope by lazy {
|
||||
CoroutineScope(Dispatchers.Main.immediate + SupervisorJob())
|
||||
}
|
||||
|
||||
val torStateLiveData: MutableLiveData<TorState> = 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<String> = MutableLiveData("")
|
||||
val eventLines: LiveData<String> = _eventLines
|
||||
private val events: MutableList<String> = 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<String>) {
|
||||
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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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" }
|
||||
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
BIN
mobile_new/html/Web.bundle/assets/federation/avatars/bazaar.webp
Normal file
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 2.6 KiB |
BIN
mobile_new/html/Web.bundle/assets/federation/avatars/exp.webp
Normal file
After Width: | Height: | Size: 49 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 7.5 KiB |
After Width: | Height: | Size: 2.8 KiB |
BIN
mobile_new/html/Web.bundle/assets/federation/avatars/lake.webp
Normal file
After Width: | Height: | Size: 7.7 KiB |
After Width: | Height: | Size: 1.1 KiB |
BIN
mobile_new/html/Web.bundle/assets/federation/avatars/local.webp
Normal file
After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 2.0 KiB |
BIN
mobile_new/html/Web.bundle/assets/federation/avatars/moon.webp
Normal file
After Width: | Height: | Size: 5.7 KiB |
After Width: | Height: | Size: 2.8 KiB |
BIN
mobile_new/html/Web.bundle/assets/federation/avatars/mostro.webp
Normal file
After Width: | Height: | Size: 8.3 KiB |
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 8.6 KiB |
After Width: | Height: | Size: 5.6 KiB |
BIN
mobile_new/html/Web.bundle/assets/federation/avatars/peach.webp
Normal file
After Width: | Height: | Size: 7.3 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 5.7 KiB |
After Width: | Height: | Size: 3.1 KiB |
BIN
mobile_new/html/Web.bundle/assets/federation/avatars/temple.webp
Normal file
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 3.5 KiB |
BIN
mobile_new/html/Web.bundle/assets/federation/avatars/veneto.webp
Normal file
After Width: | Height: | Size: 19 KiB |
@ -0,0 +1,54 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Comment: 19D8 33CB 2258 715B 4E62 7291 942B 7D51 A853 3742
|
||||
Comment: Over the Moon <otmoon@proton.me>
|
||||
|
||||
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
|
||||
wfIn16AyVN4=
|
||||
=sjet
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
@ -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-----
|
@ -0,0 +1,15 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Comment: 4833 9A19 645E 2E53 488E 0E54 79E1 B270 FACD 1BD2
|
||||
Comment: hello@peachbitcoin.com <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-----
|
@ -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-----
|
@ -0,0 +1,39 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Comment: 9D3D 3BAF 4744 305E E3F0 B837 331A B575 DD78 D930
|
||||
|
||||
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
|
||||
=b2MY
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
@ -0,0 +1,63 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Comment: B4AB 5F19 113D 4125 DDF2 1773 9C45 85B5 6131 5571
|
||||
Comment: Reckless_Satoshi <reckless.satoshi@protonmail.com>
|
||||
|
||||
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
|
||||
=WLFQ
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
@ -0,0 +1,82 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Comment: C689 4FC2 35E0 6C4F C2FE 7791 6FA7 713E 574E DEC3
|
||||
Comment: jerry <jerryfletcher@cock.email>
|
||||
Comment: jerry <mail@jerryfletcher.org>
|
||||
Comment: librebazaar <mail@librebazaar.com>
|
||||
|
||||
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
|
||||
6h09yj4O9E0f1sU=
|
||||
=kiaU
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
@ -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) <info@satstralia.com
|
||||
|
||||
xsDNBGU7i1kBDAC80Z5ae87U2nYWN+ZQda/oKB/SrQB8v+U94kbibLjF90SWaCOI
|
||||
Ei6P+e9HEJjTbia/BXra5ixvS30hPik+wxntfbyDOqiDCC9O1yXQRk9WKKmS8wEA
|
||||
KCg8PWp0noNYPIa9SdwI/m3EAjpSB63pXNNcimRdYuN4EKTPbQ1t7DEOMDDZjCjW
|
||||
IpB7W8uO1OOaG663ruRjRLwQT6hNO61B2kSvs3G/QMfgwETSLWK0X3z02rq4w6dz
|
||||
qgDGtuVjgYOnunTizaKvhTktnGCo13ZUFsuayQY28sD+kfR+STWNiPvGfI/kqwCD
|
||||
b9cQuHAcZrQyM0vOquIppBzj1/s8BKMWSdQifbj31aNBW3L1EvUI6PQ7GepIRcLK
|
||||
VWS2LqPRUX4ndWGOyYpsIUDFa7DU5YoTDHEIH3gmrn2V2yTVbRlevyhIPNag2a4P
|
||||
Rd1ToeEb18ih9o3DSOgidUV1M8bY57cYiktO6UQ/8M1eZdK8EsjENNCWT8e+aOWA
|
||||
n6aGwne0vqjlt2cAEQEAAc04U2F0c3RyYWxpYSAoaHR0cDovL3NhdHN0cmFsaWEu
|
||||
Y29tKSA8aW5mb0BzYXRzdHJhbGlhLmNvbT7CwQ4EEwEIADgWIQTRiUyYYqnQLUfZ
|
||||
bISuMLaQQhDaFAUCZTuLWQIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRCu
|
||||
MLaQQhDaFAvvDAC6H56iKHa+44Ujccy6oqwv9VLCND1I17PM7rESOOpJwZ/vk6LD
|
||||
No1GhF/UOFhL3nmg9oCXhu/dovkS1rBQ12Ot8wITtWuus/X2tOZkWttJ5LpboXIJ
|
||||
4oJ/pUNJdlvqcIgE3zaWguMwL62FwIPMijoTYI61/+g1xokPecoQn1FU5PSpVlU5
|
||||
4GVSnJhHjzXJx0Ikpv5o6QSlcCXt1WNgUt+NxGCA3R7PMjALgbrlGni8ezLWmOKu
|
||||
Ma2xlIXGpzdyOe5/MKmvJbREQ7c7YjQx8UFA7bFizBSJLjKaf7KC8QSMnIshBxdZ
|
||||
VmF+sTeslZcbtqPhteywUzB9L8L7CBIa51ZXyf8WLYrg8PrdWBekuQqTEr73Ot5J
|
||||
twAV2IirL9dQ80b4JjbNFPD+ka7FXOnDHbROyV8X9GSkLN/p0OTVPGMwPkAk3ixb
|
||||
0YlxW5yYoyL5IsfEWcr164CxMGh5qBxe1z7+HDY9MIUg+19Ff9vLNJrnJzUYlWlV
|
||||
dHbAY3fqssqgug3OwM0EZTuLWQEMALTxO978/AIxtdjoig25ySFvwZGzs0CSoxEz
|
||||
ilUsTGEyA1yhIc9SjaTzL0Fp9+7iME+UlxtRy29/BnnB6BdB2rhu3h+OA67+MB1W
|
||||
vGuzJ69wjC0fS7xrsX5BSWbtEQxsbVMKlzIR+BPQKlzCDX3Sol8J1O0FLrBWf7qy
|
||||
mC9YUJzFPzPRbLn77l5+nOSePNVoJYEwIhyLIy/lQGunu92kcZp3hwdvkOZRc5u0
|
||||
ftIOqPRcTu51mPgiRhP0DX9pbvslNrs2aU5jEF3gVeV917a1t3DBk603kWI0zLMh
|
||||
D/PoXZKMFnZ2tHvl5toe3tzWQXSIusuynNnjbx+T/5KIUCS7woIO9sdjlx+39HZl
|
||||
kgWRAW46Fwhg9WOiem64laF1YpfNZ4cL/oHLxcQV29K89httk14fdieCnHBjj4F1
|
||||
bOfqW61JfuMnqpStmndR1FmbadWK47wfPeZ7tIuU8WU0JoNQpzvLUzmy46KSoUjZ
|
||||
oNStu27Sk7Qjl/pKJoDPRv46pVoQbQARAQABwsD2BBgBCAAgFiEE0YlMmGKp0C1H
|
||||
2WyErjC2kEIQ2hQFAmU7i1kCGwwACgkQrjC2kEIQ2hSP+wwAs/smCl5o4Ob6VtaN
|
||||
NGLjT/wkuYZlQS63Vi4kqs8Bxa2OdY+fSPPoRYgnWCCsGcPm1sfwXaLj1722r8cv
|
||||
Kj2r0O67UXR8vhnyJQLpK/2jTvZn8HeGaA4xDITsIMsuvguQQyd9/mg37ERLlQwK
|
||||
2vwGItVrIuN5sV2jSo0wa+vGlNa6mRlEoG94ESsQK0VEMbdid6fsfFGFXPsAKeV7
|
||||
geq0Q13QeHR1bYrwdxWpUJRI5SYtFGgqLp/sBi58DMCpyEyqVjPxRnA5Vx7v0CXT
|
||||
f2knb9xEeYmNGirK/fm7owch+L80AbZ1XWMJv/nNI7UKGW2puLwS71Opftw3kvL+
|
||||
uy/UloL3WCSrDDZTWhjt/Bnpqy+ozhB7f2TVJLObWevYwzAMmh5NoMQdJENKaw9s
|
||||
T+rsMQr3HEhHI5lM1H+LDziVUusCl2kL2+p4bYsEmdXx52/xDZ0PALU4+rVpqc2a
|
||||
vab7BuHbVGPBfZvCS99Fn1Lqv8lrTDDN70lYCCpmPVpy4QVB
|
||||
=scGt
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
@ -0,0 +1,43 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Comment: EC4F 94F6 29AA 2824 2B54 265F 1ABE 1CA3 582A 031A
|
||||
Comment: gabbygator184 <gabbygator184@proton.me>
|
||||
|
||||
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
|
||||
iexNUI0gb/kJUazdBgFbIF1usQ==
|
||||
=nST/
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
@ -3,12 +3,16 @@ pluginManagement {
|
||||
google ()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
jcenter()
|
||||
maven("https://mvnrepository.com")
|
||||
}
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
maven("https://mvnrepository.com")
|
||||
}
|
||||
}
|
||||
|
||||
|