File size: 5,829 Bytes
7f0d098
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import JSZip from "jszip";

export interface ClonedPageAsset {
  rawUrl: string;
  localPath: string;
  type: "css" | "js" | "image";
  content?: string;
}

/**
 * Clean HTML: Strip analytics, comments, tracking pixels, ads
 */
export function cleanHtmlString(html: string): string {
  let cleaned = html;

  // 1. Remove comments
  cleaned = cleaned.replace(/<!--[\s\S]*?-->/g, "");

  // 2. Locate and remove tracking script blocks
  // e.g. Google Analytics, Facebook Pixel, doubleclick, hotjar
  cleaned = cleaned.replace(/<script[^>]*>[\s\S]*?(googletagmanager|google-analytics|connect\.facebook\.net|hotjar|analytics\.js)[\s\S]*?<\/script>/gi, "");

  // 3. Remove inline tracking pixels
  cleaned = cleaned.replace(/<img[^>]*src="[^"]*(pixel|track|doubleclick|googleads)[^"]*"[^>]*>/gi, "");

  return cleaned;
}

/**
 * Resolve relative URLs with respect to base URL
 */
export function resolveRelativeUrl(baseUrl: string, relativePath: string): string {
  try {
    const base = new URL(baseUrl.startsWith("http") ? baseUrl : `https://${baseUrl}`);
    const resolved = new URL(relativePath, base);
    return resolved.toString();
  } catch (e) {
    return relativePath;
  }
}

/**
 * Extract styles, scripts and images from clean HTML to build localized project files
 */
export async function performWebsiteClone(targetUrl: string): Promise<{
  success: boolean;
  html: string;
  css: string;
  js: string;
  size: string;
  error?: string;
}> {
  try {
    // 1. Fetch main HTML via our Express proxy
    const response = await fetch(`/api/proxy?url=${encodeURIComponent(targetUrl)}`);
    if (!response.ok) {
      throw new Error(`تعذر تحميل الموقع. رمز الخطأ: ${response.status}`);
    }

    const rawHtml = await response.text();
    const cleanedHtml = cleanHtmlString(rawHtml);

    // Parse the HTML using native DOMParser
    const parser = new DOMParser();
    const doc = parser.parseFromString(cleanedHtml, "text/html");

    // 2. Discover stylesheets & inline CSS
    const stylesheetLinks = Array.from(doc.querySelectorAll("link[rel='stylesheet']"));
    let consolidatedColorsText = "/* SiteClone Pro Bundled CSS */\n";
    
    // Attempt to pull external stylesheets (first 3 for performance)
    for (let i = 0; i < Math.min(stylesheetLinks.length, 3); i++) {
      const link = stylesheetLinks[i];
      const href = link?.getAttribute("href");
      if (href) {
        const fullCssUrl = resolveRelativeUrl(targetUrl, href);
        try {
          const cssRes = await fetch(`/api/proxy?url=${encodeURIComponent(fullCssUrl)}`);
          if (cssRes.ok) {
            const cssText = await cssRes.text();
            consolidatedColorsText += `\n/* Source: ${href} */\n${cssText}\n`;
            // Modify link to point locally
            link.setAttribute("href", "styles.css");
          }
        } catch {
          // Fallback if resource fetch fails due to any reason
        }
      }
    }

    // 3. Discover script blocks (first 3 for performance)
    const scriptTags = Array.from(doc.querySelectorAll("script[src]"));
    let consolidatedJsText = "/* SiteClone Pro Bundled JS */\nconsole.log('SiteClone Pro V20 initialized.');\n";

    for (let i = 0; i < Math.min(scriptTags.length, 3); i++) {
      const script = scriptTags[i];
      const src = script?.getAttribute("src");
      if (src && !src.includes("analytics") && !src.includes("pixel")) {
        const fullJsUrl = resolveRelativeUrl(targetUrl, src);
        try {
          const jsRes = await fetch(`/api/proxy?url=${encodeURIComponent(fullJsUrl)}`);
          if (jsRes.ok) {
            const jsText = await jsRes.text();
            consolidatedJsText += `\n/* Source: ${src} */\n${jsText}\n`;
            // Modify script tag to point locally
            script.setAttribute("src", "app.js");
          }
        } catch {
          // Fail gracefully
        }
      }
    }

    // Ensure we reference a local stylesheet/script if we harvested any
    if (stylesheetLinks.length > 0 && !doc.querySelector("link[href='styles.css']")) {
      const cssLink = doc.createElement("link");
      cssLink.setAttribute("rel", "stylesheet");
      cssLink.setAttribute("href", "styles.css");
      doc.head.appendChild(cssLink);
    }
    if (scriptTags.length > 0 && !doc.querySelector("script[src='app.js']")) {
      const jsScript = doc.createElement("script");
      jsScript.setAttribute("src", "app.js");
      doc.body.appendChild(jsScript);
    }

    const modifiedHtml = doc.documentElement.outerHTML;

    // Estimate file size
    const totalBytes = new Blob([modifiedHtml, consolidatedColorsText, consolidatedJsText]).size;
    const sizeKb = (totalBytes / 1024).toFixed(1);

    return {
      success: true,
      html: modifiedHtml,
      css: consolidatedColorsText,
      js: consolidatedJsText,
      size: `${sizeKb} KB`
    };
  } catch (err: any) {
    console.error("Cloning error:", err);
    return {
      success: false,
      html: "",
      css: "",
      js: "",
      size: "0 KB",
      error: err?.message || String(err)
    };
  }
}

/**
 * Trigger browser zip download using JSZip
 */
export async function downloadCloneAsZip(clone: { name: string; html: string; css: string; js: string }) {
  const zip = new JSZip();

  // Add files to the archive
  zip.file("index.html", clone.html);
  zip.file("styles.css", clone.css);
  zip.file("app.js", clone.js);

  // Generate ZIP file
  const content = await zip.generateAsync({ type: "blob" });
  
  // Custom anchor download trigger
  const url = URL.createObjectURL(content);
  const a = document.createElement("a");
  a.href = url;
  a.download = `${clone.name.replace(/\s+/g, "_")}_siteclone_pro.zip`;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  URL.revokeObjectURL(url);
}