Citrine: allow loopback ws:// in WebView so the local relay connects (1.3.5)

The real fix for "Citrine running but 0 connections": the app is served over
https in the WebView and Citrine listens on plaintext ws://127.0.0.1, which
modern Android WebViews block as mixed content BEFORE it reaches the network
layer — allowMixedContent + the loopback network-security-config don't fully
cover WebSocket mixed content.

MainActivity now sets the bridge WebView's mixed-content mode to
MIXED_CONTENT_ALWAYS_ALLOW after super.onCreate(), which lets the localhost
ws:// connection through for every user. All real traffic stays wss:///https://;
this only unblocks the loopback relay. The WebViewClient is left untouched
(overriding it previously caused a black screen). Wrapped in try/catch so it can
never crash the launcher.

Also removed the temporary on-screen probe debug readout (kept the diagnostic
fields in useCitrine, unused by UI, for future use).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
shakespeare.diy 2026-09-09 10:47:39 +07:00
parent 97adfba7e0
commit c7e890e65c
3 changed files with 26 additions and 12 deletions

View file

@ -2,6 +2,8 @@ package org.skatespots.app;
import android.content.Intent;
import android.os.Bundle;
import android.webkit.WebSettings;
import android.webkit.WebView;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
@ -49,5 +51,28 @@ public class MainActivity extends BridgeActivity {
registerPlugin(AmberIntentPlugin.class);
super.onCreate(savedInstanceState);
// ── Allow the on-device Citrine relay (ws://127.0.0.1) ──────────────
// The app is served to the WebView over the https scheme, and Citrine
// listens on a PLAINTEXT WebSocket (ws://, not wss://). Modern Android
// WebViews treat "secure page → insecure ws" as mixed content and block
// it BEFORE it ever reaches the network layer — which is why the local
// relay showed 0 connections despite allowMixedContent + the loopback
// network-security-config (neither fully covers WebSocket mixed content).
//
// MIXED_CONTENT_ALWAYS_ALLOW on the bridge WebView lets the localhost
// ws:// connection through. All real traffic remains wss:// / https://;
// this only unblocks the loopback relay. We do NOT touch the
// WebViewClient (overriding it previously caused a black-screen crash).
try {
WebView webView = getBridge().getWebView();
if (webView != null) {
WebSettings settings = webView.getSettings();
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
} catch (Exception e) {
// Non-fatal: if this fails the app still runs, the local relay just
// won't connect. Never crash the launcher over an optional feature.
}
}
}

View file

@ -1,7 +1,7 @@
{
"name": "skate-spots",
"private": true,
"version": "1.3.4",
"version": "1.3.5",
"type": "module",
"homepage": "https://skate-spots.com",
"scripts": {

View file

@ -490,17 +490,6 @@ function CitrineCard() {
</div>
)}
{/* Debug readout — shows the exact probe URL + failure reason on the
device so connectivity can be diagnosed without USB/adb. Only shown
while not connected. */}
{state !== 'live' && citrine.reason && citrine.reason !== 'not-android' && (
<div className="rounded-lg bg-black/40 border border-white/10 p-2.5 font-mono text-[10px] leading-relaxed text-gray-400">
<div className="text-gray-500">debug</div>
<div>url: <span className="text-gray-300">{citrine.probeUrl}</span></div>
<div>result: <span className="text-orange-300">{citrine.reason}</span></div>
</div>
)}
{/* Privacy reassurance — always present when enabled */}
<div className="flex items-start gap-2 rounded-xl bg-black/20 border border-white/5 p-2.5">
<Shield className="w-3.5 h-3.5 text-white/40 flex-shrink-0 mt-0.5" />