SkateSpots/scripts/inject-network-security.mjs
shakespeare.diy 2e6a20dc2b Citrine: native cleartext config, default-pool write wiring, polished UI (1.3.1)
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>
2026-09-08 19:11:51 +07:00

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>.');