mirror of
https://github.com/RoboSats/robosats.git
synced 2025-08-05 20:40:05 +00:00
Merge pull request #2118 from RoboSats/use-orbot-button
Use Orbot button
This commit is contained in:
@ -23,7 +23,9 @@ import android.webkit.WebViewClient
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.app.ActivityCompat
|
||||
@ -42,6 +44,8 @@ class MainActivity : AppCompatActivity() {
|
||||
private lateinit var loadingContainer: ConstraintLayout
|
||||
private lateinit var statusTextView: TextView
|
||||
private lateinit var intentData: String
|
||||
private lateinit var useOrbotButton: Button
|
||||
var useProxy: Boolean = true
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@ -58,13 +62,16 @@ class MainActivity : AppCompatActivity() {
|
||||
webView = findViewById(R.id.webView)
|
||||
loadingContainer = findViewById(R.id.loadingContainer)
|
||||
statusTextView = findViewById(R.id.statusTextView)
|
||||
useOrbotButton = findViewById(R.id.useOrbotButton)
|
||||
|
||||
// Set click listener for action button
|
||||
useOrbotButton.setOnClickListener {
|
||||
onUseOrbotButtonClicked()
|
||||
}
|
||||
|
||||
// Set initial status message
|
||||
updateStatus("Initializing Tor connection...")
|
||||
|
||||
// Initialize Tor and setup WebView only after Tor is properly connected
|
||||
initializeTor()
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
|
||||
ContextCompat.checkSelfPermission(
|
||||
this,
|
||||
@ -85,6 +92,15 @@ class MainActivity : AppCompatActivity() {
|
||||
intentData = orderId
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize Tor and setup WebView only after Tor is properly connected
|
||||
initializeTor()
|
||||
|
||||
val settingProxy = EncryptedStorage.getEncryptedStorage("settings_use_proxy")
|
||||
if (settingProxy == "false") {
|
||||
// Setup WebView to use Orbot if the user previously clicked
|
||||
onUseOrbotButtonClicked()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
@ -97,6 +113,26 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the built-in proxy for users with Orbot configured
|
||||
* This assumes that Orbot is already running and properly configured
|
||||
* to handle .onion addresses through the system proxy settings
|
||||
*/
|
||||
private fun onUseOrbotButtonClicked() {
|
||||
Log.d("OrbotMode", "Switching to Orbot proxy mode")
|
||||
EncryptedStorage.setEncryptedStorage("settings_use_proxy", "false")
|
||||
useProxy = false
|
||||
|
||||
// Show a message to the user
|
||||
Toast.makeText(
|
||||
this,
|
||||
"Using Orbot. Make sure it's running!",
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
|
||||
setupWebView()
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize Notifications service
|
||||
*/
|
||||
@ -127,7 +163,7 @@ class MainActivity : AppCompatActivity() {
|
||||
private fun initializeTor() {
|
||||
try {
|
||||
try {
|
||||
torKmp = TorKmpManager.getTorKmpObject()
|
||||
torKmp = getTorKmpObject()
|
||||
} catch (e: UninitializedPropertyAccessException) {
|
||||
torKmp = TorKmp(application as Application)
|
||||
TorKmpManager.updateTorKmpObject(torKmp)
|
||||
@ -218,19 +254,25 @@ class MainActivity : AppCompatActivity() {
|
||||
*/
|
||||
private fun setupWebView() {
|
||||
// Double-check Tor is connected before proceeding
|
||||
if (!torKmp.isConnected()) {
|
||||
if (useProxy && !torKmp.isConnected()) {
|
||||
Log.e("SecurityError", "Attempted to set up WebView without Tor connection")
|
||||
return
|
||||
}
|
||||
|
||||
// Set a blocking WebViewClient to prevent ANY network access
|
||||
webView.webViewClient = object : WebViewClient() {
|
||||
override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? {
|
||||
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 {
|
||||
override fun shouldOverrideUrlLoading(
|
||||
view: WebView,
|
||||
request: WebResourceRequest
|
||||
): Boolean {
|
||||
// Block ALL URL loading attempts
|
||||
return true
|
||||
}
|
||||
@ -241,23 +283,17 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
// Show message that we're setting up secure browsing
|
||||
runOnUiThread {
|
||||
updateStatus("Setting up secure Tor browsing...")
|
||||
updateStatus(if (useProxy) "Setting up secure Tor browsing..." else "Setting up Orbot browsing...")
|
||||
}
|
||||
|
||||
// Configure proxy for WebView in a background thread to avoid NetworkOnMainThreadException
|
||||
Thread {
|
||||
try {
|
||||
// First verify Tor is still connected
|
||||
if (!torKmp.isConnected()) {
|
||||
if (useProxy && !torKmp.isConnected()) {
|
||||
throw SecurityException("Tor disconnected during proxy setup")
|
||||
}
|
||||
|
||||
// If we get here, proxy setup was successful
|
||||
// Perform one final Tor connection check
|
||||
if (!torKmp.isConnected()) {
|
||||
throw SecurityException("Tor disconnected after proxy setup")
|
||||
}
|
||||
|
||||
// Success - now configure WebViewClient and load URL on UI thread
|
||||
runOnUiThread {
|
||||
updateStatus("Secure connection established. Loading app...")
|
||||
|
@ -14,7 +14,6 @@ import com.robosats.tor.TorKmpManager.getTorKmpObject
|
||||
import okhttp3.Call
|
||||
import okhttp3.Callback
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.OkHttpClient.Builder
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
@ -35,7 +34,7 @@ import okhttp3.Request.Builder as RequestBuilder
|
||||
* sanitization, and proper error handling.
|
||||
*/
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
class WebAppInterface(private val context: Context, private val webView: WebView) {
|
||||
class WebAppInterface(private val context: MainActivity, private val webView: WebView) {
|
||||
private val TAG = "WebAppInterface"
|
||||
private val roboIdentities = RoboIdentities()
|
||||
private val webSockets: MutableMap<String?, WebSocket?> = HashMap<String?, WebSocket?>()
|
||||
@ -170,12 +169,16 @@ class WebAppInterface(private val context: Context, private val webView: WebView
|
||||
|
||||
try {
|
||||
Log.d(TAG, "WebSocket opening: $path")
|
||||
val client: OkHttpClient = Builder()
|
||||
// Create OkHttpClient
|
||||
var builder = Builder()
|
||||
.connectTimeout(60, TimeUnit.SECONDS) // Set connection timeout
|
||||
.readTimeout(30, TimeUnit.SECONDS) // Set read timeout
|
||||
.proxy(getTorKmpObject().proxy)
|
||||
.build()
|
||||
.readTimeout(120, TimeUnit.SECONDS) // Set read timeout
|
||||
|
||||
if (context.useProxy) {
|
||||
builder = builder.proxy(getTorKmpObject().proxy)
|
||||
}
|
||||
|
||||
val client = builder.build()
|
||||
|
||||
// Create a request for the WebSocket connection
|
||||
val request: Request = RequestBuilder()
|
||||
@ -258,12 +261,16 @@ class WebAppInterface(private val context: Context, private val webView: WebView
|
||||
}
|
||||
|
||||
try {
|
||||
// Create OkHttpClient with Tor proxy
|
||||
val client = Builder()
|
||||
// Create OkHttpClient
|
||||
var builder = Builder()
|
||||
.connectTimeout(60, TimeUnit.SECONDS) // Set connection timeout
|
||||
.readTimeout(30, TimeUnit.SECONDS) // Set read timeout
|
||||
.proxy(getTorKmpObject().proxy)
|
||||
.build()
|
||||
.readTimeout(120, TimeUnit.SECONDS) // Set read timeout
|
||||
|
||||
if (context.useProxy) {
|
||||
builder = builder.proxy(getTorKmpObject().proxy)
|
||||
}
|
||||
|
||||
val client = builder.build()
|
||||
|
||||
// Build request with URL
|
||||
val requestBuilder = RequestBuilder().url(url)
|
||||
|
@ -68,7 +68,16 @@
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/loadingProgressBar" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/useOrbotButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:text="@string/useOrbotButton"
|
||||
android:textColor="#FFFFFF"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
@ -5,4 +5,5 @@
|
||||
<string name="configuration">Configuration</string>
|
||||
<string name="robosats_is_running_in_background">Robosats is running in background fetching for notifications</string>
|
||||
<string name="notifications">Notifications</string>
|
||||
<string name="useOrbotButton">Use Orbot</string>
|
||||
</resources>
|
||||
|
@ -43,7 +43,7 @@ const MenuDrawer = ({ show, setShow }: MenuDrawerProps): React.JSX.Element => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const { garage } = useContext<UseGarageStoreType>(GarageContext);
|
||||
const { open, setOpen, client, torStatus, page, navigateToPage } =
|
||||
const { open, setOpen, client, torStatus, page, navigateToPage, settings } =
|
||||
useContext<UseAppStoreType>(AppContext);
|
||||
const { federation } = useContext<UseFederationStoreType>(FederationContext);
|
||||
const [openGarage, setOpenGarage] = useState<boolean>(false);
|
||||
@ -242,28 +242,14 @@ const MenuDrawer = ({ show, setShow }: MenuDrawerProps): React.JSX.Element => {
|
||||
<ListItemText primary={t('Settings')} />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
{client === 'mobile' && (
|
||||
{client === 'mobile' && settings.useProxy && (
|
||||
<ListItem disablePadding sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<ListItemButton selected>
|
||||
<ListItemIcon>
|
||||
{torProgress ? (
|
||||
<>
|
||||
<Box>
|
||||
<CircularProgress color={torColor} thickness={6} size={22} />
|
||||
<Box
|
||||
sx={{
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
position: 'absolute',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<TorIcon color={torColor} sx={{ width: 20, height: 20 }} />
|
||||
</Box>
|
||||
</>
|
||||
</Box>
|
||||
) : (
|
||||
<Box>
|
||||
<TorIcon color={torColor} sx={{ width: 20, height: 20 }} />
|
||||
|
@ -1,7 +1,5 @@
|
||||
import i18n from '../i18n/Web';
|
||||
import { systemClient } from '../services/System';
|
||||
import { websocketClient } from '../services/Websocket';
|
||||
import { apiClient } from '../services/api';
|
||||
import { getHost } from '../utils';
|
||||
|
||||
export type Language =
|
||||
@ -65,8 +63,6 @@ class BaseSettings {
|
||||
|
||||
systemClient.getItem('settings_use_proxy').then((result) => {
|
||||
this.useProxy = client === 'mobile' && result !== 'false';
|
||||
apiClient.useProxy = this.useProxy;
|
||||
websocketClient.useProxy = this.useProxy;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -47,13 +47,9 @@ class WebsocketConnectionAndroid implements WebsocketConnection {
|
||||
}
|
||||
|
||||
class WebsocketAndroidClient implements WebsocketClient {
|
||||
public useProxy = true;
|
||||
|
||||
private readonly webClient: WebsocketWebClient = new WebsocketWebClient();
|
||||
|
||||
public open: (path: string) => Promise<WebsocketConnection> = async (path) => {
|
||||
if (!this.useProxy) return await this.webClient.open(path);
|
||||
|
||||
return new Promise<WebsocketConnectionAndroid>((resolve, reject) => {
|
||||
const uuid: string = uuidv4();
|
||||
window.AndroidAppRobosats?.openWS(uuid, path);
|
||||
|
@ -39,8 +39,6 @@ class WebsocketConnectionWeb implements WebsocketConnection {
|
||||
}
|
||||
|
||||
class WebsocketWebClient implements WebsocketClient {
|
||||
public useProxy = false;
|
||||
|
||||
public open: (path: string) => Promise<WebsocketConnection> = async (path) => {
|
||||
return await new Promise<WebsocketConnection>((resolve, reject) => {
|
||||
try {
|
||||
|
@ -18,7 +18,6 @@ export interface WebsocketConnection {
|
||||
}
|
||||
|
||||
export interface WebsocketClient {
|
||||
useProxy: boolean;
|
||||
open: (path: string) => Promise<WebsocketConnection>;
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,7 @@
|
||||
import { type ApiClient, type Auth } from '..';
|
||||
import ApiWebClient from '../ApiWebClient';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
class ApiAndroidClient implements ApiClient {
|
||||
public useProxy = true;
|
||||
|
||||
private readonly webClient: ApiClient = new ApiWebClient();
|
||||
|
||||
private readonly getHeaders: (auth?: Auth) => HeadersInit = (auth) => {
|
||||
let headers = {
|
||||
'Content-Type': 'application/json',
|
||||
@ -47,8 +42,6 @@ class ApiAndroidClient implements ApiClient {
|
||||
|
||||
public delete: (baseUrl: string, path: string, auth?: Auth) => Promise<object | undefined> =
|
||||
async (baseUrl, path, auth) => {
|
||||
if (!this.useProxy) return await this.webClient.delete(baseUrl, path, auth);
|
||||
|
||||
const jsonHeaders = JSON.stringify(this.getHeaders(auth));
|
||||
|
||||
const result = await new Promise<string>((resolve, reject) => {
|
||||
@ -66,8 +59,6 @@ class ApiAndroidClient implements ApiClient {
|
||||
body: object,
|
||||
auth?: Auth,
|
||||
) => Promise<object | undefined> = async (baseUrl, path, body, auth) => {
|
||||
if (!this.useProxy) return await this.webClient.post(baseUrl, path, body, auth);
|
||||
|
||||
const jsonHeaders = JSON.stringify(this.getHeaders(auth));
|
||||
const jsonBody = JSON.stringify(body);
|
||||
|
||||
@ -85,8 +76,6 @@ class ApiAndroidClient implements ApiClient {
|
||||
path,
|
||||
auth,
|
||||
) => {
|
||||
if (!this.useProxy) return await this.webClient.get(baseUrl, path, auth);
|
||||
|
||||
const jsonHeaders = JSON.stringify(this.getHeaders(auth));
|
||||
|
||||
const result = await new Promise<string>((resolve, reject) => {
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { type ApiClient, type Auth } from '..';
|
||||
|
||||
class ApiWebClient implements ApiClient {
|
||||
public useProxy = false;
|
||||
|
||||
private readonly getHeaders: (auth?: Auth) => HeadersInit = (auth) => {
|
||||
let headers = {
|
||||
'Content-Type': 'application/json',
|
||||
|
@ -8,7 +8,6 @@ export interface Auth {
|
||||
}
|
||||
|
||||
export interface ApiClient {
|
||||
useProxy: boolean;
|
||||
post: (baseUrl: string, path: string, body: object, auth?: Auth) => Promise<object | undefined>;
|
||||
put: (baseUrl: string, path: string, body: object, auth?: Auth) => Promise<object | undefined>;
|
||||
get: (baseUrl: string, path: string, auth?: Auth) => Promise<object | undefined>;
|
||||
|
Reference in New Issue
Block a user