@accelerated-agency/visual-editor 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +8 -6
- package/dist/vite.js +244 -226
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1867,7 +1867,7 @@ import { useState as useState6, useCallback as useCallback6, useEffect as useEff
|
|
|
1867
1867
|
import { useCallback as useCallback4, useEffect as useEffect4, useRef as useRef4, useState as useState4 } from "react";
|
|
1868
1868
|
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1869
1869
|
var CHANNEL = "conversion-editor";
|
|
1870
|
-
function IframeCanvas({ url, password, onBridgeReady, onPong }) {
|
|
1870
|
+
function IframeCanvas({ url, password, proxyBaseUrl = "", onBridgeReady, onPong }) {
|
|
1871
1871
|
const iframeElRef = useRef4(null);
|
|
1872
1872
|
const setSelectedElement = useMutationsStore((s) => s.setSelectedElement);
|
|
1873
1873
|
const addMutationToActive = useVariationsStore((s) => s.addMutationToActive);
|
|
@@ -1917,7 +1917,7 @@ function IframeCanvas({ url, password, onBridgeReady, onPong }) {
|
|
|
1917
1917
|
if (url.toLowerCase() === "test") {
|
|
1918
1918
|
resolvedUrl = "/test";
|
|
1919
1919
|
} else if (url.startsWith("http")) {
|
|
1920
|
-
resolvedUrl =
|
|
1920
|
+
resolvedUrl = `${proxyBaseUrl}/api/proxy?password=${encodeURIComponent(password || "")}&url=${encodeURIComponent(url)}`;
|
|
1921
1921
|
} else {
|
|
1922
1922
|
resolvedUrl = url;
|
|
1923
1923
|
}
|
|
@@ -2185,7 +2185,7 @@ var VIEWPORT_LABELS = {
|
|
|
2185
2185
|
tablet: "768 \xD7 1024",
|
|
2186
2186
|
mobile: "375 \xD7 812"
|
|
2187
2187
|
};
|
|
2188
|
-
function CanvasArea({ url, password, viewport, onBridgeReady, onPong }) {
|
|
2188
|
+
function CanvasArea({ url, password, viewport, proxyBaseUrl, onBridgeReady, onPong }) {
|
|
2189
2189
|
const iframeWidth = VIEWPORT_WIDTHS[viewport];
|
|
2190
2190
|
const isConstrained = viewport !== "desktop";
|
|
2191
2191
|
const [dragOver, setDragOver] = useState6(false);
|
|
@@ -2289,7 +2289,7 @@ function CanvasArea({ url, password, viewport, onBridgeReady, onPong }) {
|
|
|
2289
2289
|
maxWidth: "100%",
|
|
2290
2290
|
boxShadow: isConstrained ? "0 4px 24px rgba(0,0,0,0.1)" : "none"
|
|
2291
2291
|
},
|
|
2292
|
-
children: /* @__PURE__ */ jsx6(IframeCanvas, { url, password, onBridgeReady, onPong })
|
|
2292
|
+
children: /* @__PURE__ */ jsx6(IframeCanvas, { url, password, proxyBaseUrl, onBridgeReady, onPong })
|
|
2293
2293
|
}
|
|
2294
2294
|
) }),
|
|
2295
2295
|
dragOver && draggedSection && /* @__PURE__ */ jsx6(
|
|
@@ -4136,7 +4136,7 @@ function sendToPlatform(type, payload) {
|
|
|
4136
4136
|
data: { channel: PLATFORM_CHANNEL, type, payload }
|
|
4137
4137
|
}));
|
|
4138
4138
|
}
|
|
4139
|
-
function EditorShell({ initialExperiment, embeddedMode }) {
|
|
4139
|
+
function EditorShell({ initialExperiment, embeddedMode, proxyBaseUrl }) {
|
|
4140
4140
|
const [url, setUrl] = useState12("");
|
|
4141
4141
|
const [password, setPassword] = useState12("");
|
|
4142
4142
|
const [connectionStatus, setConnectionStatus] = useState12("disconnected");
|
|
@@ -4590,6 +4590,7 @@ function EditorShell({ initialExperiment, embeddedMode }) {
|
|
|
4590
4590
|
url,
|
|
4591
4591
|
password,
|
|
4592
4592
|
viewport,
|
|
4593
|
+
proxyBaseUrl,
|
|
4593
4594
|
onBridgeReady: handleBridgeReady,
|
|
4594
4595
|
onPong: handlePong
|
|
4595
4596
|
}
|
|
@@ -4605,6 +4606,7 @@ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
|
4605
4606
|
function PlatformVisualEditor({
|
|
4606
4607
|
channel = "conversion-platform",
|
|
4607
4608
|
embeddedGlobalKey = "__CONVERSION_EMBEDDED__",
|
|
4609
|
+
proxyBaseUrl = typeof import.meta !== "undefined" && import.meta.env?.VITE_API_BASE_URL || "",
|
|
4608
4610
|
className = "fixed inset-0 z-[9999] flex flex-col bg-white",
|
|
4609
4611
|
editorClassName = "flex-1 min-h-0",
|
|
4610
4612
|
showHeader = true,
|
|
@@ -4816,7 +4818,7 @@ function PlatformVisualEditor({
|
|
|
4816
4818
|
) : null
|
|
4817
4819
|
] })
|
|
4818
4820
|
] }) : null,
|
|
4819
|
-
/* @__PURE__ */ jsx13("div", { className: editorClassName, children: /* @__PURE__ */ jsx13(ToastProvider, { children: /* @__PURE__ */ jsx13(EditorShell, { initialExperiment: experiment, embeddedMode: true }) }) })
|
|
4821
|
+
/* @__PURE__ */ jsx13("div", { className: editorClassName, children: /* @__PURE__ */ jsx13(ToastProvider, { children: /* @__PURE__ */ jsx13(EditorShell, { initialExperiment: experiment, embeddedMode: true, proxyBaseUrl }) }) })
|
|
4820
4822
|
] });
|
|
4821
4823
|
}
|
|
4822
4824
|
|
package/dist/vite.js
CHANGED
|
@@ -1766,246 +1766,264 @@ var getDefaultAnthropicApiKey = () => {
|
|
|
1766
1766
|
return "";
|
|
1767
1767
|
}
|
|
1768
1768
|
};
|
|
1769
|
-
function
|
|
1769
|
+
function createVisualEditorMiddleware(options) {
|
|
1770
1770
|
const anthropicApiKey = options?.anthropicApiKey || getDefaultAnthropicApiKey();
|
|
1771
1771
|
const enableGenerateTestApi = options?.enableGenerateTestApi ?? true;
|
|
1772
|
-
return {
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
}
|
|
1809
|
-
try {
|
|
1810
|
-
const chunks = [];
|
|
1811
|
-
for await (const chunk of req) chunks.push(chunk);
|
|
1812
|
-
const body = JSON.parse(Buffer.concat(chunks).toString());
|
|
1813
|
-
if (!body.prompt || !body.pageSnapshot) {
|
|
1814
|
-
res.statusCode = 400;
|
|
1815
|
-
res.setHeader("Content-Type", "application/json");
|
|
1816
|
-
res.end(JSON.stringify({ error: "Missing prompt or pageSnapshot" }));
|
|
1817
|
-
return;
|
|
1818
|
-
}
|
|
1819
|
-
const parts = [
|
|
1820
|
-
"## Optimization Goal",
|
|
1821
|
-
body.prompt,
|
|
1822
|
-
"",
|
|
1823
|
-
"## Page Snapshot",
|
|
1824
|
-
JSON.stringify(body.pageSnapshot, null, 2)
|
|
1825
|
-
];
|
|
1826
|
-
const anthropicRes = await fetch("https://api.anthropic.com/v1/messages", {
|
|
1827
|
-
method: "POST",
|
|
1828
|
-
headers: {
|
|
1829
|
-
"Content-Type": "application/json",
|
|
1830
|
-
"x-api-key": anthropicApiKey,
|
|
1831
|
-
"anthropic-version": "2023-06-01"
|
|
1832
|
-
},
|
|
1833
|
-
body: JSON.stringify({
|
|
1834
|
-
model: "claude-sonnet-4-20250514",
|
|
1835
|
-
max_tokens: 4096,
|
|
1836
|
-
system: AI_SYSTEM_PROMPT,
|
|
1837
|
-
messages: [{ role: "user", content: parts.join("\n") }]
|
|
1838
|
-
})
|
|
1839
|
-
});
|
|
1840
|
-
if (!anthropicRes.ok) {
|
|
1841
|
-
const errText = await anthropicRes.text();
|
|
1842
|
-
res.statusCode = 502;
|
|
1843
|
-
res.setHeader("Content-Type", "application/json");
|
|
1844
|
-
res.end(
|
|
1845
|
-
JSON.stringify({
|
|
1846
|
-
error: `Anthropic API returned ${anthropicRes.status}`,
|
|
1847
|
-
detail: errText
|
|
1848
|
-
})
|
|
1849
|
-
);
|
|
1850
|
-
return;
|
|
1851
|
-
}
|
|
1852
|
-
const anthropicData = await anthropicRes.json();
|
|
1853
|
-
const textBlock = anthropicData.content?.find((b) => b.type === "text");
|
|
1854
|
-
if (!textBlock?.text) {
|
|
1855
|
-
res.statusCode = 502;
|
|
1856
|
-
res.setHeader("Content-Type", "application/json");
|
|
1857
|
-
res.end(JSON.stringify({ error: "No text in Anthropic response" }));
|
|
1858
|
-
return;
|
|
1859
|
-
}
|
|
1860
|
-
let cleaned = textBlock.text.trim();
|
|
1861
|
-
if (cleaned.startsWith("```")) {
|
|
1862
|
-
cleaned = cleaned.replace(/^```[a-zA-Z]*\n?/, "").replace(/\n?```\s*$/, "").trim();
|
|
1863
|
-
}
|
|
1864
|
-
let parsed;
|
|
1865
|
-
try {
|
|
1866
|
-
parsed = JSON.parse(cleaned);
|
|
1867
|
-
} catch {
|
|
1868
|
-
res.statusCode = 502;
|
|
1869
|
-
res.setHeader("Content-Type", "application/json");
|
|
1870
|
-
res.end(JSON.stringify({ error: "Invalid JSON from Claude", raw: cleaned }));
|
|
1871
|
-
return;
|
|
1872
|
-
}
|
|
1873
|
-
const now = Date.now();
|
|
1874
|
-
if (parsed.mutations) {
|
|
1875
|
-
parsed.mutations = parsed.mutations.map((m, i) => ({
|
|
1876
|
-
...m,
|
|
1877
|
-
id: m.id || `ai_mut_${String(i + 1).padStart(3, "0")}`,
|
|
1878
|
-
timestamp: now + i
|
|
1879
|
-
}));
|
|
1880
|
-
}
|
|
1881
|
-
res.setHeader("Content-Type", "application/json");
|
|
1882
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1883
|
-
res.end(JSON.stringify(parsed));
|
|
1884
|
-
} catch (err) {
|
|
1885
|
-
res.statusCode = 500;
|
|
1886
|
-
res.setHeader("Content-Type", "application/json");
|
|
1887
|
-
res.end(JSON.stringify({ error: err.message }));
|
|
1888
|
-
}
|
|
1889
|
-
});
|
|
1772
|
+
return async (req, res, next) => {
|
|
1773
|
+
const pathname = (req.url || "").split("?")[0];
|
|
1774
|
+
if (pathname === "/bridge.js") {
|
|
1775
|
+
res.setHeader("Content-Type", "application/javascript; charset=utf-8");
|
|
1776
|
+
res.setHeader("Cache-Control", "no-store");
|
|
1777
|
+
res.end(BRIDGE_SCRIPT);
|
|
1778
|
+
return;
|
|
1779
|
+
}
|
|
1780
|
+
if (pathname === "/vvveb-editor") {
|
|
1781
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
1782
|
+
res.setHeader("Cache-Control", "no-store");
|
|
1783
|
+
res.setHeader("X-Frame-Options", "SAMEORIGIN");
|
|
1784
|
+
res.end(buildVvvebEditorHtml());
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1787
|
+
if (pathname === "/api/generate-test" && enableGenerateTestApi) {
|
|
1788
|
+
if (req.method === "OPTIONS") {
|
|
1789
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1790
|
+
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
|
|
1791
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
1792
|
+
res.statusCode = 204;
|
|
1793
|
+
res.end();
|
|
1794
|
+
return;
|
|
1795
|
+
}
|
|
1796
|
+
if (req.method !== "POST") {
|
|
1797
|
+
res.statusCode = 405;
|
|
1798
|
+
res.end(JSON.stringify({ error: "Method not allowed" }));
|
|
1799
|
+
return;
|
|
1800
|
+
}
|
|
1801
|
+
if (!anthropicApiKey) {
|
|
1802
|
+
res.statusCode = 500;
|
|
1803
|
+
res.setHeader("Content-Type", "application/json");
|
|
1804
|
+
res.end(
|
|
1805
|
+
JSON.stringify({ error: "ANTHROPIC_API_KEY is not configured" })
|
|
1806
|
+
);
|
|
1807
|
+
return;
|
|
1890
1808
|
}
|
|
1891
|
-
|
|
1809
|
+
try {
|
|
1810
|
+
const chunks = [];
|
|
1811
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
1812
|
+
const body = JSON.parse(Buffer.concat(chunks).toString());
|
|
1813
|
+
if (!body.prompt || !body.pageSnapshot) {
|
|
1814
|
+
res.statusCode = 400;
|
|
1815
|
+
res.setHeader("Content-Type", "application/json");
|
|
1816
|
+
res.end(JSON.stringify({ error: "Missing prompt or pageSnapshot" }));
|
|
1817
|
+
return;
|
|
1818
|
+
}
|
|
1819
|
+
const parts = [
|
|
1820
|
+
"## Optimization Goal",
|
|
1821
|
+
body.prompt,
|
|
1822
|
+
"",
|
|
1823
|
+
"## Page Snapshot",
|
|
1824
|
+
JSON.stringify(body.pageSnapshot, null, 2)
|
|
1825
|
+
];
|
|
1826
|
+
const anthropicRes = await fetch("https://api.anthropic.com/v1/messages", {
|
|
1827
|
+
method: "POST",
|
|
1828
|
+
headers: {
|
|
1829
|
+
"Content-Type": "application/json",
|
|
1830
|
+
"x-api-key": anthropicApiKey,
|
|
1831
|
+
"anthropic-version": "2023-06-01"
|
|
1832
|
+
},
|
|
1833
|
+
body: JSON.stringify({
|
|
1834
|
+
model: "claude-sonnet-4-20250514",
|
|
1835
|
+
max_tokens: 4096,
|
|
1836
|
+
system: AI_SYSTEM_PROMPT,
|
|
1837
|
+
messages: [{ role: "user", content: parts.join("\n") }]
|
|
1838
|
+
})
|
|
1839
|
+
});
|
|
1840
|
+
if (!anthropicRes.ok) {
|
|
1841
|
+
const errText = await anthropicRes.text();
|
|
1842
|
+
res.statusCode = 502;
|
|
1843
|
+
res.setHeader("Content-Type", "application/json");
|
|
1844
|
+
res.end(
|
|
1845
|
+
JSON.stringify({
|
|
1846
|
+
error: `Anthropic API returned ${anthropicRes.status}`,
|
|
1847
|
+
detail: errText
|
|
1848
|
+
})
|
|
1849
|
+
);
|
|
1850
|
+
return;
|
|
1851
|
+
}
|
|
1852
|
+
const anthropicData = await anthropicRes.json();
|
|
1853
|
+
const textBlock = anthropicData.content?.find((b) => b.type === "text");
|
|
1854
|
+
if (!textBlock?.text) {
|
|
1855
|
+
res.statusCode = 502;
|
|
1856
|
+
res.setHeader("Content-Type", "application/json");
|
|
1857
|
+
res.end(JSON.stringify({ error: "No text in Anthropic response" }));
|
|
1858
|
+
return;
|
|
1859
|
+
}
|
|
1860
|
+
let cleaned = textBlock.text.trim();
|
|
1861
|
+
if (cleaned.startsWith("```")) {
|
|
1862
|
+
cleaned = cleaned.replace(/^```[a-zA-Z]*\n?/, "").replace(/\n?```\s*$/, "").trim();
|
|
1863
|
+
}
|
|
1864
|
+
let parsed;
|
|
1892
1865
|
try {
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
for await (const chunk of req) {
|
|
1948
|
-
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
1949
|
-
}
|
|
1950
|
-
if (chunks.length > 0) requestBody = Buffer.concat(chunks);
|
|
1951
|
-
}
|
|
1952
|
-
const upstream = await fetch(targetUrl, {
|
|
1953
|
-
method,
|
|
1954
|
-
headers: fetchHeaders,
|
|
1955
|
-
body: requestBody ? Buffer.from(requestBody) : null,
|
|
1956
|
-
redirect: "follow"
|
|
1866
|
+
parsed = JSON.parse(cleaned);
|
|
1867
|
+
} catch {
|
|
1868
|
+
res.statusCode = 502;
|
|
1869
|
+
res.setHeader("Content-Type", "application/json");
|
|
1870
|
+
res.end(JSON.stringify({ error: "Invalid JSON from Claude", raw: cleaned }));
|
|
1871
|
+
return;
|
|
1872
|
+
}
|
|
1873
|
+
const now = Date.now();
|
|
1874
|
+
if (parsed.mutations) {
|
|
1875
|
+
parsed.mutations = parsed.mutations.map((m, i) => ({
|
|
1876
|
+
...m,
|
|
1877
|
+
id: m.id || `ai_mut_${String(i + 1).padStart(3, "0")}`,
|
|
1878
|
+
timestamp: now + i
|
|
1879
|
+
}));
|
|
1880
|
+
}
|
|
1881
|
+
res.setHeader("Content-Type", "application/json");
|
|
1882
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1883
|
+
res.end(JSON.stringify(parsed));
|
|
1884
|
+
} catch (err) {
|
|
1885
|
+
res.statusCode = 500;
|
|
1886
|
+
res.setHeader("Content-Type", "application/json");
|
|
1887
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
1888
|
+
}
|
|
1889
|
+
return;
|
|
1890
|
+
}
|
|
1891
|
+
if (pathname.startsWith("/api/proxy")) {
|
|
1892
|
+
try {
|
|
1893
|
+
const url = new URL(req.url || "", "http://localhost");
|
|
1894
|
+
const targetUrl = url.searchParams.get("url");
|
|
1895
|
+
const password = url.searchParams.get("password") || "";
|
|
1896
|
+
if (!targetUrl) {
|
|
1897
|
+
res.statusCode = 400;
|
|
1898
|
+
res.end(JSON.stringify({ error: "Missing url parameter" }));
|
|
1899
|
+
return;
|
|
1900
|
+
}
|
|
1901
|
+
const parsed = new URL(targetUrl);
|
|
1902
|
+
const origin = parsed.origin;
|
|
1903
|
+
const method = (req.method || "GET").toUpperCase();
|
|
1904
|
+
const headers = {
|
|
1905
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
|
|
1906
|
+
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
|
|
1907
|
+
};
|
|
1908
|
+
let cookieHeader = "";
|
|
1909
|
+
if (password) {
|
|
1910
|
+
const passResp = await fetch(`${origin}/password`, {
|
|
1911
|
+
method: "POST",
|
|
1912
|
+
headers: {
|
|
1913
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1914
|
+
...headers
|
|
1915
|
+
},
|
|
1916
|
+
body: `form_type=storefront_password&utf8=%E2%9C%93&password=${encodeURIComponent(
|
|
1917
|
+
password
|
|
1918
|
+
)}`,
|
|
1919
|
+
redirect: "manual"
|
|
1957
1920
|
});
|
|
1958
|
-
const
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
res.statusCode = 401;
|
|
1973
|
-
res.end(JSON.stringify({ error: "Password authentication failed." }));
|
|
1921
|
+
const setCookies = passResp.headers.getSetCookie?.() || [];
|
|
1922
|
+
cookieHeader = setCookies.map((c) => c.split(";")[0]).join("; ");
|
|
1923
|
+
}
|
|
1924
|
+
const fetchHeaders = { ...headers };
|
|
1925
|
+
Object.entries(req.headers || {}).forEach(([key, value]) => {
|
|
1926
|
+
const lowerKey = key.toLowerCase();
|
|
1927
|
+
if (!value || [
|
|
1928
|
+
"host",
|
|
1929
|
+
"connection",
|
|
1930
|
+
"content-length",
|
|
1931
|
+
"accept-encoding",
|
|
1932
|
+
"origin",
|
|
1933
|
+
"referer"
|
|
1934
|
+
].includes(lowerKey)) {
|
|
1974
1935
|
return;
|
|
1975
1936
|
}
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1937
|
+
fetchHeaders[key] = Array.isArray(value) ? value.join(",") : String(value);
|
|
1938
|
+
});
|
|
1939
|
+
fetchHeaders.Origin = origin;
|
|
1940
|
+
fetchHeaders.Referer = targetUrl;
|
|
1941
|
+
if (cookieHeader) {
|
|
1942
|
+
fetchHeaders.Cookie = fetchHeaders.Cookie ? `${fetchHeaders.Cookie}; ${cookieHeader}` : cookieHeader;
|
|
1943
|
+
}
|
|
1944
|
+
let requestBody;
|
|
1945
|
+
if (!["GET", "HEAD"].includes(method)) {
|
|
1946
|
+
const chunks = [];
|
|
1947
|
+
for await (const chunk of req) {
|
|
1948
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
1986
1949
|
}
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1950
|
+
if (chunks.length > 0) requestBody = Buffer.concat(chunks);
|
|
1951
|
+
}
|
|
1952
|
+
const upstream = await fetch(targetUrl, {
|
|
1953
|
+
method,
|
|
1954
|
+
headers: fetchHeaders,
|
|
1955
|
+
body: requestBody ? Buffer.from(requestBody) : null,
|
|
1956
|
+
redirect: "follow"
|
|
1957
|
+
});
|
|
1958
|
+
const responseContentType = upstream.headers.get("content-type") || "";
|
|
1959
|
+
const isHtmlResponse = responseContentType.includes("text/html");
|
|
1960
|
+
if (!isHtmlResponse) {
|
|
1961
|
+
const binary = Buffer.from(await upstream.arrayBuffer());
|
|
1962
|
+
res.statusCode = upstream.status;
|
|
1963
|
+
if (responseContentType) {
|
|
1964
|
+
res.setHeader("Content-Type", responseContentType);
|
|
1993
1965
|
}
|
|
1994
|
-
const bridgeScript = `<script src="/bridge.js"></script>`;
|
|
1995
|
-
html = html.includes("</body>") ? html.replace("</body>", `${bridgeScript}
|
|
1996
|
-
</body>`) : html + bridgeScript;
|
|
1997
|
-
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
1998
1966
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1999
|
-
res.
|
|
2000
|
-
|
|
2001
|
-
} catch (err) {
|
|
2002
|
-
res.statusCode = 500;
|
|
2003
|
-
res.end(JSON.stringify({ error: err.message }));
|
|
1967
|
+
res.end(binary);
|
|
1968
|
+
return;
|
|
2004
1969
|
}
|
|
2005
|
-
|
|
1970
|
+
let html = await upstream.text();
|
|
1971
|
+
if (html.includes("form_type") && html.includes("storefront_password")) {
|
|
1972
|
+
res.statusCode = 401;
|
|
1973
|
+
res.end(JSON.stringify({ error: "Password authentication failed." }));
|
|
1974
|
+
return;
|
|
1975
|
+
}
|
|
1976
|
+
html = html.replace(/(href|src|action)="\/(?!\/)/g, `$1="${origin}/`);
|
|
1977
|
+
const escapedOrigin = origin.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1978
|
+
const proxyBase = `/api/proxy?password=${encodeURIComponent(password)}&url=`;
|
|
1979
|
+
html = html.replace(
|
|
1980
|
+
new RegExp(`(href|src|action)="${escapedOrigin}(/[^"]*)"`, "g"),
|
|
1981
|
+
(_, attr, urlPath) => `${attr}="${proxyBase}${encodeURIComponent(origin + urlPath)}"`
|
|
1982
|
+
);
|
|
1983
|
+
if (html.includes("</head>")) {
|
|
1984
|
+
html = html.replace("</head>", `${popupHideCss}
|
|
1985
|
+
</head>`);
|
|
1986
|
+
}
|
|
1987
|
+
const runtimeProxyScript = `<script>(function(){try{var TARGET_ORIGIN=${JSON.stringify(origin)};var TARGET_PAGE_URL=${JSON.stringify(targetUrl)};var PROXY_BASE=${JSON.stringify(`/api/proxy?password=${encodeURIComponent(password)}&url=`)};function isSkippable(raw){if(!raw||typeof raw!=="string")return true;return raw.startsWith("data:")||raw.startsWith("blob:")||raw.startsWith("javascript:")||raw.startsWith("#");}function toProxiedUrl(raw){if(isSkippable(raw))return raw;try{if(raw.startsWith("/api/proxy?"))return raw;var base=raw.startsWith("/")?TARGET_ORIGIN:TARGET_PAGE_URL;var abs=new URL(raw,base);if(abs.origin!==TARGET_ORIGIN)return raw;return PROXY_BASE+encodeURIComponent(abs.toString());}catch(_){return raw;}}if(window.fetch){var _fetch=window.fetch.bind(window);window.fetch=function(input,init){try{if(typeof input==="string"){input=toProxiedUrl(input);}else if(input&&input.url){var next=toProxiedUrl(input.url);if(next!==input.url){input=new Request(next,input);}}}catch(_){}return _fetch(input,init);};}if(window.XMLHttpRequest&&window.XMLHttpRequest.prototype&&window.XMLHttpRequest.prototype.open){var _open=window.XMLHttpRequest.prototype.open;window.XMLHttpRequest.prototype.open=function(method,url){try{arguments[1]=toProxiedUrl(url);}catch(_){}return _open.apply(this,arguments);};}if(window.navigator&&typeof window.navigator.sendBeacon==="function"){var _beacon=window.navigator.sendBeacon.bind(window.navigator);window.navigator.sendBeacon=function(url,data){try{return _beacon(toProxiedUrl(url),data);}catch(_){return _beacon(url,data);}};}if(window.navigator&&window.navigator.serviceWorker&&typeof window.navigator.serviceWorker.register==="function"){window.navigator.serviceWorker.register=function(){return Promise.resolve({scope:"disabled-in-editor-proxy"});};}}catch(_){}})();</script>`;
|
|
1988
|
+
if (html.includes("</head>")) {
|
|
1989
|
+
html = html.replace("</head>", `${runtimeProxyScript}
|
|
1990
|
+
</head>`);
|
|
1991
|
+
} else {
|
|
1992
|
+
html = runtimeProxyScript + html;
|
|
1993
|
+
}
|
|
1994
|
+
const bridgeScript = `<script src="/bridge.js"></script>`;
|
|
1995
|
+
html = html.includes("</body>") ? html.replace("</body>", `${bridgeScript}
|
|
1996
|
+
</body>`) : html + bridgeScript;
|
|
1997
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
1998
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1999
|
+
res.setHeader("X-Frame-Options", "SAMEORIGIN");
|
|
2000
|
+
res.end(html);
|
|
2001
|
+
} catch (err) {
|
|
2002
|
+
res.statusCode = 500;
|
|
2003
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
2004
|
+
}
|
|
2005
|
+
return;
|
|
2006
|
+
}
|
|
2007
|
+
next();
|
|
2008
|
+
};
|
|
2009
|
+
}
|
|
2010
|
+
function visualEditorProxyPlugin(options) {
|
|
2011
|
+
const mw = createVisualEditorMiddleware(options);
|
|
2012
|
+
return {
|
|
2013
|
+
name: "visual-editor-proxy",
|
|
2014
|
+
generateBundle() {
|
|
2015
|
+
this.emitFile({ type: "asset", fileName: "bridge.js", source: BRIDGE_SCRIPT });
|
|
2016
|
+
this.emitFile({ type: "asset", fileName: "vvveb-editor/index.html", source: buildVvvebEditorHtml() });
|
|
2017
|
+
},
|
|
2018
|
+
configureServer(server) {
|
|
2019
|
+
server.middlewares.use(mw);
|
|
2020
|
+
},
|
|
2021
|
+
configurePreviewServer(server) {
|
|
2022
|
+
server.middlewares.use(mw);
|
|
2006
2023
|
}
|
|
2007
2024
|
};
|
|
2008
2025
|
}
|
|
2009
2026
|
export {
|
|
2027
|
+
createVisualEditorMiddleware,
|
|
2010
2028
|
visualEditorProxyPlugin
|
|
2011
2029
|
};
|