SkateSpots/vite.config.ts
shakespeare.diy 63db160b80 fix(amber): Amethyst-style foreground-intent signer to end the GrapheneOS sign hang
Root cause: the stock nostr-signer-capacitor-plugin (0.0.5) tries Amber's
background ContentProvider first, which GrapheneOS blocks and never returns,
with no clean fallback — so signing hung 90s and Amber never appeared.

Fix (matches Amethyst, the reference NIP-55 client):
- New native Capacitor plugin AmberIntent (android-templates/AmberIntentPlugin.java)
  that goes STRAIGHT to a foreground nostrsigner: intent via a properly-registered
  ActivityResultLauncher, tracks each request by id, and times out gracefully.
- MainActivity now registers the launcher in onCreate (AndroidX contract) and
  hands it to the plugin, while staying a safe stock BridgeActivity (no
  WebViewClient override → no black screen).
- inject-main-activity.mjs now installs both MainActivity.java and AmberIntentPlugin.java.
- capacitorAmberSigner.ts rewired to registerPlugin('AmberIntent'); every op is a
  user-visible Amber Approve/Reject popup; rejections surface as a friendly message.
- Removed the dead npm nostr-signer-capacitor-plugin dependency (and its phantom
  coracle fork spec) plus its vite external entry — signing no longer uses it.
- Delete flow copy updated to the new foreground UX (Approve in Amber / Cancelled
  on reject) instead of the diagnostic SIGN/PUBLISH narration.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-07 12:39:00 +07:00

144 lines
5.8 KiB
TypeScript

import path from "node:path";
import react from "@vitejs/plugin-react-swc";
import { defineConfig } from "vitest/config";
// https://vitejs.dev/config/
export default defineConfig(() => ({
// In a native Capacitor WebView the bundle is served from the local
// filesystem, so assets must be referenced with RELATIVE paths ('./…').
// Absolute '/…' paths are the classic cause of a white screen on device.
// The regular web deploy keeps '/' (served from a domain root).
base: process.env.CAPACITOR_BUILD ? './' : '/',
server: {
host: "::",
port: 8080,
},
plugins: [
react(),
],
test: {
globals: true,
environment: 'jsdom',
setupFiles: './src/test/setup.ts',
// Only run the app's own tests. Skill templates under .agents/ carry their
// own *.test.tsx that assume a different project layout (e.g. they import
// @/lib/getDisplayName, which doesn't exist here) and must never run as
// part of this app's suite. Build artifacts are excluded too.
include: ['src/**/*.{test,spec}.{ts,tsx}'],
exclude: ['node_modules', 'dist', 'android', 'ios', '.agents', 'cvm-server'],
onConsoleLog(log) {
return !log.includes("React Router Future Flag Warning");
},
env: {
DEBUG_PRINT_LIMIT: '0',
},
},
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
optimizeDeps: {
exclude: ['@lightninglabs/lnc-web'],
},
build: {
// Strip console.log/warn/debug in production (keeps console.error for real errors)
minify: 'esbuild',
commonjsOptions: {
transformMixedEsModules: true,
},
rollupOptions: {
// Bundling policy — the distinction that matters is STATIC vs DYNAMIC import,
// and whether the package resolves on the build machine:
//
// BUNDLED (absent from this list):
// - @capacitor/core — imported STATICALLY (lib/platform.ts). It MUST be
// bundled: leaving it external emitted a bare `import "@capacitor/core"`
// specifier the native WebView can't resolve → white-screen boot crash.
// Capacitor guarantees core is installed, so bundling always resolves.
//
// EXTERNAL (listed below):
// - @capacitor/camera — used only via a GUARDED dynamic import() on native
// (components/CameraCapture.tsx). It may not be installed on every build
// machine, and it's optional/native-only, so it must stay external. The
// dynamic import() remains a runtime call that degrades gracefully.
// - @capacitor/app|haptics|keyboard|status-bar — NOT installed and never
// statically imported (accessed via window.Capacitor.Plugins at runtime).
//
// NOTE: Amber signing no longer uses the npm `nostr-signer-capacitor-plugin`.
// It now goes through our OWN native plugin `AmberIntent`
// (android-templates/AmberIntentPlugin.java), which implements the Amethyst
// foreground-intent NIP-55 flow and is reached via registerPlugin('AmberIntent')
// from @capacitor/core (already bundled). No bare specifier to externalize.
external: [
'@capacitor/camera',
'@capacitor/app',
'@capacitor/haptics',
'@capacitor/keyboard',
'@capacitor/status-bar',
],
output: process.env.CAPACITOR_BUILD ? {
// Single bundle for Capacitor APK — avoids 100+ chunk file requests
// that fail silently in Android WebView local file serving
manualChunks: undefined,
inlineDynamicImports: true,
} : {
// Split large vendor libraries into separate cacheable chunks.
// Users only re-download a chunk when that specific library updates.
manualChunks(id) {
// Map — Leaflet is large (~150KB), isolate it so non-map pages don't pay for it
if (id.includes('leaflet') || id.includes('ngeohash')) {
return 'vendor-map';
}
// Nostr core — changes infrequently, worth caching separately
if (id.includes('nostr-tools') || id.includes('@nostrify') || id.includes('@noble')) {
return 'vendor-nostr';
}
// Bitcoin / Lightning — large, rarely changes
if (
id.includes('@cashu') ||
id.includes('bitcoinjs-lib') ||
id.includes('@scure') ||
id.includes('@bitcoinerlab') ||
id.includes('ecpair') ||
id.includes('@getalby') ||
id.includes('npubcash')
) {
return 'vendor-bitcoin';
}
// React ecosystem — very stable, long cache life.
// IMPORTANT: `react` (core, with useLayoutEffect etc.) MUST live in
// the SAME chunk as react-dom / react-router. If react-dom is split
// into vendor-react but core `react` lands elsewhere, react-dom runs
// before React is defined → "Cannot read properties of undefined
// (reading 'useLayoutEffect')". The `/react/` and scheduler matches
// below keep the whole React runtime together and loaded first.
if (
id.includes('node_modules/react/') ||
id.includes('node_modules/react-dom/') ||
id.includes('node_modules/scheduler/') ||
id.includes('react-router') ||
id.includes('react/jsx-runtime')
) {
return 'vendor-react';
}
// Radix UI components — large but stable
if (id.includes('@radix-ui')) {
return 'vendor-radix';
}
// TanStack Query
if (id.includes('@tanstack')) {
return 'vendor-query';
}
},
},
},
},
// Strip console.log/warn/debug calls from production build
esbuild: {
drop: ['debugger'],
pure: ['console.log', 'console.warn', 'console.debug'],
},
}));