deepsite / app /api /proxy /route.ts
enzostvs's picture
enzostvs HF Staff
update login url + remove same origin
8cbbe52
raw
history blame
8.91 kB
import { NextRequest, NextResponse } from "next/server";
import { isAuthenticated } from "@/lib/auth";
export async function GET(req: NextRequest) {
const user: any = await isAuthenticated();
if (user instanceof NextResponse || !user) {
return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
}
const { searchParams } = new URL(req.url);
const spaceId = searchParams.get('spaceId');
const commitId = searchParams.get('commitId');
const path = searchParams.get('path') || '/';
if (!spaceId) {
return NextResponse.json({ error: "spaceId parameter required" }, { status: 400 });
}
try {
const spaceDomain = `${spaceId.replace("/", "-")}${commitId !== null? `--rev-${commitId.slice(0, 7)}` : ""}.static.hf.space`;
const targetUrl = `https://${spaceDomain}${path}`;
console.log("targetUrl", targetUrl);
const response = await fetch(targetUrl, {
headers: {
'User-Agent': req.headers.get('user-agent') || '',
},
});
if (!response.ok) {
console.error('Failed to fetch from HF space:', response.status, response.statusText);
return NextResponse.json({
error: "Failed to fetch content",
details: `${response.status} ${response.statusText}`,
targetUrl
}, { status: response.status });
}
let content = await response.text();
const contentType = response.headers.get('content-type') || 'text/html';
// Rewrite relative URLs to go through the proxy
if (contentType.includes('text/html')) {
const baseUrl = `https://${spaceDomain}`;
// Fix relative URLs in href attributes
content = content.replace(/href="([^"]+)"/g, (match, url) => {
if (url.startsWith('/') && !url.startsWith('//')) {
// Relative URL starting with /
return `href="${baseUrl}${url}"`;
} else if (!url.includes('://') && !url.startsWith('#') && !url.startsWith('mailto:') && !url.startsWith('tel:')) {
// Relative URL not starting with /
return `href="${baseUrl}/${url}"`;
}
return match;
});
// Fix relative URLs in src attributes
content = content.replace(/src="([^"]+)"/g, (match, url) => {
if (url.startsWith('/') && !url.startsWith('//')) {
return `src="${baseUrl}${url}"`;
} else if (!url.includes('://')) {
return `src="${baseUrl}/${url}"`;
}
return match;
});
// Add base tag to ensure relative URLs work correctly
const baseTag = `<base href="${baseUrl}/">`;
if (content.includes('<head>')) {
content = content.replace('<head>', `<head>${baseTag}`);
} else if (content.includes('<html>')) {
content = content.replace('<html>', `<html><head>${baseTag}</head>`);
} else {
content = `<head>${baseTag}</head>` + content;
}
}
const injectedScript = `
<script>
// Add event listeners and communicate with parent
document.addEventListener('DOMContentLoaded', function() {
let hoveredElement = null;
let isEditModeEnabled = false;
document.addEventListener('mouseover', function(event) {
if (event.target !== document.body && event.target !== document.documentElement) {
hoveredElement = event.target;
const rect = event.target.getBoundingClientRect();
const message = {
type: 'ELEMENT_HOVERED',
data: {
tagName: event.target.tagName,
rect: {
top: rect.top,
left: rect.left,
width: rect.width,
height: rect.height
},
element: event.target.outerHTML
}
};
parent.postMessage(message, '*');
}
});
document.addEventListener('mouseout', function(event) {
hoveredElement = null;
parent.postMessage({
type: 'ELEMENT_MOUSE_OUT'
}, '*');
});
// Handle clicks - prevent default only in edit mode
document.addEventListener('click', function(event) {
// Only prevent default if edit mode is enabled
if (isEditModeEnabled) {
event.preventDefault();
event.stopPropagation();
const rect = event.target.getBoundingClientRect();
parent.postMessage({
type: 'ELEMENT_CLICKED',
data: {
tagName: event.target.tagName,
rect: {
top: rect.top,
left: rect.left,
width: rect.width,
height: rect.height
},
element: event.target.outerHTML
}
}, '*');
} else {
// In non-edit mode, handle link clicks to maintain proxy context
const link = event.target.closest('a');
if (link && link.href) {
event.preventDefault();
const url = new URL(link.href);
// If it's an external link (different domain than the space), open in new tab
if (url.hostname !== '${spaceDomain}') {
window.open(link.href, '_blank');
} else {
// For internal links within the space, navigate through the proxy
// Extract the path and query parameters from the original link
const targetPath = url.pathname + url.search + url.hash;
// Get current proxy URL parameters
const currentUrl = new URL(window.location.href);
const spaceId = currentUrl.searchParams.get('spaceId') || '';
const commitId = currentUrl.searchParams.get('commitId') || '';
// Construct new proxy URL with the target path
const proxyUrl = '/api/proxy/?' +
'spaceId=' + encodeURIComponent(spaceId) +
(commitId ? '&commitId=' + encodeURIComponent(commitId) : '') +
'&path=' + encodeURIComponent(targetPath);
// Navigate to the new URL through the parent window
parent.postMessage({
type: 'NAVIGATE_TO_PROXY',
data: {
proxyUrl: proxyUrl,
targetPath: targetPath
}
}, '*');
}
}
}
});
// Prevent form submissions when in edit mode
document.addEventListener('submit', function(event) {
if (isEditModeEnabled) {
event.preventDefault();
event.stopPropagation();
}
});
// Prevent other navigation events when in edit mode
document.addEventListener('keydown', function(event) {
if (isEditModeEnabled && event.key === 'Enter' && (event.target.tagName === 'A' || event.target.tagName === 'BUTTON')) {
event.preventDefault();
event.stopPropagation();
}
});
// Listen for messages from parent
window.addEventListener('message', function(event) {
if (event.data.type === 'ENABLE_EDIT_MODE') {
isEditModeEnabled = true;
document.body.style.userSelect = 'none';
document.body.style.pointerEvents = 'auto';
} else if (event.data.type === 'DISABLE_EDIT_MODE') {
isEditModeEnabled = false;
document.body.style.userSelect = '';
document.body.style.pointerEvents = '';
}
});
// Notify parent that script is ready
parent.postMessage({
type: 'PROXY_SCRIPT_READY'
}, '*');
});
</script>
`;
let modifiedContent;
if (content.includes('</body>')) {
modifiedContent = content.replace(
/<\/body>/i,
`${injectedScript}</body>`
);
} else {
modifiedContent = content + injectedScript;
}
return new NextResponse(modifiedContent, {
headers: {
'Content-Type': contentType,
'Cache-Control': 'no-cache, no-store, must-revalidate',
},
});
} catch (error) {
return NextResponse.json({
error: "Proxy request failed",
details: error instanceof Error ? error.message : String(error),
spaceId
}, { status: 500 });
}
}