File size: 8,911 Bytes
c10f8f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8cbbe52
 
c10f8f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17234c8
c10f8f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17234c8
c10f8f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
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 });
  }
}