package org.skatespots.app; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.webkit.WebView; import android.webkit.WebViewClient; import com.getcapacitor.BridgeActivity; /** * Custom MainActivity that lets the WebView hand OFF `intent:` / `nostrsigner:` * URLs to the Android OS so Amber's approval screen actually opens. * * WHY THIS EXISTS * ─────────────── * When the JS signer navigates to an `intent:...;scheme=nostrsigner;...;end` * URL, Capacitor's default WebView treats it as a page navigation and silently * DROPS it — so Amber never launches and every signing action (delete, accept * crew invite, zap, profile edit) hangs forever. Login works only because it * goes through the native plugin's startActivityForResult; JS-initiated intents * were never reaching the OS. On GrapheneOS this is enforced strictly. * * The fix: override shouldOverrideUrlLoading. Whenever the WebView tries to load * an `intent:` or `nostrsigner:` URL, we parse it and call startActivity() * ourselves — a REAL native launch that opens Amber, exactly like login. The * result comes back via the app's custom-scheme deep link * (org.skatespots.app://amber-callback...), handled by DeepLinkHandler / * resolveAmberIntent in the web layer. * * This file is injected into the generated android/ project at build time by * scripts/inject-main-activity.mjs (android/ is not committed). */ public class MainActivity extends BridgeActivity { private static final String TAG = "SkateSpotsAmber"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final WebView webView = getBridge().getWebView(); if (webView == null) { return; } final WebViewClient existing = new WebViewClient(); webView.setWebViewClient(new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { if (handleExternalScheme(url)) { return true; } return super.shouldOverrideUrlLoading(view, url); } @Override public boolean shouldOverrideUrlLoading(WebView view, android.webkit.WebResourceRequest request) { final String url = request.getUrl() != null ? request.getUrl().toString() : null; if (url != null && handleExternalScheme(url)) { return true; } return super.shouldOverrideUrlLoading(view, request); } }); } /** * If the URL is an Amber/signer intent, launch it natively and return true. * Returns false for normal in-app URLs so Capacitor handles them as usual. */ private boolean handleExternalScheme(String url) { if (url == null) { return false; } try { if (url.startsWith("intent:")) { // Parse the full Android intent: URI (carries scheme, extras, end). Intent intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); return true; } if (url.startsWith("nostrsigner:")) { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); return true; } } catch (Exception e) { Log.e(TAG, "Failed to launch external signer intent: " + e.getMessage(), e); // Fall through — let the WebView try to handle it (will no-op safely). } return false; } }