Completes the local-relay integration so it actually works end-to-end and feels first-class. Native (Android): - android-templates/network_security_config.xml — permits cleartext for LOOPBACK ONLY (127.0.0.1 / localhost / ::1); all real traffic stays TLS. This is what lets ws://127.0.0.1 (Citrine) connect on API 28+, which otherwise silently blocks cleartext. - scripts/inject-network-security.mjs — idempotently copies the XML into res/xml/ and adds android:networkSecurityConfig to <application>, mirroring the existing inject-amber-queries pattern so it survives `cap sync`. - Wired into android:init / android:sync / android:build. Reads AND writes now reach Citrine app-wide: - NostrProvider's NPool routers (reqRouter/eventRouter) fold in the active Citrine URL via a ref, so every default-pool query hits it first (instant local reads) and every useNostrPublish write also lands on-device (backup + offline capture). Previously only explicit useSkateRelays paths benefited. - A dedicated effect refreshes ['nostr'] queries when the local relay comes/goes so reads switch over immediately. Fully no-op off-Android (url stays null). UI/UX (Relays settings CitrineCard): - Four crisp states — off / searching / live / waiting — with a breathing then glowing status pip, benefit chips (Instant reads · Works offline · On-device backup) that pop in on connect, a spinning Recheck, an actionable install CTA, and a persistent encryption-reassurance note. - Graceful first-run: shows "searching" until the first probe settles (via new useCitrine.settled), never flashing the orange "not detected" state. Tailwind: added status-breathe / status-live / pop-in keyframes+animations. Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
85 lines
3.6 KiB
JavaScript
85 lines
3.6 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Inject the network security config that allows the optional local Citrine
|
|
* relay's loopback cleartext WebSocket (ws://127.0.0.1).
|
|
*
|
|
* WHY THIS EXISTS
|
|
* ───────────────
|
|
* Android API 28+ blocks cleartext (non-TLS) traffic by default. Citrine
|
|
* (github.com/greenart7c3/Citrine) runs an on-device Nostr relay on a plaintext
|
|
* localhost WebSocket, so without a loopback-scoped cleartext allow-entry the OS
|
|
* silently refuses the connection and the in-app "Local Relay" toggle can never
|
|
* detect it.
|
|
*
|
|
* This script (idempotent, part of the android build):
|
|
* 1. Copies android-templates/network_security_config.xml into
|
|
* android/app/src/main/res/xml/.
|
|
* 2. Adds android:networkSecurityConfig="@xml/network_security_config" to the
|
|
* <application> tag in AndroidManifest.xml if not already present.
|
|
*
|
|
* The config permits cleartext for LOOPBACK ONLY — all real traffic (wss://,
|
|
* https://) stays encrypted. Exits quietly if android/ isn't generated yet.
|
|
*/
|
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, copyFileSync } from 'node:fs';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { dirname, join } from 'node:path';
|
|
|
|
const root = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
const appDir = join(root, 'android', 'app');
|
|
|
|
if (!existsSync(appDir)) {
|
|
console.log('[inject-network-security] android/ not generated yet — skipping.');
|
|
process.exit(0);
|
|
}
|
|
|
|
// ── 1. Copy the XML resource into res/xml/ ──────────────────────────────────
|
|
const template = join(root, 'android-templates', 'network_security_config.xml');
|
|
if (!existsSync(template)) {
|
|
console.error('[inject-network-security] template android-templates/network_security_config.xml missing — aborting.');
|
|
process.exit(1);
|
|
}
|
|
|
|
const xmlDir = join(appDir, 'src', 'main', 'res', 'xml');
|
|
if (!existsSync(xmlDir)) {
|
|
mkdirSync(xmlDir, { recursive: true });
|
|
}
|
|
|
|
const target = join(xmlDir, 'network_security_config.xml');
|
|
const desired = readFileSync(template, 'utf8');
|
|
const current = existsSync(target) ? readFileSync(target, 'utf8') : null;
|
|
if (current !== desired) {
|
|
copyFileSync(template, target);
|
|
console.log('[inject-network-security] ✓ Wrote res/xml/network_security_config.xml.');
|
|
} else {
|
|
console.log('[inject-network-security] res/xml/network_security_config.xml already correct.');
|
|
}
|
|
|
|
// ── 2. Reference it from the <application> tag in the manifest ──────────────
|
|
const manifestPath = join(appDir, 'src', 'main', 'AndroidManifest.xml');
|
|
if (!existsSync(manifestPath)) {
|
|
console.log('[inject-network-security] AndroidManifest.xml not found — skipping manifest wiring.');
|
|
process.exit(0);
|
|
}
|
|
|
|
let manifest = readFileSync(manifestPath, 'utf8');
|
|
|
|
if (manifest.includes('android:networkSecurityConfig')) {
|
|
console.log('[inject-network-security] manifest already references a network security config.');
|
|
process.exit(0);
|
|
}
|
|
|
|
// Add the attribute to the opening <application ...> tag. Capacitor always
|
|
// emits an <application> element; we insert the attribute right after the tag
|
|
// name so it merges cleanly regardless of the other attributes' order.
|
|
const appTagRegex = /<application\b/;
|
|
if (!appTagRegex.test(manifest)) {
|
|
console.error('[inject-network-security] No <application> tag found — aborting.');
|
|
process.exit(1);
|
|
}
|
|
|
|
manifest = manifest.replace(
|
|
appTagRegex,
|
|
'<application android:networkSecurityConfig="@xml/network_security_config"',
|
|
);
|
|
writeFileSync(manifestPath, manifest);
|
|
console.log('[inject-network-security] ✓ Added android:networkSecurityConfig to <application>.');
|