import tailwindcss from "@tailwindcss/vite"; import react from "@vitejs/plugin-react-swc"; import path from "path"; import { defineConfig, Plugin } from "vite"; import { VitePWA } from "vite-plugin-pwa"; export default defineConfig(({ command }) => ({ plugins: [ react(), tailwindcss(), VitePWA({ registerType: "autoUpdate", // disable service worker - Alby Hub cannot be used offline (and also breaks oauth callback) injectRegister: false, includeAssets: [ "favicon.ico", "robots.txt", "icon-192.png", "icon-512.png", ], useCredentials: true, // because the manifest might sit behind authentication manifest: { short_name: "Alby Hub", name: "Alby Hub", icons: [ { src: "icon-192.png", sizes: "192x192", type: "image/png", }, { src: "icon-512.png", sizes: "512x512", type: "image/png", }, { src: "icon-1024.png", sizes: "1024x1024", type: "image/png", }, ], start_url: ".", display: "standalone", theme_color: "#000000", background_color: "#ffffff", }, workbox: { maximumFileSizeToCacheInBytes: 3000000, // 3MB }, }), ...(command === "serve" ? [insertDevCSPPlugin] : [insertProdCSPPlugin]), ], server: { port: process.env.VITE_PORT ? parseInt(process.env.VITE_PORT) : undefined, proxy: { "/api": { target: process.env.VITE_API_URL || "http://localhost:8080", secure: false, }, "/logout": { target: process.env.VITE_API_URL || "http://localhost:8080", secure: false, }, }, }, resolve: { tsconfigPaths: true, alias: { src: path.resolve(import.meta.dirname, "./src"), wailsjs: path.resolve(import.meta.dirname, "./wailsjs"), // used to refrence public assets when importing images or other // assets from the public folder // this is necessary to inject the base path during build public: "", // lottie-react's `browser` field points to a UMD build, which breaks // under Vite 8's Node-style CJS interop (default import resolves to the // whole exports object); force the ESM build instead "lottie-react": "lottie-react/build/index.es.js", }, }, build: { assetsInlineLimit: 0, }, html: command === "serve" ? { cspNonce: "DEVELOPMENT", } : undefined, base: process.env.BASE_PATH || "/", })); const DEVELOPMENT_NONCE = "'nonce-DEVELOPMENT'"; // when making changes here, also update the CSP header in http_service.go const buildCSP = (nonce?: string) => `default-src 'self'${nonce ? " " + nonce : ""}; img-src 'self' https://uploads.getalby-assets.com https://cdn.getalby-assets.com https://getalby.com; connect-src 'self' https://api.getalby.com https://getalby.com https://zapplanner.albylabs.com wss://relay.getalby.com wss://relay2.getalby.com; frame-src https://www.youtube-nocookie.com`; const insertCSPMetaTag = (comment: string, csp: string) => (html: string) => html.replace( "
", ` ` ); const insertDevCSPPlugin: Plugin = { name: "dev-csp", transformIndexHtml: { order: "pre", handler: insertCSPMetaTag( "DEV-ONLY CSP - when making changes here, also update the CSP header in http_service.go (without the nonce!)", buildCSP(DEVELOPMENT_NONCE) ), }, }; // the same CSP is served as a HTTP header in http mode (see http_service.go). // The meta tag ensures the policy also applies where no HTTP headers are set, // e.g. in the desktop app. const insertProdCSPPlugin: Plugin = { name: "prod-csp", transformIndexHtml: { order: "pre", handler: insertCSPMetaTag( "when making changes here, also update the CSP header in http_service.go", buildCSP() ), }, };