| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170 |
- import { defineConfig } from 'vite'
- import react from '@vitejs/plugin-react'
- import { VitePWA } from 'vite-plugin-pwa'
- import path from 'path'
- // https://vite.dev/config/
- export default defineConfig({
- plugins: [
- react(),
- VitePWA({
- registerType: 'autoUpdate',
- includeAssets: ['favicon.ico', 'apple-touch-icon.png', 'android-chrome-192x192.png', 'android-chrome-512x512.png'],
- manifest: false, // We use our own manifest at /static/site.webmanifest
- workbox: {
- // Cache static assets
- globPatterns: ['**/*.{js,css,html,ico,png,svg,woff,woff2}'],
- // Runtime caching rules
- runtimeCaching: [
- {
- // Cache pattern preview images
- urlPattern: /\/static\/.*\.webp$/,
- handler: 'CacheFirst',
- options: {
- cacheName: 'pattern-previews',
- expiration: {
- maxEntries: 500,
- maxAgeSeconds: 60 * 60 * 24 * 30, // 30 days
- },
- },
- },
- {
- // Cache static assets from backend
- urlPattern: /\/static\/.*\.(png|jpg|ico|svg)$/,
- handler: 'CacheFirst',
- options: {
- cacheName: 'static-assets',
- expiration: {
- maxEntries: 100,
- maxAgeSeconds: 60 * 60 * 24 * 7, // 7 days
- },
- },
- },
- {
- // Network-first for API calls (always want fresh data, but cache as fallback)
- urlPattern: /\/api\//,
- handler: 'NetworkFirst',
- options: {
- cacheName: 'api-cache',
- expiration: {
- maxEntries: 50,
- maxAgeSeconds: 60 * 5, // 5 minutes
- },
- networkTimeoutSeconds: 10,
- },
- },
- ],
- },
- devOptions: {
- enabled: false, // Disable in dev mode to avoid caching issues
- },
- }),
- ],
- resolve: {
- alias: {
- '@': path.resolve(__dirname, './src'),
- },
- },
- server: {
- port: parseInt(process.env.PORT || '5173'),
- host: true, // Listen on all interfaces (0.0.0.0) for mobile/network access
- allowedHosts: true, // Allow all hosts for local network development
- proxy: {
- // WebSocket endpoints
- '/ws': {
- target: 'ws://localhost:8080',
- ws: true,
- // Suppress connection errors (common during backend restarts)
- configure: (proxy, _options) => {
- // Handle proxy errors silently for expected connection issues
- const isConnectionError = (err: Error & { code?: string }) => {
- const msg = err.message || ''
- const code = err.code || ''
- // Check error code (most reliable for AggregateError)
- if (['ECONNRESET', 'ECONNREFUSED', 'EPIPE', 'ETIMEDOUT'].includes(code)) {
- return true
- }
- // Check message as fallback
- if (msg.includes('ECONNRESET') || msg.includes('ECONNREFUSED') ||
- msg.includes('EPIPE') || msg.includes('ETIMEDOUT') ||
- msg.includes('AggregateError')) {
- return true
- }
- return false
- }
- const handleError = (err: Error) => {
- if (isConnectionError(err)) {
- return // Silently ignore connection errors
- }
- // Only log unexpected errors
- console.error('WebSocket proxy error:', err.message)
- }
- proxy.on('error', handleError)
- proxy.on('proxyReqWs', (_proxyReq, _req, socket) => {
- socket.on('error', (err) => {
- if (!isConnectionError(err)) {
- console.error('WebSocket socket error:', err.message)
- }
- })
- })
- },
- },
- // All /api endpoints
- '/api': 'http://localhost:8080',
- // Static assets
- '/static': 'http://localhost:8080',
- // Preview images
- '/preview': 'http://localhost:8080',
- // Legacy root-level API endpoints (for backwards compatibility)
- // Pattern execution
- '/send_home': 'http://localhost:8080',
- '/send_coordinate': 'http://localhost:8080',
- '/stop_execution': 'http://localhost:8080',
- '/force_stop': 'http://localhost:8080',
- '/soft_reset': 'http://localhost:8080',
- '/controller_restart': 'http://localhost:8080',
- '/pause_execution': 'http://localhost:8080',
- '/resume_execution': 'http://localhost:8080',
- '/skip_pattern': 'http://localhost:8080',
- '/reorder_playlist': 'http://localhost:8080',
- '/add_to_queue': 'http://localhost:8080',
- '/run_theta_rho': 'http://localhost:8080',
- '/run_playlist': 'http://localhost:8080',
- // Movement
- '/move_to_center': 'http://localhost:8080',
- '/move_to_perimeter': 'http://localhost:8080',
- // Speed
- '/set_speed': 'http://localhost:8080',
- '/get_speed': 'http://localhost:8080',
- // Connection
- '/serial_status': 'http://localhost:8080',
- '/list_serial_ports': 'http://localhost:8080',
- '/connect': 'http://localhost:8080',
- '/disconnect': 'http://localhost:8080',
- // Patterns
- '/list_theta_rho_files': 'http://localhost:8080',
- '/list_theta_rho_files_with_metadata': 'http://localhost:8080',
- '/preview_thr': 'http://localhost:8080',
- '/preview_thr_batch': 'http://localhost:8080',
- '/get_theta_rho_coordinates': 'http://localhost:8080',
- '/delete_theta_rho_file': 'http://localhost:8080',
- // Playlists
- '/list_all_playlists': 'http://localhost:8080',
- '/get_playlist': 'http://localhost:8080',
- '/create_playlist': 'http://localhost:8080',
- '/modify_playlist': 'http://localhost:8080',
- '/delete_playlist': 'http://localhost:8080',
- '/rename_playlist': 'http://localhost:8080',
- '/add_to_playlist': 'http://localhost:8080',
- // LED
- '/get_led_config': 'http://localhost:8080',
- '/set_led_config': 'http://localhost:8080',
- },
- },
- build: {
- outDir: '../static/dist',
- emptyOutDir: true,
- },
- })
|