@accelerated-agency/visual-editor 0.2.0 → 0.2.1

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 CHANGED
@@ -1832,8 +1832,10 @@ function ElementIcon({ tag }) {
1832
1832
  return /* @__PURE__ */ jsx("span", { className: "shrink-0 w-5 h-5 rounded flex items-center justify-center", style: { backgroundColor: bgColor }, children: /* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", children: /* @__PURE__ */ jsx("rect", { x: "1.5", y: "1.5", width: "9", height: "9", rx: "1.5", stroke: iconColor, strokeWidth: "1.2" }) }) });
1833
1833
  }
1834
1834
  var CHANNEL = "conversion-editor";
1835
+ var IFRAME_LOAD_GUARD_MS = 13e4;
1835
1836
  function IframeCanvas({ url, password, proxyBaseUrl = "", onBridgeReady, onPong }) {
1836
1837
  const iframeElRef = useRef(null);
1838
+ const loadGuardRef = useRef(null);
1837
1839
  const setSelectedElement = useMutationsStore((s) => s.setSelectedElement);
1838
1840
  const addMutationToActive = useVariationsStore((s) => s.addMutationToActive);
1839
1841
  const [loading, setLoading] = useState(false);
@@ -1876,8 +1878,24 @@ function IframeCanvas({ url, password, proxyBaseUrl = "", onBridgeReady, onPong
1876
1878
  return () => window.removeEventListener("message", handleMessage);
1877
1879
  }, [handleMessage]);
1878
1880
  useEffect(() => {
1879
- if (url) setLoading(true);
1881
+ if (!url) return;
1882
+ setLoading(true);
1883
+ if (loadGuardRef.current) clearTimeout(loadGuardRef.current);
1884
+ loadGuardRef.current = setTimeout(() => {
1885
+ loadGuardRef.current = null;
1886
+ setLoading(false);
1887
+ }, IFRAME_LOAD_GUARD_MS);
1888
+ return () => {
1889
+ if (loadGuardRef.current) clearTimeout(loadGuardRef.current);
1890
+ };
1880
1891
  }, [url]);
1892
+ const clearLoadGuard = useCallback(() => {
1893
+ if (loadGuardRef.current) {
1894
+ clearTimeout(loadGuardRef.current);
1895
+ loadGuardRef.current = null;
1896
+ }
1897
+ setLoading(false);
1898
+ }, []);
1881
1899
  let resolvedUrl;
1882
1900
  if (url.toLowerCase() === "test") {
1883
1901
  resolvedUrl = "/test";
@@ -1908,7 +1926,8 @@ function IframeCanvas({ url, password, proxyBaseUrl = "", onBridgeReady, onPong
1908
1926
  src: resolvedUrl,
1909
1927
  className: "w-full h-full border-0",
1910
1928
  sandbox: "allow-scripts allow-same-origin allow-forms allow-popups",
1911
- onLoad: () => setLoading(false)
1929
+ onLoad: clearLoadGuard,
1930
+ onError: clearLoadGuard
1912
1931
  }
1913
1932
  )
1914
1933
  ] });
package/dist/vite.cjs CHANGED
@@ -1800,7 +1800,7 @@ function createVisualEditorMiddleware(options) {
1800
1800
  }
1801
1801
  }
1802
1802
  return async (req, res, next) => {
1803
- const pathname = (req.url || "").split("?")[0];
1803
+ let pathname = (req.url || "").split("?")[0];
1804
1804
  if (pathname === "/bridge.js") {
1805
1805
  res.removeHeader("X-Frame-Options");
1806
1806
  res.setHeader("Content-Type", "application/javascript; charset=utf-8");
@@ -1937,8 +1937,8 @@ function createVisualEditorMiddleware(options) {
1937
1937
  return;
1938
1938
  }
1939
1939
  if (pathname.startsWith("/api/proxy")) {
1940
- const rewritten = (req.url || "").replace("/api/proxy", "/api/conversion-proxy");
1941
- req.url = rewritten;
1940
+ req.url = (req.url || "").replace("/api/proxy", "/api/conversion-proxy");
1941
+ pathname = (req.url || "").split("?")[0];
1942
1942
  }
1943
1943
  if (pathname.startsWith("/api/conversion-proxy")) {
1944
1944
  try {
@@ -2001,17 +2001,39 @@ function createVisualEditorMiddleware(options) {
2001
2001
  }
2002
2002
  if (chunks.length > 0) requestBody = Buffer.concat(chunks);
2003
2003
  }
2004
- const upstream = await fetch(targetUrl, {
2005
- method,
2006
- headers: fetchHeaders,
2007
- body: requestBody ? Buffer.from(requestBody) : null,
2008
- redirect: "follow"
2009
- });
2004
+ const upstreamTimeoutMs = 12e4;
2005
+ const ac = new AbortController();
2006
+ const timeoutId = setTimeout(() => ac.abort(), upstreamTimeoutMs);
2007
+ let upstream;
2008
+ try {
2009
+ upstream = await fetch(targetUrl, {
2010
+ method,
2011
+ headers: fetchHeaders,
2012
+ body: requestBody ? Buffer.from(requestBody) : null,
2013
+ redirect: "follow",
2014
+ signal: ac.signal
2015
+ });
2016
+ } catch (fetchErr) {
2017
+ clearTimeout(timeoutId);
2018
+ const aborted = fetchErr?.name === "AbortError";
2019
+ res.statusCode = aborted ? 504 : 502;
2020
+ res.setHeader("Content-Type", "application/json");
2021
+ res.end(
2022
+ JSON.stringify({
2023
+ error: aborted ? `Upstream request timed out after ${upstreamTimeoutMs / 1e3}s` : fetchErr?.message || "Upstream fetch failed"
2024
+ })
2025
+ );
2026
+ return;
2027
+ }
2028
+ clearTimeout(timeoutId);
2010
2029
  const responseContentType = upstream.headers.get("content-type") || "";
2011
2030
  const isHtmlResponse = responseContentType.includes("text/html");
2031
+ const secFetchMode = (req.headers?.["sec-fetch-mode"] || "").toLowerCase();
2012
2032
  const secFetchDest = (req.headers?.["sec-fetch-dest"] || "").toLowerCase();
2013
- const isNavigationRequest = secFetchDest === "iframe" || secFetchDest === "document" || secFetchDest === "";
2014
- if (!isHtmlResponse || !isNavigationRequest) {
2033
+ const isLikelyDocumentNavigation = secFetchMode === "navigate" || secFetchDest === "iframe" || secFetchDest === "document" || secFetchDest === "nested-document" || secFetchDest === "frame";
2034
+ const isLikelyFetchOrXHR = secFetchDest === "empty" && (secFetchMode === "cors" || secFetchMode === "same-origin" || secFetchMode === "no-cors");
2035
+ const shouldInjectHtmlBridge = isHtmlResponse && (isLikelyDocumentNavigation || !isLikelyFetchOrXHR);
2036
+ if (!isHtmlResponse || !shouldInjectHtmlBridge) {
2015
2037
  const binary = Buffer.from(await upstream.arrayBuffer());
2016
2038
  res.statusCode = upstream.status;
2017
2039
  if (responseContentType) {
@@ -2044,7 +2066,7 @@ function createVisualEditorMiddleware(options) {
2044
2066
  html = html.replace("</head>", `${popupHideCss}
2045
2067
  </head>`);
2046
2068
  }
2047
- const runtimeProxyScript = `<script>(function(){try{var TARGET_ORIGIN=${JSON.stringify(origin)};var TARGET_PAGE_URL=${JSON.stringify(targetUrl)};var EMPTY_JSON_DATA="data:application/json;charset=utf-8,%7B%7D";function isSkippable(raw){if(!raw||typeof raw!=="string")return true;return raw.startsWith("data:")||raw.startsWith("blob:")||raw.startsWith("javascript:")||raw.startsWith("#")||raw.startsWith("http")||raw.startsWith("//");}function toAbsoluteOriginUrl(raw){if(isSkippable(raw))return raw;try{var base=raw.startsWith("/")?TARGET_ORIGIN:TARGET_PAGE_URL;var abs=new URL(raw,base);if(abs.origin!==TARGET_ORIGIN)return raw;return abs.toString();}catch(_){return raw;}}function resolveUrl(s){try{return new URL(s,window.location.href);}catch(_){return null;}}function isNestedMalformedProxy(u){if(!u)return false;var p=u.pathname||"";if(p==="/api/conversion-proxy"||p.indexOf("/api/conversion-proxy/")===0)return false;return p.indexOf("api/conversion-proxy")!==-1;}function skipNestedProxyNetwork(s){var u=typeof s==="string"?resolveUrl(s):null;return u&&isNestedMalformedProxy(u);}function emptyJsonFetchResponse(){return Promise.resolve(new Response("{}",{status:200,headers:{"Content-Type":"application/json; charset=utf-8"}}));}if(window.fetch){var _fetch=window.fetch.bind(window);window.fetch=function(input,init){try{var rawUrl=typeof input==="string"?input:(input&&input.url?String(input.url):"");if(rawUrl&&skipNestedProxyNetwork(rawUrl))return emptyJsonFetchResponse();if(typeof input==="string"){input=toAbsoluteOriginUrl(input);}else if(input&&input.url){var next=toAbsoluteOriginUrl(input.url);if(next!==input.url){input=new Request(next,input);}}var after=typeof input==="string"?input:(input&&input.url?String(input.url):"");if(after&&skipNestedProxyNetwork(after))return emptyJsonFetchResponse();}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{var u=resolveUrl(String(url));if(u&&isNestedMalformedProxy(u)){arguments[1]=EMPTY_JSON_DATA;}else{arguments[1]=toAbsoluteOriginUrl(url);}}catch(_){}return _open.apply(this,arguments);};}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>`;
2069
+ const runtimeProxyScript = `<script>(function(){try{var TARGET_ORIGIN=${JSON.stringify(origin)};var TARGET_PAGE_URL=${JSON.stringify(targetUrl)};var EMPTY_JSON_DATA="data:application/json;charset=utf-8,%7B%7D";function isSkippable(raw){if(!raw||typeof raw!=="string")return true;return raw.startsWith("data:")||raw.startsWith("blob:")||raw.startsWith("javascript:")||raw.startsWith("#")||raw.startsWith("http")||raw.startsWith("//");}function toAbsoluteOriginUrl(raw){if(isSkippable(raw))return raw;try{var base=raw.startsWith("/")?TARGET_ORIGIN:TARGET_PAGE_URL;var abs=new URL(raw,base);if(abs.origin!==TARGET_ORIGIN)return raw;return abs.toString();}catch(_){return raw;}}function resolveUrl(s){try{return new URL(s,window.location.href);}catch(_){return null;}}function isNestedMalformedProxy(u){if(!u)return false;var p=u.pathname||"";if(p==="/api/conversion-proxy"||p.indexOf("/api/conversion-proxy/")===0)return false;return p.indexOf("api/conversion-proxy")!==-1;}function skipNestedProxyNetwork(s){var u=typeof s==="string"?resolveUrl(s):null;return u&&isNestedMalformedProxy(u);}function emptyJsonFetchResponse(){return Promise.resolve(new Response("{}",{status:200,headers:{"Content-Type":"application/json; charset=utf-8"}}));}if(window.fetch){var _fetch=window.fetch.bind(window);window.fetch=function(input,init){try{var rawUrl=typeof input==="string"?input:(input&&input.url?String(input.url):"");if(rawUrl&&skipNestedProxyNetwork(rawUrl))return emptyJsonFetchResponse();if(typeof input==="string"){input=toAbsoluteOriginUrl(input);}else if(input&&input.url){var next=toAbsoluteOriginUrl(input.url);if(next!==input.url){input=new Request(next,input);}}var after=typeof input==="string"?input:(input&&input.url?String(input.url):"");if(after&&skipNestedProxyNetwork(after))return emptyJsonFetchResponse();}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{var u=resolveUrl(String(url));if(u&&isNestedMalformedProxy(u)){arguments[1]=EMPTY_JSON_DATA;}else{arguments[1]=toAbsoluteOriginUrl(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{if(skipNestedProxyNetwork(String(url)))return true;}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>`;
2048
2070
  if (html.includes("</head>")) {
2049
2071
  html = html.replace("</head>", `${runtimeProxyScript}
2050
2072
  </head>`);
package/dist/vite.js CHANGED
@@ -1792,7 +1792,7 @@ function createVisualEditorMiddleware(options) {
1792
1792
  }
1793
1793
  }
1794
1794
  return async (req, res, next) => {
1795
- const pathname = (req.url || "").split("?")[0];
1795
+ let pathname = (req.url || "").split("?")[0];
1796
1796
  if (pathname === "/bridge.js") {
1797
1797
  res.removeHeader("X-Frame-Options");
1798
1798
  res.setHeader("Content-Type", "application/javascript; charset=utf-8");
@@ -1929,8 +1929,8 @@ function createVisualEditorMiddleware(options) {
1929
1929
  return;
1930
1930
  }
1931
1931
  if (pathname.startsWith("/api/proxy")) {
1932
- const rewritten = (req.url || "").replace("/api/proxy", "/api/conversion-proxy");
1933
- req.url = rewritten;
1932
+ req.url = (req.url || "").replace("/api/proxy", "/api/conversion-proxy");
1933
+ pathname = (req.url || "").split("?")[0];
1934
1934
  }
1935
1935
  if (pathname.startsWith("/api/conversion-proxy")) {
1936
1936
  try {
@@ -1993,17 +1993,39 @@ function createVisualEditorMiddleware(options) {
1993
1993
  }
1994
1994
  if (chunks.length > 0) requestBody = Buffer.concat(chunks);
1995
1995
  }
1996
- const upstream = await fetch(targetUrl, {
1997
- method,
1998
- headers: fetchHeaders,
1999
- body: requestBody ? Buffer.from(requestBody) : null,
2000
- redirect: "follow"
2001
- });
1996
+ const upstreamTimeoutMs = 12e4;
1997
+ const ac = new AbortController();
1998
+ const timeoutId = setTimeout(() => ac.abort(), upstreamTimeoutMs);
1999
+ let upstream;
2000
+ try {
2001
+ upstream = await fetch(targetUrl, {
2002
+ method,
2003
+ headers: fetchHeaders,
2004
+ body: requestBody ? Buffer.from(requestBody) : null,
2005
+ redirect: "follow",
2006
+ signal: ac.signal
2007
+ });
2008
+ } catch (fetchErr) {
2009
+ clearTimeout(timeoutId);
2010
+ const aborted = fetchErr?.name === "AbortError";
2011
+ res.statusCode = aborted ? 504 : 502;
2012
+ res.setHeader("Content-Type", "application/json");
2013
+ res.end(
2014
+ JSON.stringify({
2015
+ error: aborted ? `Upstream request timed out after ${upstreamTimeoutMs / 1e3}s` : fetchErr?.message || "Upstream fetch failed"
2016
+ })
2017
+ );
2018
+ return;
2019
+ }
2020
+ clearTimeout(timeoutId);
2002
2021
  const responseContentType = upstream.headers.get("content-type") || "";
2003
2022
  const isHtmlResponse = responseContentType.includes("text/html");
2023
+ const secFetchMode = (req.headers?.["sec-fetch-mode"] || "").toLowerCase();
2004
2024
  const secFetchDest = (req.headers?.["sec-fetch-dest"] || "").toLowerCase();
2005
- const isNavigationRequest = secFetchDest === "iframe" || secFetchDest === "document" || secFetchDest === "";
2006
- if (!isHtmlResponse || !isNavigationRequest) {
2025
+ const isLikelyDocumentNavigation = secFetchMode === "navigate" || secFetchDest === "iframe" || secFetchDest === "document" || secFetchDest === "nested-document" || secFetchDest === "frame";
2026
+ const isLikelyFetchOrXHR = secFetchDest === "empty" && (secFetchMode === "cors" || secFetchMode === "same-origin" || secFetchMode === "no-cors");
2027
+ const shouldInjectHtmlBridge = isHtmlResponse && (isLikelyDocumentNavigation || !isLikelyFetchOrXHR);
2028
+ if (!isHtmlResponse || !shouldInjectHtmlBridge) {
2007
2029
  const binary = Buffer.from(await upstream.arrayBuffer());
2008
2030
  res.statusCode = upstream.status;
2009
2031
  if (responseContentType) {
@@ -2036,7 +2058,7 @@ function createVisualEditorMiddleware(options) {
2036
2058
  html = html.replace("</head>", `${popupHideCss}
2037
2059
  </head>`);
2038
2060
  }
2039
- const runtimeProxyScript = `<script>(function(){try{var TARGET_ORIGIN=${JSON.stringify(origin)};var TARGET_PAGE_URL=${JSON.stringify(targetUrl)};var EMPTY_JSON_DATA="data:application/json;charset=utf-8,%7B%7D";function isSkippable(raw){if(!raw||typeof raw!=="string")return true;return raw.startsWith("data:")||raw.startsWith("blob:")||raw.startsWith("javascript:")||raw.startsWith("#")||raw.startsWith("http")||raw.startsWith("//");}function toAbsoluteOriginUrl(raw){if(isSkippable(raw))return raw;try{var base=raw.startsWith("/")?TARGET_ORIGIN:TARGET_PAGE_URL;var abs=new URL(raw,base);if(abs.origin!==TARGET_ORIGIN)return raw;return abs.toString();}catch(_){return raw;}}function resolveUrl(s){try{return new URL(s,window.location.href);}catch(_){return null;}}function isNestedMalformedProxy(u){if(!u)return false;var p=u.pathname||"";if(p==="/api/conversion-proxy"||p.indexOf("/api/conversion-proxy/")===0)return false;return p.indexOf("api/conversion-proxy")!==-1;}function skipNestedProxyNetwork(s){var u=typeof s==="string"?resolveUrl(s):null;return u&&isNestedMalformedProxy(u);}function emptyJsonFetchResponse(){return Promise.resolve(new Response("{}",{status:200,headers:{"Content-Type":"application/json; charset=utf-8"}}));}if(window.fetch){var _fetch=window.fetch.bind(window);window.fetch=function(input,init){try{var rawUrl=typeof input==="string"?input:(input&&input.url?String(input.url):"");if(rawUrl&&skipNestedProxyNetwork(rawUrl))return emptyJsonFetchResponse();if(typeof input==="string"){input=toAbsoluteOriginUrl(input);}else if(input&&input.url){var next=toAbsoluteOriginUrl(input.url);if(next!==input.url){input=new Request(next,input);}}var after=typeof input==="string"?input:(input&&input.url?String(input.url):"");if(after&&skipNestedProxyNetwork(after))return emptyJsonFetchResponse();}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{var u=resolveUrl(String(url));if(u&&isNestedMalformedProxy(u)){arguments[1]=EMPTY_JSON_DATA;}else{arguments[1]=toAbsoluteOriginUrl(url);}}catch(_){}return _open.apply(this,arguments);};}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>`;
2061
+ const runtimeProxyScript = `<script>(function(){try{var TARGET_ORIGIN=${JSON.stringify(origin)};var TARGET_PAGE_URL=${JSON.stringify(targetUrl)};var EMPTY_JSON_DATA="data:application/json;charset=utf-8,%7B%7D";function isSkippable(raw){if(!raw||typeof raw!=="string")return true;return raw.startsWith("data:")||raw.startsWith("blob:")||raw.startsWith("javascript:")||raw.startsWith("#")||raw.startsWith("http")||raw.startsWith("//");}function toAbsoluteOriginUrl(raw){if(isSkippable(raw))return raw;try{var base=raw.startsWith("/")?TARGET_ORIGIN:TARGET_PAGE_URL;var abs=new URL(raw,base);if(abs.origin!==TARGET_ORIGIN)return raw;return abs.toString();}catch(_){return raw;}}function resolveUrl(s){try{return new URL(s,window.location.href);}catch(_){return null;}}function isNestedMalformedProxy(u){if(!u)return false;var p=u.pathname||"";if(p==="/api/conversion-proxy"||p.indexOf("/api/conversion-proxy/")===0)return false;return p.indexOf("api/conversion-proxy")!==-1;}function skipNestedProxyNetwork(s){var u=typeof s==="string"?resolveUrl(s):null;return u&&isNestedMalformedProxy(u);}function emptyJsonFetchResponse(){return Promise.resolve(new Response("{}",{status:200,headers:{"Content-Type":"application/json; charset=utf-8"}}));}if(window.fetch){var _fetch=window.fetch.bind(window);window.fetch=function(input,init){try{var rawUrl=typeof input==="string"?input:(input&&input.url?String(input.url):"");if(rawUrl&&skipNestedProxyNetwork(rawUrl))return emptyJsonFetchResponse();if(typeof input==="string"){input=toAbsoluteOriginUrl(input);}else if(input&&input.url){var next=toAbsoluteOriginUrl(input.url);if(next!==input.url){input=new Request(next,input);}}var after=typeof input==="string"?input:(input&&input.url?String(input.url):"");if(after&&skipNestedProxyNetwork(after))return emptyJsonFetchResponse();}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{var u=resolveUrl(String(url));if(u&&isNestedMalformedProxy(u)){arguments[1]=EMPTY_JSON_DATA;}else{arguments[1]=toAbsoluteOriginUrl(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{if(skipNestedProxyNetwork(String(url)))return true;}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>`;
2040
2062
  if (html.includes("</head>")) {
2041
2063
  html = html.replace("</head>", `${runtimeProxyScript}
2042
2064
  </head>`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@accelerated-agency/visual-editor",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "private": false,
5
5
  "description": "Conversion visual editor as a reusable React package",
6
6
  "type": "module",