Sage100/vite.config.js
2026-01-20 11:04:04 +03:00

707 lines
18 KiB
JavaScript

import path from "node:path";
import react from "@vitejs/plugin-react";
import { createLogger, defineConfig } from "vite";
import inlineEditPlugin from "./plugins/visual-editor/vite-plugin-react-inline-editor.js";
import editModeDevPlugin from "./plugins/visual-editor/vite-plugin-edit-mode.js";
import iframeRouteRestorationPlugin from "./plugins/vite-plugin-iframe-route-restoration.js";
import selectionModePlugin from "./plugins/selection-mode/vite-plugin-selection-mode.js";
const isDev = process.env.NODE_ENV !== "production";
// Configuration des origines autorisées pour postMessage
// En production, définir via variable d'environnement
const ALLOWED_PARENT_ORIGINS = isDev
? [
"http://localhost:3000",
"http://localhost:5173",
"https://prod.dataven.fr",
]
: (
process.env.VITE_ALLOWED_PARENT_ORIGINS || "https://prod.dataven.fr"
).split(",");
// Utilitaire pour postMessage sécurisé avec handshake
const configSecurePostMessage = `
(function() {
const ALLOWED_ORIGINS = ${JSON.stringify(ALLOWED_PARENT_ORIGINS)};
let verifiedParentOrigin = null;
let isEmbedded = false;
// Détecter si on est dans une iframe
try {
isEmbedded = window.self !== window.top;
} catch (e) {
isEmbedded = true; // Erreur de cross-origin = on est dans une iframe
}
// Écouter le handshake du parent
window.addEventListener('message', (event) => {
if (!ALLOWED_ORIGINS.includes(event.origin)) {
return; // Ignorer les origines non autorisées
}
if (event.data?.type === 'HORIZONS_HANDSHAKE') {
verifiedParentOrigin = event.origin;
// Confirmer le handshake
window.parent.postMessage({ type: 'HORIZONS_HANDSHAKE_ACK' }, event.origin);
}
});
// Fonction sécurisée pour envoyer des messages au parent
window.__securePostMessage = function(payload) {
if (!isEmbedded) return;
if (!verifiedParentOrigin) {
// En attente de handshake, stocker temporairement (optionnel)
console.debug('[SecurePostMessage] No verified parent origin yet');
return;
}
// Sanitiser le payload avant envoi
const sanitizedPayload = sanitizePayload(payload);
window.parent.postMessage(sanitizedPayload, verifiedParentOrigin);
};
// Sanitisation du payload
function sanitizePayload(payload) {
const sanitized = { ...payload };
${
isDev
? `
// En DEV: conserver les détails pour le debugging
return sanitized;
`
: `
// En PROD: supprimer les infos sensibles
if (sanitized.error) {
// Remplacer la stack trace par un ID de corrélation
sanitized.errorId = generateCorrelationId();
sanitized.error = sanitized.error.split('\\n')[0]; // Garder uniquement la première ligne
delete sanitized.stack;
}
// Ne pas exposer les URLs internes ou les détails de réponse
if (sanitized.url) {
sanitized.url = '[REDACTED]';
}
return sanitized;
`
}
}
function generateCorrelationId() {
return 'err_' + Date.now().toString(36) + Math.random().toString(36).substr(2, 9);
}
// Exposer pour vérification
window.__isSecureMessagingReady = () => !!verifiedParentOrigin;
})();
`;
// Handler d'erreurs Vite sécurisé
const configHorizonsViteErrorHandler = `
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
for (const addedNode of mutation.addedNodes) {
if (
addedNode.nodeType === Node.ELEMENT_NODE &&
(addedNode.tagName?.toLowerCase() === 'vite-error-overlay' ||
addedNode.classList?.contains('backdrop'))
) {
handleViteOverlay(addedNode);
}
}
}
});
observer.observe(document.documentElement, { childList: true, subtree: true });
function handleViteOverlay(node) {
if (!node.shadowRoot) return;
const backdrop = node.shadowRoot.querySelector('.backdrop');
if (!backdrop) return;
const messageBodyElement = backdrop.querySelector('.message-body');
const messageText = messageBodyElement ? messageBodyElement.textContent.trim() : '';
// Utiliser la fonction sécurisée
window.__securePostMessage?.({
type: 'horizons-vite-error',
error: messageText,
timestamp: Date.now()
});
}
`;
// Handler d'erreurs runtime sécurisé
const configHorizonsRuntimeErrorHandler = `
window.onerror = (message, source, lineno, colno, errorObj) => {
${
isDev
? `
// DEV: Détails complets pour debugging
const errorDetails = errorObj ? {
name: errorObj.name,
message: errorObj.message,
stack: errorObj.stack,
source,
lineno,
colno,
} : null;
`
: `
// PROD: Informations minimales
const errorDetails = errorObj ? {
name: errorObj.name,
message: errorObj.message?.substring(0, 100), // Tronquer
} : null;
`
}
window.__securePostMessage?.({
type: 'horizons-runtime-error',
message: typeof message === 'string' ? message.substring(0, 200) : 'Unknown error',
error: errorDetails,
timestamp: Date.now()
});
};
`;
// Handler console.error sécurisé
const configHorizonsConsoleErrorHandler = `
const originalConsoleError = console.error;
console.error = function(...args) {
originalConsoleError.apply(console, args);
${
isDev
? `
// DEV: Logger les détails
let errorString = '';
for (const arg of args) {
if (arg instanceof Error) {
errorString = arg.stack || \`\${arg.name}: \${arg.message}\`;
break;
}
}
if (!errorString) {
errorString = args.map(arg =>
typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
).join(' ');
}
`
: `
// PROD: Message générique uniquement
let errorString = 'An error occurred';
for (const arg of args) {
if (arg instanceof Error) {
errorString = arg.name + ': ' + (arg.message || '').substring(0, 50);
break;
}
}
`
}
window.__securePostMessage?.({
type: 'horizons-console-error',
error: errorString,
timestamp: Date.now()
});
};
`;
// Monkey patch fetch sécurisé
const configWindowFetchMonkeyPatch = `
const originalFetch = window.fetch;
window.fetch = function(...args) {
const url = args[0] instanceof Request ? args[0].url : args[0];
// Skip WebSocket URLs
if (url.startsWith('ws:') || url.startsWith('wss:')) {
return originalFetch.apply(this, args);
}
return originalFetch.apply(this, args)
.then(async response => {
const contentType = response.headers.get('Content-Type') || '';
const isDocumentResponse =
contentType.includes('text/html') ||
contentType.includes('application/xhtml+xml');
if (!response.ok && !isDocumentResponse) {
${
isDev
? `
// DEV: Logger le contenu de l'erreur
const responseClone = response.clone();
const errorFromRes = await responseClone.text();
console.error(\`Fetch error from \${response.url}: \${errorFromRes}\`);
`
: `
// PROD: Ne pas exposer le contenu de la réponse
console.error(\`Fetch error: \${response.status}\`);
`
}
}
return response;
})
.catch(error => {
if (!url.match(/\\.html?$/i)) {
${
isDev
? `
console.error(error);
`
: `
// PROD: Ne pas exposer l'URL ou les détails
console.error('Network request failed');
`
}
}
throw error;
});
};
`;
// Handler de navigation sécurisé
const configNavigationHandler = `
if (window.navigation && window.self !== window.top) {
window.navigation.addEventListener('navigate', (event) => {
const url = event.destination.url;
try {
const destinationUrl = new URL(url);
const currentOrigin = window.location.origin;
if (destinationUrl.origin === currentOrigin) {
return;
}
} catch (error) {
return;
}
window.__securePostMessage?.({
type: 'horizons-navigation-error',
${isDev ? `url,` : `url: '[external]',`}
timestamp: Date.now()
});
});
}
`;
const addTransformIndexHtml = {
name: "add-transform-index-html",
transformIndexHtml(html) {
const tags = [
// Le script de postMessage sécurisé doit être chargé EN PREMIER
{
tag: "script",
attrs: { type: "module" },
children: configSecurePostMessage,
injectTo: "head-prepend", // Avant tous les autres scripts
},
{
tag: "script",
attrs: { type: "module" },
children: configHorizonsRuntimeErrorHandler,
injectTo: "head",
},
{
tag: "script",
attrs: { type: "module" },
children: configHorizonsViteErrorHandler,
injectTo: "head",
},
{
tag: "script",
attrs: { type: "module" },
children: configHorizonsConsoleErrorHandler,
injectTo: "head",
},
{
tag: "script",
attrs: { type: "module" },
children: configWindowFetchMonkeyPatch,
injectTo: "head",
},
{
tag: "script",
attrs: { type: "module" },
children: configNavigationHandler,
injectTo: "head",
},
];
if (
!isDev &&
process.env.TEMPLATE_BANNER_SCRIPT_URL &&
process.env.TEMPLATE_REDIRECT_URL
) {
tags.push({
tag: "script",
attrs: {
src: process.env.TEMPLATE_BANNER_SCRIPT_URL,
"template-redirect-url": process.env.TEMPLATE_REDIRECT_URL,
},
injectTo: "head",
});
}
return { html, tags };
},
};
console.warn = () => {};
const logger = createLogger();
const loggerError = logger.error;
logger.error = (msg, options) => {
if (options?.error?.toString().includes("CssSyntaxError: [postcss]")) {
return;
}
loggerError(msg, options);
};
export default defineConfig({
customLogger: logger,
plugins: [
...(isDev
? [
inlineEditPlugin(),
editModeDevPlugin(),
iframeRouteRestorationPlugin(),
selectionModePlugin(),
]
: []),
react(),
addTransformIndexHtml,
],
server: {
cors: true,
headers: {
"Cross-Origin-Embedder-Policy": "credentialless",
// Ajouter CSP pour frame-ancestors si l'app ne doit pas être iframée
// ou limiter aux origines autorisées
...(isDev
? {}
: {
"Content-Security-Policy": `frame-ancestors ${ALLOWED_PARENT_ORIGINS.join(
" "
)}`,
"X-Frame-Options": "SAMEORIGIN", // Fallback pour anciens navigateurs
}),
},
allowedHosts: true,
hmr: { overlay: false },
watch: { usePolling: true, ignored: ["**/.git/**"] },
},
optimizeDeps: { force: true },
// esbuild: {
// loader: 'jsx',
// include: /\.js$/,
// },
resolve: {
extensions: [".jsx", ".js", ".tsx", ".ts", ".json"],
alias: { "@": path.resolve(__dirname, "./src") },
},
build: {
rollupOptions: {
external: [
"@babel/parser",
"@babel/traverse",
"@babel/generator",
"@babel/types",
],
},
},
cacheDir: "node_modules/.vite",
});
// import path from 'node:path';
// import react from '@vitejs/plugin-react';
// import { createLogger, defineConfig } from 'vite';
// import inlineEditPlugin from './plugins/visual-editor/vite-plugin-react-inline-editor.js';
// import editModeDevPlugin from './plugins/visual-editor/vite-plugin-edit-mode.js';
// import iframeRouteRestorationPlugin from './plugins/vite-plugin-iframe-route-restoration.js';
// import selectionModePlugin from './plugins/selection-mode/vite-plugin-selection-mode.js';
// const isDev = process.env.NODE_ENV !== 'production';
// const configHorizonsViteErrorHandler = `
// const observer = new MutationObserver((mutations) => {
// for (const mutation of mutations) {
// for (const addedNode of mutation.addedNodes) {
// if (
// addedNode.nodeType === Node.ELEMENT_NODE &&
// (
// addedNode.tagName?.toLowerCase() === 'vite-error-overlay' ||
// addedNode.classList?.contains('backdrop')
// )
// ) {
// handleViteOverlay(addedNode);
// }
// }
// }
// });
// observer.observe(document.documentElement, {
// childList: true,
// subtree: true
// });
// function handleViteOverlay(node) {
// if (!node.shadowRoot) {
// return;
// }
// const backdrop = node.shadowRoot.querySelector('.backdrop');
// if (backdrop) {
// const overlayHtml = backdrop.outerHTML;
// const parser = new DOMParser();
// const doc = parser.parseFromString(overlayHtml, 'text/html');
// const messageBodyElement = doc.querySelector('.message-body');
// const fileElement = doc.querySelector('.file');
// const messageText = messageBodyElement ? messageBodyElement.textContent.trim() : '';
// const fileText = fileElement ? fileElement.textContent.trim() : '';
// const error = messageText + (fileText ? ' File:' + fileText : '');
// window.parent.postMessage({
// type: 'horizons-vite-error',
// error,
// }, '*');
// }
// }
// `;
// const configHorizonsRuntimeErrorHandler = `
// window.onerror = (message, source, lineno, colno, errorObj) => {
// const errorDetails = errorObj ? JSON.stringify({
// name: errorObj.name,
// message: errorObj.message,
// stack: errorObj.stack,
// source,
// lineno,
// colno,
// }) : null;
// window.parent.postMessage({
// type: 'horizons-runtime-error',
// message,
// error: errorDetails
// }, '*');
// };
// `;
// const configHorizonsConsoleErrroHandler = `
// const originalConsoleError = console.error;
// console.error = function(...args) {
// originalConsoleError.apply(console, args);
// let errorString = '';
// for (let i = 0; i < args.length; i++) {
// const arg = args[i];
// if (arg instanceof Error) {
// errorString = arg.stack || \`\${arg.name}: \${arg.message}\`;
// break;
// }
// }
// if (!errorString) {
// errorString = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : String(arg)).join(' ');
// }
// window.parent.postMessage({
// type: 'horizons-console-error',
// error: errorString
// }, '*');
// };
// `;
// const configWindowFetchMonkeyPatch = `
// const originalFetch = window.fetch;
// window.fetch = function(...args) {
// const url = args[0] instanceof Request ? args[0].url : args[0];
// // Skip WebSocket URLs
// if (url.startsWith('ws:') || url.startsWith('wss:')) {
// return originalFetch.apply(this, args);
// }
// return originalFetch.apply(this, args)
// .then(async response => {
// const contentType = response.headers.get('Content-Type') || '';
// // Exclude HTML document responses
// const isDocumentResponse =
// contentType.includes('text/html') ||
// contentType.includes('application/xhtml+xml');
// if (!response.ok && !isDocumentResponse) {
// const responseClone = response.clone();
// const errorFromRes = await responseClone.text();
// const requestUrl = response.url;
// console.error(\`Fetch error from \${requestUrl}: \${errorFromRes}\`);
// }
// return response;
// })
// .catch(error => {
// if (!url.match(/\.html?$/i)) {
// console.error(error);
// }
// throw error;
// });
// };
// `;
// const configNavigationHandler = `
// if (window.navigation && window.self !== window.top) {
// window.navigation.addEventListener('navigate', (event) => {
// const url = event.destination.url;
// try {
// const destinationUrl = new URL(url);
// const destinationOrigin = destinationUrl.origin;
// const currentOrigin = window.location.origin;
// if (destinationOrigin === currentOrigin) {
// return;
// }
// } catch (error) {
// return;
// }
// window.parent.postMessage({
// type: 'horizons-navigation-error',
// url,
// }, '*');
// });
// }
// `;
// const addTransformIndexHtml = {
// name: 'add-transform-index-html',
// transformIndexHtml(html) {
// const tags = [
// {
// tag: 'script',
// attrs: { type: 'module' },
// children: configHorizonsRuntimeErrorHandler,
// injectTo: 'head',
// },
// {
// tag: 'script',
// attrs: { type: 'module' },
// children: configHorizonsViteErrorHandler,
// injectTo: 'head',
// },
// {
// tag: 'script',
// attrs: {type: 'module'},
// children: configHorizonsConsoleErrroHandler,
// injectTo: 'head',
// },
// {
// tag: 'script',
// attrs: { type: 'module' },
// children: configWindowFetchMonkeyPatch,
// injectTo: 'head',
// },
// {
// tag: 'script',
// attrs: { type: 'module' },
// children: configNavigationHandler,
// injectTo: 'head',
// },
// ];
// if (!isDev && process.env.TEMPLATE_BANNER_SCRIPT_URL && process.env.TEMPLATE_REDIRECT_URL) {
// tags.push(
// {
// tag: 'script',
// attrs: {
// src: process.env.TEMPLATE_BANNER_SCRIPT_URL,
// 'template-redirect-url': process.env.TEMPLATE_REDIRECT_URL,
// },
// injectTo: 'head',
// }
// );
// }
// return {
// html,
// tags,
// };
// },
// };
// console.warn = () => {};
// const logger = createLogger()
// const loggerError = logger.error
// logger.error = (msg, options) => {
// if (options?.error?.toString().includes('CssSyntaxError: [postcss]')) {
// return;
// }
// loggerError(msg, options);
// }
// export default defineConfig({
// customLogger: logger,
// plugins: [
// ...(isDev ? [inlineEditPlugin(), editModeDevPlugin(), iframeRouteRestorationPlugin(), selectionModePlugin()] : []),
// react(),
// addTransformIndexHtml
// ],
// server: {
// cors: true,
// headers: {
// 'Cross-Origin-Embedder-Policy': 'credentialless',
// },
// allowedHosts: true,
// // watch: {
// // ignored: ['**/.git/**']
// // },
// hmr: {
// overlay: true, // Afficher les erreurs en overlay
// },
// watch: {
// usePolling: true, // Utile sur certains systèmes
// }
// },
// optimizeDeps: {
// force: true, // Force la ré-optimisation des dépendances
// },
// resolve: {
// extensions: ['.jsx', '.js', '.tsx', '.ts', '.json', ],
// alias: {
// '@': path.resolve(__dirname, './src'),
// },
// },
// build: {
// rollupOptions: {
// external: [
// '@babel/parser',
// '@babel/traverse',
// '@babel/generator',
// '@babel/types'
// ]
// }
// },
// // optimizeDeps: {
// // exclude: ['.git']
// // },
// // Vider le cache au démarrage si nécessaire
// cacheDir: 'node_modules/.vite',
// });