* fix(HH-540): scope iframe policy to widget page * fix(HH-540): reject non-string allowed domains --------- Co-authored-by: Rogee <rogee@ipao.vip>
219 lines
6.7 KiB
TypeScript
219 lines
6.7 KiB
TypeScript
/// <reference types="vitest" />
|
|
|
|
/**
|
|
* GoChat frontend Vite config — decoupled from Rails.
|
|
*
|
|
* Originally Chatwoot used vite-plugin-ruby which auto-discovers entrypoints
|
|
* from app/javascript/entrypoints/ via config/vite.json. We replaced it with
|
|
* explicit rollupOptions.input so the frontend builds as a standalone Vite app
|
|
* that talks to the GoChat Go backend (no Ruby/Rails required).
|
|
*
|
|
* Entry points (mirrors app/javascript/entrypoints/):
|
|
* dashboard — agent dashboard SPA (mounted at #app via /app/*)
|
|
* widget — customer-facing web widget
|
|
* portal — help-center portal
|
|
* sdk — standalone SDK (library mode, built separately)
|
|
* superadmin — super admin panel
|
|
* survey — CSAT survey form
|
|
* v3app — v3 login/signup flow
|
|
*/
|
|
import { defineConfig } from 'vite';
|
|
import path from 'path';
|
|
import vue from '@vitejs/plugin-vue';
|
|
import yaml from '@rollup/plugin-yaml';
|
|
|
|
const isLibraryMode = process.env.BUILD_MODE === 'library';
|
|
const isTestMode = process.env.TEST === 'true';
|
|
|
|
const vueOptions = {
|
|
template: {
|
|
compilerOptions: {
|
|
isCustomElement: (tag: string) => ['ninja-keys'].includes(tag),
|
|
},
|
|
},
|
|
};
|
|
|
|
const resolveAliases = {
|
|
vue: 'vue/dist/vue.esm-bundler.js',
|
|
components: path.resolve(__dirname, './app/javascript/dashboard/components'),
|
|
next: path.resolve(__dirname, './app/javascript/dashboard/components-next'),
|
|
v3: path.resolve(__dirname, './app/javascript/v3'),
|
|
dashboard: path.resolve(__dirname, './app/javascript/dashboard'),
|
|
helpers: path.resolve(__dirname, './app/javascript/shared/helpers'),
|
|
shared: path.resolve(__dirname, './app/javascript/shared'),
|
|
survey: path.resolve(__dirname, './app/javascript/survey'),
|
|
widget: path.resolve(__dirname, './app/javascript/widget'),
|
|
assets: path.resolve(__dirname, './app/javascript/dashboard/assets'),
|
|
vuedraggable: path.resolve(
|
|
__dirname,
|
|
'./node_modules/vuedraggable/src/vuedraggable.js'
|
|
),
|
|
'@material/mwc-icon': path.resolve(
|
|
__dirname,
|
|
'./app/javascript/shared/components/MaterialIconElement.js'
|
|
),
|
|
};
|
|
|
|
// Entrypoints that vite-plugin-ruby used to auto-discover
|
|
const entrypoints = {
|
|
dashboard: path.resolve(__dirname, './index.html'),
|
|
widget: path.resolve(__dirname, './widget.html'),
|
|
portal: path.resolve(__dirname, './app/javascript/entrypoints/portal.js'),
|
|
superadmin: path.resolve(
|
|
__dirname,
|
|
'./app/javascript/entrypoints/superadmin.js'
|
|
),
|
|
superadmin_pages: path.resolve(
|
|
__dirname,
|
|
'./app/javascript/entrypoints/superadmin_pages.js'
|
|
),
|
|
survey: path.resolve(__dirname, './app/javascript/entrypoints/survey.js'),
|
|
v3app: path.resolve(__dirname, './app/javascript/entrypoints/v3app.js'),
|
|
};
|
|
|
|
let plugins = [vue(vueOptions), yaml()];
|
|
|
|
if (isLibraryMode) {
|
|
plugins = [];
|
|
} else if (isTestMode) {
|
|
plugins = [vue(vueOptions), yaml()];
|
|
}
|
|
|
|
export default defineConfig({
|
|
plugins,
|
|
define: {
|
|
__INTLIFY_JIT_COMPILATION__: true,
|
|
regeneratorRuntime: 'globalThis.regeneratorRuntime',
|
|
},
|
|
css: {
|
|
preprocessorOptions: {
|
|
scss: {
|
|
api: 'modern-compiler',
|
|
quietDeps: true,
|
|
silenceDeprecations: ['import', 'global-builtin'],
|
|
},
|
|
},
|
|
},
|
|
// Dev server: SPA mode for dashboard + widget.html route
|
|
server: {
|
|
host: '0.0.0.0',
|
|
port: 5000,
|
|
strictPort: true, // port conflict → refuse to start instead of auto-incrementing
|
|
// Serve widget.html for /widget path (widget iframe), index.html for SPA routes
|
|
historyApiFallback: {
|
|
rewrites: [
|
|
{ from: /^\/widget/, to: '/widget.html' },
|
|
{ from: /^\/super_admin/, to: '/super_admin.html' },
|
|
],
|
|
},
|
|
proxy: {
|
|
'/api': {
|
|
target: process.env.VITE_API_HOST || 'http://127.0.0.1:3000',
|
|
changeOrigin: true,
|
|
},
|
|
'/auth': {
|
|
target: process.env.VITE_API_HOST || 'http://127.0.0.1:3000',
|
|
changeOrigin: true,
|
|
},
|
|
'/platform': {
|
|
target: process.env.VITE_API_HOST || 'http://127.0.0.1:3000',
|
|
changeOrigin: true,
|
|
},
|
|
'/profile': {
|
|
target: process.env.VITE_API_HOST || 'http://127.0.0.1:3000',
|
|
changeOrigin: true,
|
|
},
|
|
'/uploads': {
|
|
target: process.env.VITE_API_HOST || 'http://127.0.0.1:3000',
|
|
changeOrigin: true,
|
|
},
|
|
'/cable': {
|
|
target: process.env.VITE_API_HOST || 'http://127.0.0.1:3000',
|
|
ws: true,
|
|
changeOrigin: true,
|
|
},
|
|
'/health': {
|
|
target: process.env.VITE_API_HOST || 'http://127.0.0.1:3000',
|
|
changeOrigin: true,
|
|
},
|
|
'/runtime-config.js': {
|
|
target: process.env.VITE_API_HOST || 'http://127.0.0.1:3000',
|
|
changeOrigin: true,
|
|
},
|
|
'/widget': {
|
|
target: process.env.VITE_API_HOST || 'http://127.0.0.1:3000',
|
|
changeOrigin: true,
|
|
// Let Vite handle the /widget standalone HTML page (rewrite to /widget.html),
|
|
// proxy everything else (API calls: /widget/init, /widget/conversations, etc.)
|
|
bypass(req) {
|
|
if (req.headers.accept?.includes('text/html')) {
|
|
return '/widget.html';
|
|
}
|
|
},
|
|
},
|
|
},
|
|
},
|
|
build: {
|
|
outDir: 'dist',
|
|
// Dashboard is a 3 MB entry; warn only when it grows beyond that budget.
|
|
chunkSizeWarningLimit: 3000,
|
|
rollupOptions: {
|
|
input: isLibraryMode ? undefined : entrypoints,
|
|
output: {
|
|
// [NOTE] when not in library mode, no new keys will be added or overwritten
|
|
...(isLibraryMode
|
|
? {
|
|
dir: 'dist/sdk',
|
|
entryFileNames: (chunkInfo: { name: string }) => {
|
|
if (chunkInfo.name === 'sdk') {
|
|
return 'js/sdk.js';
|
|
}
|
|
return '[name].js';
|
|
},
|
|
}
|
|
: {}),
|
|
inlineDynamicImports: isLibraryMode, // Disable code-splitting for SDK
|
|
},
|
|
},
|
|
lib: isLibraryMode
|
|
? {
|
|
entry: path.resolve(__dirname, './app/javascript/entrypoints/sdk.js'),
|
|
formats: ['iife'] as const, // IIFE format for single file
|
|
name: 'sdk',
|
|
}
|
|
: undefined,
|
|
},
|
|
resolve: {
|
|
alias: resolveAliases,
|
|
},
|
|
test: {
|
|
environment: 'jsdom',
|
|
include: ['app/**/*.{test,spec}.?(c|m)[jt]s?(x)'],
|
|
coverage: {
|
|
reporter: ['lcov', 'text'],
|
|
include: ['app/**/*.js', 'app/**/*.vue'],
|
|
exclude: [
|
|
'app/**/*.@(spec|stories|routes).js',
|
|
'**/specs/**/*',
|
|
'**/i18n/**/*',
|
|
],
|
|
},
|
|
globals: true,
|
|
outputFile: 'coverage/sonar-report.xml',
|
|
pool: 'threads',
|
|
poolOptions: {
|
|
threads: {
|
|
singleThread: false,
|
|
},
|
|
},
|
|
server: {
|
|
deps: {
|
|
inline: ['tinykeys', '@material/mwc-icon'],
|
|
},
|
|
},
|
|
setupFiles: ['fake-indexeddb/auto', 'vitest.setup.js'],
|
|
mockReset: true,
|
|
clearMocks: true,
|
|
},
|
|
});
|