This commit is contained in:
koalasat
2025-07-16 12:36:32 +02:00
parent 05c7d02c4f
commit 431aa512d7
13 changed files with 80 additions and 50 deletions

View File

@ -8,6 +8,7 @@ declare global {
interface AndroidAppRobosats {
generateRoboname: (uuid: string, initialString: string) => void;
generateRobohash: (uuid: string, initialString: string) => void;
}
class AndroidRobosats {

View File

@ -32,15 +32,20 @@ class RoboidentitiesAndroidClient implements RoboidentitiesClient {
if (this.robohashes[key]) {
return this.robohashes[key];
} else {
const response = await window.NativeRobosats?.postMessage({
category: 'roboidentities',
type: 'robohash',
detail: key,
});
const result: string = response ? Object.values(response)[0] : '';
const image: string = `data:image/png;base64,${result}`;
this.robohashes[key] = image;
return image;
try {
const result = await new Promise<string>((resolve, reject) => {
const uuid: string = uuidv4();
window.AndroidAppRobosats?.generateRobohash(uuid, initialString);
window.AndroidRobosats?.storePromise(uuid, resolve, reject);
});
const image: string = `data:image/png;base64,${result}`;
this.robohashes[key] = image;
return image;
} catch (error) {
console.error('Error generating robohash:', error);
return '';
}
}
};
}

View File

@ -6,11 +6,11 @@ plugins {
}
android {
namespace = "com.koalasat.robosats"
namespace = "com.robosats"
compileSdk = 36
defaultConfig {
applicationId = "com.koalasat.robosats"
applicationId = "com.robosats"
minSdk = 24
targetSdk = 36
versionCode = 1

View File

@ -1,4 +1,4 @@
package com.koalasat.robosats
package com.robosats
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
@ -19,6 +19,6 @@ class ExampleInstrumentedTest {
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.koalasat.robosats", appContext.packageName)
assertEquals("com.robosats", appContext.packageName)
}
}

View File

@ -17,7 +17,7 @@
android:usesCleartextTraffic="true"
android:theme="@style/Theme.Robosats">
<activity
android:name=".MainActivity"
android:name="com.robosats.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

@ -1,15 +1,21 @@
package com.koalasat.robosats
package com.robosats
import android.app.Application
import android.content.Context
import android.graphics.Bitmap
import android.os.Bundle
import android.util.Log
import android.webkit.*
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.koalasat.robosats.WebAppInterface
import com.robosats.R
import com.robosats.tor.TorKmp
import com.robosats.tor.TorKmpManager
import java.io.ByteArrayInputStream
import java.net.HttpURLConnection
import java.net.InetSocketAddress
import java.net.Proxy
import java.net.URL
class MainActivity : AppCompatActivity() {
private lateinit var webView: WebView
@ -50,10 +56,10 @@ class MainActivity : AppCompatActivity() {
// Show a toast notification about the critical error
runOnUiThread {
android.widget.Toast.makeText(
Toast.makeText(
this,
"Critical error: Tor initialization failed. App cannot proceed securely.",
android.widget.Toast.LENGTH_LONG
Toast.LENGTH_LONG
).show()
}
}
@ -66,10 +72,10 @@ class MainActivity : AppCompatActivity() {
try {
// Display connecting message
runOnUiThread {
android.widget.Toast.makeText(
Toast.makeText(
this,
"Connecting to Tor network...",
android.widget.Toast.LENGTH_SHORT
Toast.LENGTH_SHORT
).show()
}
@ -84,10 +90,10 @@ class MainActivity : AppCompatActivity() {
// Update status on UI thread every few retries
if (retries % 3 == 0) {
runOnUiThread {
android.widget.Toast.makeText(
Toast.makeText(
this,
"Still connecting to Tor (attempt $retries/$maxRetries)...",
android.widget.Toast.LENGTH_SHORT
Toast.LENGTH_SHORT
).show()
}
}
@ -99,10 +105,10 @@ class MainActivity : AppCompatActivity() {
// Show success message
runOnUiThread {
android.widget.Toast.makeText(
Toast.makeText(
this,
"Tor connected successfully",
android.widget.Toast.LENGTH_SHORT
Toast.LENGTH_SHORT
).show()
// Now that Tor is connected, set up the WebView
@ -113,10 +119,10 @@ class MainActivity : AppCompatActivity() {
Log.e("TorInitialization", "Failed to connect to Tor after $maxRetries retries")
runOnUiThread {
android.widget.Toast.makeText(
Toast.makeText(
this,
"Failed to connect to Tor after multiple attempts. App cannot proceed securely.",
android.widget.Toast.LENGTH_LONG
Toast.LENGTH_LONG
).show()
}
}
@ -124,10 +130,10 @@ class MainActivity : AppCompatActivity() {
Log.e("TorInitialization", "Error during Tor connection: ${e.message}", e)
runOnUiThread {
android.widget.Toast.makeText(
Toast.makeText(
this,
"Error connecting to Tor: ${e.message}",
android.widget.Toast.LENGTH_LONG
Toast.LENGTH_LONG
).show()
}
}
@ -190,10 +196,10 @@ class MainActivity : AppCompatActivity() {
// Show message that we're setting up secure browsing
runOnUiThread {
android.widget.Toast.makeText(
Toast.makeText(
this,
"Setting up secure Tor browsing...",
android.widget.Toast.LENGTH_SHORT
Toast.LENGTH_SHORT
).show()
}
@ -225,10 +231,10 @@ class MainActivity : AppCompatActivity() {
// Success - now configure WebViewClient and load URL on UI thread
runOnUiThread {
android.widget.Toast.makeText(
Toast.makeText(
this,
"Secure connection established",
android.widget.Toast.LENGTH_SHORT
Toast.LENGTH_SHORT
).show()
// Create a custom WebViewClient that forces all traffic through Tor
@ -249,14 +255,14 @@ class MainActivity : AppCompatActivity() {
// For .onion domains, we must use SOCKS proxy type
val proxyType = if (isOnionDomain)
java.net.Proxy.Type.SOCKS
Proxy.Type.SOCKS
else
java.net.Proxy.Type.HTTP
Proxy.Type.HTTP
// Create a proxy instance for Tor with the appropriate type
val torProxy = java.net.Proxy(
val torProxy = Proxy(
proxyType,
java.net.InetSocketAddress(proxyHost, proxyPort)
InetSocketAddress(proxyHost, proxyPort)
)
if (isOnionDomain) {
@ -264,14 +270,14 @@ class MainActivity : AppCompatActivity() {
}
// Create connection with proxy already configured
val url = java.net.URL(urlString)
val url = 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) {
if (connection is HttpURLConnection) {
// Ensure no connection reuse to prevent proxy leaks
connection.setRequestProperty("Connection", "close")
@ -305,7 +311,7 @@ class MainActivity : AppCompatActivity() {
// Get the correct input stream based on response code
val inputStream = if (responseCode >= 400) {
connection.errorStream ?: java.io.ByteArrayInputStream(byteArrayOf())
connection.errorStream ?: ByteArrayInputStream(byteArrayOf())
} else {
connection.inputStream
}
@ -371,7 +377,7 @@ class MainActivity : AppCompatActivity() {
super.onReceivedError(view, request, error)
}
override fun onPageStarted(view: WebView, url: String, favicon: android.graphics.Bitmap?) {
override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) {
// Verify Tor is connected when page starts loading
if (!torKmp.isConnected()) {
Log.e("SecurityError", "Tor disconnected as page started loading")
@ -409,10 +415,10 @@ class MainActivity : AppCompatActivity() {
// Show error and exit - DO NOT LOAD WEBVIEW
runOnUiThread {
// Show toast with error
android.widget.Toast.makeText(
Toast.makeText(
this,
"SECURITY ERROR: Cannot set up secure browsing: ${e.message}",
android.widget.Toast.LENGTH_LONG
Toast.LENGTH_LONG
).show()
}
}
@ -478,7 +484,7 @@ class MainActivity : AppCompatActivity() {
try {
// Try to set global proxy through ConnectivityManager
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE)
val connectivityManager = context.getSystemService(CONNECTIVITY_SERVICE)
val setDefaultProxyMethod = connectivityManager.javaClass.getDeclaredMethod("setDefaultProxy", proxyClass)
setDefaultProxyMethod.isAccessible = true
setDefaultProxyMethod.invoke(connectivityManager, proxyInfo)

View File

@ -1,4 +1,4 @@
package com.koalasat.robosats
package com.robosats
import android.util.Log

View File

@ -1,4 +1,4 @@
package com.koalasat.robosats
package com.robosats
import android.content.Context
import android.util.Log
@ -26,13 +26,11 @@ class WebAppInterface(private val context: Context, private val webView: WebView
fun generateRoboname(uuid: String, message: String) {
try {
val roboname = roboIdentities.generateRoboname(message)
Log.d(TAG, "Generated roboname: $roboname for message: $message")
webView.post {
webView.evaluateJavascript("javascript:window.AndroidRobosats.onResolvePromise('${uuid}', '${roboname}')", null)
}
} catch (e: Exception) {
Log.e(TAG, "Error in generateRoboname: ${e.message}", e)
Log.e(TAG, "Error in generateRoboname", e)
// Handle error gracefully by returning a fallback value
webView.post {
@ -43,4 +41,24 @@ class WebAppInterface(private val context: Context, private val webView: WebView
}
}
}
@JavascriptInterface
fun generateRobohash(uuid: String, message: String) {
try {
val roboname = roboIdentities.generateRobohash(message)
webView.post {
webView.evaluateJavascript("javascript:window.AndroidRobosats.onResolvePromise('${uuid}', '${roboname}')", null)
}
} catch (e: Exception) {
Log.e(TAG, "Error in generateRobohash", e)
// Handle error gracefully by returning a fallback value
webView.post {
webView.evaluateJavascript(
"javascript:window.AndroidRobosats.onRejectPromise('${uuid}', 'Error generating robot hash')",
null
)
}
}
}
}

View File

@ -6,7 +6,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".MainActivity">
tools:context="com.robosats.MainActivity">
<WebView
android:id="@+id/webView"

View File

@ -1,4 +1,4 @@
package com.koalasat.robosats
package com.robosats
import org.junit.Test