feat(waiter): Capacitor 8 Android project, native build mode, signed APK tooling

- capacitor.config.json: appId gr.bonamin.xenia, webDir dist-native,
  androidScheme http (origin http://localhost - no mixed-content block when
  calling venue servers over plain HTTP on the LAN)
- vite: `--mode native` builds to dist-native with the PWA plugin disabled
  (no service worker inside the app)
- Android: minSdk 24 / target 36; CAMERA permission for QR pairing;
  cleartext allowed via network_security_config (LAN IPs can't be listed
  per-domain); allowBackup=false so backups never carry login tokens
- Release signing reads ~/.xenia/keystore.properties (override with
  XENIA_KEYSTORE_PROPS) - the key never enters the repo; versionName 1.0.0 /
  versionCode 1 with a bump-both rule for sideloaded updates
- npm scripts build:native, apk:debug, apk:release (scripts/build-apk.mjs);
  APKs land in releases/ (gitignored); release APK is also copied to
  public/downloads/ so venue servers serve it at /downloads/xenia-waiter.apk
- waiter nginx: /downloads/ served as application/vnd.android.package-archive,
  real 404 when missing (never falls through to index.html)
- launcher icons + splash generated from the app icon (assets/ is the source)
- .gitattributes: gradlew LF, *.bat CRLF

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-28 13:31:23 +03:00
co-authored by Claude Opus 5.5
parent 5230606144
commit 0b0f5c60c3
88 changed files with 2167 additions and 46 deletions
+53 -42
View File
@@ -2,48 +2,59 @@ import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { VitePWA } from 'vite-plugin-pwa'
export default defineConfig({
server: {
host: '0.0.0.0',
port: 5173,
allowedHosts: ['all'],
proxy: {
'/api/ws': {
target: 'ws://localhost:8000',
ws: true,
changeOrigin: true,
},
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
// `vite build --mode native` builds the UI bundled inside the Capacitor app:
// no service worker (the app ships its own files) and a separate output dir so
// it never mixes with the web build served by the venue's nginx.
export default defineConfig(({ mode }) => {
const native = mode === 'native'
return {
build: {
outDir: native ? 'dist-native' : 'dist',
},
server: {
host: '0.0.0.0',
port: 5173,
allowedHosts: ['all'],
proxy: {
'/api/ws': {
target: 'ws://localhost:8000',
ws: true,
changeOrigin: true,
},
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
},
},
},
},
plugins: [
react(),
VitePWA({
registerType: 'prompt',
manifest: {
name: 'Xenia',
short_name: 'Xenia',
start_url: '/',
display: 'standalone',
background_color: '#0f172a',
theme_color: '#0f172a',
icons: [
{ src: '/icons/icon-192.png', sizes: '192x192', type: 'image/png', purpose: 'any' },
{ src: '/icons/icon-192.png', sizes: '192x192', type: 'image/png', purpose: 'maskable' },
{ src: '/icons/icon-512.png', sizes: '512x512', type: 'image/png', purpose: 'any' },
{ src: '/icons/icon-512.png', sizes: '512x512', type: 'image/png', purpose: 'maskable' },
],
},
workbox: {
globPatterns: ['**/*.{js,css,html,ico,png,svg}'],
runtimeCaching: [],
},
devOptions: {
enabled: false,
},
}),
],
plugins: [
react(),
VitePWA({
disable: native,
registerType: 'prompt',
manifest: {
name: 'Xenia',
short_name: 'Xenia',
start_url: '/',
display: 'standalone',
background_color: '#0f172a',
theme_color: '#0f172a',
icons: [
{ src: '/icons/icon-192.png', sizes: '192x192', type: 'image/png', purpose: 'any' },
{ src: '/icons/icon-192.png', sizes: '192x192', type: 'image/png', purpose: 'maskable' },
{ src: '/icons/icon-512.png', sizes: '512x512', type: 'image/png', purpose: 'any' },
{ src: '/icons/icon-512.png', sizes: '512x512', type: 'image/png', purpose: 'maskable' },
],
},
workbox: {
globPatterns: ['**/*.{js,css,html,ico,png,svg}'],
runtimeCaching: [],
},
devOptions: {
enabled: false,
},
}),
],
}
})