707 lines
18 KiB
JavaScript
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',
|
|
// });
|