#!/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 * 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 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 tag. Capacitor always // emits an element; we insert the attribute right after the tag // name so it merges cleanly regardless of the other attributes' order. const appTagRegex = / tag found — aborting.'); process.exit(1); } manifest = manifest.replace( appTagRegex, '.');