vite.config.ts raw
1 import tailwindcss from "@tailwindcss/vite";
2 import react from "@vitejs/plugin-react-swc";
3 import path from "path";
4 import { defineConfig, Plugin } from "vite";
5 import { VitePWA } from "vite-plugin-pwa";
6
7 export default defineConfig(({ command }) => ({
8 plugins: [
9 react(),
10 tailwindcss(),
11 VitePWA({
12 registerType: "autoUpdate",
13 // disable service worker - Alby Hub cannot be used offline (and also breaks oauth callback)
14 injectRegister: false,
15 includeAssets: [
16 "favicon.ico",
17 "robots.txt",
18 "icon-192.png",
19 "icon-512.png",
20 ],
21 useCredentials: true, // because the manifest might sit behind authentication
22 manifest: {
23 short_name: "Alby Hub",
24 name: "Alby Hub",
25 icons: [
26 {
27 src: "icon-192.png",
28 sizes: "192x192",
29 type: "image/png",
30 },
31 {
32 src: "icon-512.png",
33 sizes: "512x512",
34 type: "image/png",
35 },
36 {
37 src: "icon-1024.png",
38 sizes: "1024x1024",
39 type: "image/png",
40 },
41 ],
42 start_url: ".",
43 display: "standalone",
44 theme_color: "#000000",
45 background_color: "#ffffff",
46 },
47 workbox: {
48 maximumFileSizeToCacheInBytes: 3000000, // 3MB
49 },
50 }),
51 ...(command === "serve" ? [insertDevCSPPlugin] : [insertProdCSPPlugin]),
52 ],
53 server: {
54 port: process.env.VITE_PORT ? parseInt(process.env.VITE_PORT) : undefined,
55 proxy: {
56 "/api": {
57 target: process.env.VITE_API_URL || "http://localhost:8080",
58 secure: false,
59 },
60 "/logout": {
61 target: process.env.VITE_API_URL || "http://localhost:8080",
62 secure: false,
63 },
64 },
65 },
66 resolve: {
67 tsconfigPaths: true,
68 alias: {
69 src: path.resolve(import.meta.dirname, "./src"),
70 wailsjs: path.resolve(import.meta.dirname, "./wailsjs"),
71 // used to refrence public assets when importing images or other
72 // assets from the public folder
73 // this is necessary to inject the base path during build
74 public: "",
75 // lottie-react's `browser` field points to a UMD build, which breaks
76 // under Vite 8's Node-style CJS interop (default import resolves to the
77 // whole exports object); force the ESM build instead
78 "lottie-react": "lottie-react/build/index.es.js",
79 },
80 },
81 build: {
82 assetsInlineLimit: 0,
83 },
84 html:
85 command === "serve"
86 ? {
87 cspNonce: "DEVELOPMENT",
88 }
89 : undefined,
90 base: process.env.BASE_PATH || "/",
91 }));
92
93 const DEVELOPMENT_NONCE = "'nonce-DEVELOPMENT'";
94
95 // when making changes here, also update the CSP header in http_service.go
96 const buildCSP = (nonce?: string) =>
97 `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.mleku.dev wss://relay.getalby.com wss://relay2.getalby.com; frame-src https://www.youtube-nocookie.com`;
98
99 const insertCSPMetaTag = (comment: string, csp: string) => (html: string) =>
100 html.replace(
101 "<head>",
102 `<head>
103 <!-- ${comment} -->
104 <meta http-equiv="Content-Security-Policy" content="${csp}" />`
105 );
106
107 const insertDevCSPPlugin: Plugin = {
108 name: "dev-csp",
109 transformIndexHtml: {
110 order: "pre",
111 handler: insertCSPMetaTag(
112 "DEV-ONLY CSP - when making changes here, also update the CSP header in http_service.go (without the nonce!)",
113 buildCSP(DEVELOPMENT_NONCE)
114 ),
115 },
116 };
117
118 // the same CSP is served as a HTTP header in http mode (see http_service.go).
119 // The meta tag ensures the policy also applies where no HTTP headers are set,
120 // e.g. in the desktop app.
121 const insertProdCSPPlugin: Plugin = {
122 name: "prod-csp",
123 transformIndexHtml: {
124 order: "pre",
125 handler: insertCSPMetaTag(
126 "when making changes here, also update the CSP header in http_service.go",
127 buildCSP()
128 ),
129 },
130 };
131