|
|
|
@@ -25,6 +25,93 @@ app.use((req, res, next) => {
|
|
|
|
|
app.use(cors());
|
|
|
|
|
app.use(express.json());
|
|
|
|
|
|
|
|
|
|
// Helper para parsear cookies
|
|
|
|
|
function parseCookies(cookieHeader) {
|
|
|
|
|
const list = {};
|
|
|
|
|
if (!cookieHeader) return list;
|
|
|
|
|
cookieHeader.split(';').forEach(cookie => {
|
|
|
|
|
let [name, ...rest] = cookie.split('=');
|
|
|
|
|
name = name?.trim();
|
|
|
|
|
if (!name) return;
|
|
|
|
|
const value = rest.join('=').trim();
|
|
|
|
|
if (!value) return;
|
|
|
|
|
list[name] = decodeURIComponent(value);
|
|
|
|
|
});
|
|
|
|
|
return list;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Rota para ativar o Virtual Root Preview (define o cookie e redireciona para a raiz)
|
|
|
|
|
app.get('/api/preview/:folder', (req, res) => {
|
|
|
|
|
const folderName = req.params.folder;
|
|
|
|
|
res.setHeader('Set-Cookie', `clone_preview_target=${encodeURIComponent(folderName)}; Path=/; HttpOnly; SameSite=Lax`);
|
|
|
|
|
res.redirect('/');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Rota para sair do Virtual Root Preview (limpa o cookie e volta ao dashboard)
|
|
|
|
|
app.get('/api/exit-preview', (req, res) => {
|
|
|
|
|
res.setHeader('Set-Cookie', 'clone_preview_target=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT');
|
|
|
|
|
res.redirect('/');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Middleware de Virtual Root Proxy (serve o site clonado diretamente na raiz / quando o cookie está ativo)
|
|
|
|
|
app.use((req, res, next) => {
|
|
|
|
|
const cookies = parseCookies(req.headers.cookie);
|
|
|
|
|
if (cookies.clone_preview_target && !req.path.startsWith('/api') && req.path !== '/old') {
|
|
|
|
|
const folderName = cookies.clone_preview_target;
|
|
|
|
|
const folderPath = path.join(__dirname, '..', 'cloned-sites', folderName);
|
|
|
|
|
|
|
|
|
|
if (fs.existsSync(folderPath)) {
|
|
|
|
|
let targetFile = req.path;
|
|
|
|
|
if (targetFile === '/') targetFile = '/index.html';
|
|
|
|
|
|
|
|
|
|
// Se o arquivo solicitado existir dentro da pasta do clone
|
|
|
|
|
const fullPath = path.join(folderPath, targetFile);
|
|
|
|
|
|
|
|
|
|
if (fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) {
|
|
|
|
|
// Se for HTML, injeta a barra de preview flutuante
|
|
|
|
|
if (targetFile.endsWith('.html')) {
|
|
|
|
|
let html = fs.readFileSync(fullPath, 'utf8');
|
|
|
|
|
const previewBar = `
|
|
|
|
|
<div id="__cloneweb_preview_bar" style="position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); background: #1f2937; color: white; padding: 12px 24px; border-radius: 50px; box-shadow: 0 10px 25px rgba(0,0,0,0.3); display: flex; align-items: center; gap: 16px; z-index: 2147483647; font-family: 'Inter', sans-serif; font-size: 14px;">
|
|
|
|
|
<span style="display: flex; align-items: center; gap: 8px;"><i class="fas fa-eye" style="color: #10b981;"></i> Live Preview: <strong style="color: #60a5fa;">${folderName.split('_')[0]}</strong></span>
|
|
|
|
|
<a href="/api/exit-preview" style="background: #ef4444; color: white; padding: 6px 16px; border-radius: 30px; text-decoration: none; font-weight: 600; transition: all 0.2s; box-shadow: 0 4px 12px rgba(239,68,68,0.3); font-size: 13px;">Sair do Preview</a>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
if (html.includes('</body>')) {
|
|
|
|
|
html = html.replace('</body>', previewBar + '</body>');
|
|
|
|
|
} else {
|
|
|
|
|
html += previewBar;
|
|
|
|
|
}
|
|
|
|
|
res.setHeader('Content-Type', 'text/html');
|
|
|
|
|
return res.send(html);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Para outros arquivos (CSS, JS, imagens), serve diretamente com o MIME type correto
|
|
|
|
|
if (targetFile.endsWith('.css')) res.setHeader('Content-Type', 'text/css');
|
|
|
|
|
if (targetFile.endsWith('.js')) res.setHeader('Content-Type', 'application/javascript');
|
|
|
|
|
return res.sendFile(fullPath);
|
|
|
|
|
} else {
|
|
|
|
|
// Fallback de SPA: se a rota virtual não existir como arquivo no disco, serve o index.html do clone!!!
|
|
|
|
|
const indexPath = path.join(folderPath, 'index.html');
|
|
|
|
|
if (fs.existsSync(indexPath)) {
|
|
|
|
|
let html = fs.readFileSync(indexPath, 'utf8');
|
|
|
|
|
const previewBar = `
|
|
|
|
|
<div id="__cloneweb_preview_bar" style="position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); background: #1f2937; color: white; padding: 12px 24px; border-radius: 50px; box-shadow: 0 10px 25px rgba(0,0,0,0.3); display: flex; align-items: center; gap: 16px; z-index: 2147483647; font-family: 'Inter', sans-serif; font-size: 14px;">
|
|
|
|
|
<span style="display: flex; align-items: center; gap: 8px;"><i class="fas fa-eye" style="color: #10b981;"></i> Live Preview: <strong style="color: #60a5fa;">${folderName.split('_')[0]}</strong></span>
|
|
|
|
|
<a href="/api/exit-preview" style="background: #ef4444; color: white; padding: 6px 16px; border-radius: 30px; text-decoration: none; font-weight: 600; transition: all 0.2s; box-shadow: 0 4px 12px rgba(239,68,68,0.3); font-size: 13px;">Sair do Preview</a>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
if (html.includes('</body>')) html = html.replace('</body>', previewBar + '</body>');
|
|
|
|
|
else html += previewBar;
|
|
|
|
|
res.setHeader('Content-Type', 'text/html');
|
|
|
|
|
return res.send(html);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
next();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Serve static files with proper MIME types
|
|
|
|
|
app.use(express.static(path.join(__dirname, 'public'), {
|
|
|
|
|
setHeaders: (res, path) => {
|
|
|
|
|