@accelerated-agency/visual-editor 0.4.6 → 0.4.7

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.
Files changed (3) hide show
  1. package/dist/vite.cjs +204 -104
  2. package/dist/vite.js +204 -104
  3. package/package.json +1 -1
package/dist/vite.cjs CHANGED
@@ -11,20 +11,6 @@ var fs__default = /*#__PURE__*/_interopDefault(fs);
11
11
  var path__default = /*#__PURE__*/_interopDefault(path);
12
12
 
13
13
  // src/visualEditorProxyPlugin.ts
14
- var SCRAPER_PROXY_HOST = process.env.SCRAPERAPI_PROXY_HOST || "proxy-server.scraperapi.com";
15
- var SCRAPER_PROXY_PORT = Number(process.env.SCRAPERAPI_PROXY_PORT || "8001");
16
- var SCRAPER_PROXY_USERNAME_BASE = process.env.SCRAPERAPI_PROXY_USERNAME_BASE || "scraperapi";
17
- var SCRAPER_PROXY_USERNAME_PARAMS = process.env.SCRAPERAPI_PROXY_USERNAME_PARAMS || "render=true.wait_for_selector=body.follow_redirect=false.keep_headers=true";
18
- var SCRAPER_PROXY_USERNAME = process.env.SCRAPERAPI_PROXY_USERNAME || [
19
- SCRAPER_PROXY_USERNAME_BASE,
20
- SCRAPER_PROXY_USERNAME_PARAMS
21
- ].filter(Boolean).join(".");
22
- var SCRAPER_PROXY_PASSWORD = process.env.SCRAPERAPI_PROXY_PASSWORD || process.env.SCRAPERAPI_API_KEY;
23
- var SCRAPER_API_ENDPOINT = process.env.SCRAPERAPI_ENDPOINT || "https://api.scraperapi.com/";
24
- var SCRAPER_REQUEST_TLS_REJECT_UNAUTHORIZED_RAW = process.env.SCRAPERAPI_REQUEST_TLS_REJECT_UNAUTHORIZED || "false";
25
- var SCRAPER_REQUEST_TLS_REJECT_UNAUTHORIZED = !["0", "false", "no"].includes(
26
- SCRAPER_REQUEST_TLS_REJECT_UNAUTHORIZED_RAW.toLowerCase()
27
- );
28
14
  var DEFAULT_TRACKING_MARKERS = [
29
15
  "snowplow",
30
16
  "taboola",
@@ -122,30 +108,6 @@ function stripTrackingScriptsFromScrapedHtml(html, markers) {
122
108
  }
123
109
  return out;
124
110
  }
125
- var SCRAPER_BILLING_ERROR_RE = /exhausted the api credits|upgrade your subscription|enable overages|dashboard\.scraperapi\.com\/billing|insufficient credits|billing/i;
126
- var scraperProxyClientPromise = null;
127
- async function getScraperProxyClient() {
128
- if (scraperProxyClientPromise) return scraperProxyClientPromise;
129
- scraperProxyClientPromise = (async () => {
130
- if (!SCRAPER_PROXY_PASSWORD) return null;
131
- try {
132
- const undici = await import('undici');
133
- const proxyUrl = "http://" + encodeURIComponent(SCRAPER_PROXY_USERNAME) + ":" + encodeURIComponent(SCRAPER_PROXY_PASSWORD) + "@" + SCRAPER_PROXY_HOST + ":" + String(SCRAPER_PROXY_PORT);
134
- return {
135
- dispatcher: new undici.ProxyAgent({
136
- uri: proxyUrl,
137
- requestTls: {
138
- rejectUnauthorized: SCRAPER_REQUEST_TLS_REJECT_UNAUTHORIZED
139
- }
140
- }),
141
- fetchFn: undici.fetch
142
- };
143
- } catch (_) {
144
- return null;
145
- }
146
- })();
147
- return scraperProxyClientPromise;
148
- }
149
111
  var iframeAlwaysShowCss = `<style id="__ce_force_show">
150
112
 
151
113
  </style>`;
@@ -3283,6 +3245,35 @@ function parseEditorUrlPayload(rawUrl) {
3283
3245
  password: nestedPassword || undefined,
3284
3246
  };
3285
3247
  }
3248
+ var path = parsed.pathname || '';
3249
+ var isProxyPath =
3250
+ path === '/api/conversion-proxy' ||
3251
+ path.indexOf('/api/conversion-proxy/') === 0 ||
3252
+ path.indexOf('api/conversion-proxy') !== -1;
3253
+ if (isProxyPath) {
3254
+ var fallbackUrl = (experimentData && experimentData.pageUrl) ? String(experimentData.pageUrl) : '';
3255
+ if (fallbackUrl) {
3256
+ var recovered = new URL(fallbackUrl, window.location.href);
3257
+ var qp = new URLSearchParams(parsed.search || '');
3258
+ qp.delete('url');
3259
+ qp.delete('password');
3260
+ qp.delete('conversionProxyBaseUrl');
3261
+ qp.delete('trackingMarkers');
3262
+ qp.delete('strictObserverFreeze');
3263
+ qp.delete('proxy');
3264
+ qp.delete('raw');
3265
+ recovered.search = '';
3266
+ qp.forEach(function(v, k) { recovered.searchParams.set(k, v); });
3267
+ recovered.hash = parsed.hash || '';
3268
+ return {
3269
+ url: recovered.toString(),
3270
+ password:
3271
+ (experimentData && experimentData.editorPassword)
3272
+ ? String(experimentData.editorPassword)
3273
+ : undefined,
3274
+ };
3275
+ }
3276
+ }
3286
3277
  return {
3287
3278
  url: parsed.toString(),
3288
3279
  password:
@@ -3301,6 +3292,12 @@ function emitEditorUrlChangedPayload(payload) {
3301
3292
  var key = String(payload.url || '') + '|' + String(payload.password || '');
3302
3293
  if (key === lastEditorUrlPayloadKey) return;
3303
3294
  lastEditorUrlPayloadKey = key;
3295
+ try {
3296
+ console.info('[V2 iframe-url]', {
3297
+ url: payload.url || '',
3298
+ passwordPresent: !!payload.password,
3299
+ });
3300
+ } catch(_) {}
3304
3301
  send('editor-url-changed', payload);
3305
3302
  }
3306
3303
  function emitEditorUrlChanged(rawUrl) {
@@ -5073,6 +5070,67 @@ function stripDataVveInstanceSubtree(root) {
5073
5070
  }
5074
5071
  }
5075
5072
 
5073
+ function cssEscapeIdent(raw) {
5074
+ var s = raw == null ? '' : String(raw);
5075
+ if (!s) return '';
5076
+ try {
5077
+ if (typeof CSS !== 'undefined' && CSS && typeof CSS.escape === 'function') {
5078
+ return CSS.escape(s);
5079
+ }
5080
+ } catch(_) {}
5081
+ // Fallback escape for identifiers when CSS.escape is unavailable.
5082
+ return s.replace(/[^a-zA-Z0-9_-]/g, function(ch) { return '\\\\' + ch; });
5083
+ }
5084
+
5085
+ function unescapeCssToken(token) {
5086
+ if (!token) return '';
5087
+ var s = String(token);
5088
+ var out = '';
5089
+ for (var i = 0; i < s.length; i++) {
5090
+ var ch = s.charAt(i);
5091
+ if (ch === '\\\\' && i + 1 < s.length) {
5092
+ out += s.charAt(i + 1);
5093
+ i += 1;
5094
+ continue;
5095
+ }
5096
+ out += ch;
5097
+ }
5098
+ return out;
5099
+ }
5100
+
5101
+ function isGeneratedClassToken(token) {
5102
+ var t = token == null ? '' : String(token).trim();
5103
+ if (!t) return true;
5104
+ if (t.indexOf('vve-') === 0) return true;
5105
+ // Tailwind arbitrary values / variant-heavy utilities are often unstable in selectors.
5106
+ if (
5107
+ t.indexOf('[') !== -1 ||
5108
+ t.indexOf(']') !== -1 ||
5109
+ t.indexOf('(') !== -1 ||
5110
+ t.indexOf(')') !== -1 ||
5111
+ t.indexOf('{') !== -1 ||
5112
+ t.indexOf('}') !== -1 ||
5113
+ t.indexOf(':') !== -1
5114
+ ) return true;
5115
+ // CSS-in-JS / runtime hash prefixes.
5116
+ if (/^(css|jsx|sc|emotion|styled|chakra|mantine|mui|ant)-/i.test(t)) return true;
5117
+ // Classnames with long hashy suffixes (framework/runtime generated).
5118
+ if (/[a-f0-9]{8,}/i.test(t)) return true;
5119
+ if (/^[a-zA-Z_-]+[0-9]{4,}[a-zA-Z0-9_-]*$/.test(t)) return true;
5120
+ if (/^[a-zA-Z0-9_-]{36,}$/.test(t)) return true;
5121
+ return false;
5122
+ }
5123
+
5124
+ function escapeSelectorClassTokens(sel) {
5125
+ if (!sel || typeof sel !== 'string') return '';
5126
+ return sel.replace(/.((?:\\.|[^s>+~:#.])+)/g, function(_m, cls) {
5127
+ if (!cls) return _m;
5128
+ // Already escaped token; keep it as-is.
5129
+ if (cls.indexOf('\\\\') >= 0) return '.' + cls;
5130
+ return '.' + cssEscapeIdent(cls);
5131
+ });
5132
+ }
5133
+
5076
5134
  function buildSelector(el) {
5077
5135
  if (!el) return '';
5078
5136
  var doc = el.ownerDocument || document;
@@ -5084,16 +5142,16 @@ function buildSelector(el) {
5084
5142
  if (doc.querySelectorAll(attrSel).length === 1) return attrSel;
5085
5143
  } catch(_) {}
5086
5144
  }
5087
- if (el.id) return '#' + el.id;
5145
+ if (el.id) return '#' + cssEscapeIdent(el.id);
5088
5146
  var parts = [], node = el, depth = 0;
5089
5147
  while (node && node.nodeType === 1 && depth < 5) {
5090
- if (node.id) { parts.unshift('#' + node.id); break; }
5148
+ if (node.id) { parts.unshift('#' + cssEscapeIdent(node.id)); break; }
5091
5149
  var p = node.tagName.toLowerCase();
5092
5150
  if (node.classList && node.classList.length) {
5093
5151
  var clsArr = Array.from(node.classList).filter(function(c) {
5094
- return c.indexOf('vve-') !== 0;
5152
+ return c.indexOf('vve-') !== 0 && !isGeneratedClassToken(c);
5095
5153
  });
5096
- if (clsArr.length) p += '.' + clsArr.slice(0, 2).join('.');
5154
+ if (clsArr.length) p += '.' + clsArr.slice(0, 2).map(function(c) { return cssEscapeIdent(c); }).join('.');
5097
5155
  }
5098
5156
  var idx = 1, sib = node.previousElementSibling;
5099
5157
  while (sib) { if (sib.tagName === node.tagName) idx++; sib = sib.previousElementSibling; }
@@ -5105,17 +5163,32 @@ function buildSelector(el) {
5105
5163
  return parts.join(' > ');
5106
5164
  }
5107
5165
 
5166
+ function stripGeneratedSelectorClassTokens(sel) {
5167
+ if (!sel || typeof sel !== 'string') return '';
5168
+ var s = sel.replace(/.((?:\\.|[^s>+~:#.])+)/g, function(_m, cls) {
5169
+ var raw = unescapeCssToken(cls);
5170
+ if (isGeneratedClassToken(raw)) return '';
5171
+ return '.' + cssEscapeIdent(raw);
5172
+ });
5173
+ return s
5174
+ .replace(/.{2,}/g, '.')
5175
+ .replace(/s{2,}/g, ' ')
5176
+ .replace(/s*>s*>/g, ' > ')
5177
+ .trim();
5178
+ }
5179
+
5108
5180
  /**
5109
5181
  * Strip editor-only .vve-* class tokens from a selector string (fixes DB rows saved while an element was selected).
5110
5182
  */
5111
5183
  function sanitizeSelectorForMatch(sel) {
5112
5184
  if (!sel || typeof sel !== 'string') return '';
5113
5185
  var s0 = sel.replace(/.vve-[a-zA-Z0-9_-]+/gi, '');
5186
+ s0 = stripGeneratedSelectorClassTokens(s0);
5114
5187
  var parts = s0.split(/s*>s*/).map(function(seg) {
5115
5188
  var t = seg.replace(/.+/g, '.').replace(/.$/, '');
5116
5189
  return t.trim();
5117
5190
  });
5118
- return parts.filter(Boolean).join(' > ');
5191
+ return escapeSelectorClassTokens(parts.filter(Boolean).join(' > '));
5119
5192
  }
5120
5193
 
5121
5194
  /** Drop the rightmost :nth-of-type(n) (hydration / layout often shifts sibling indices). */
@@ -5159,11 +5232,12 @@ function querySelectorResolved(iframeDoc, selector) {
5159
5232
  return null;
5160
5233
  }
5161
5234
  var alt = sanitizeSelectorForMatch(selector);
5235
+ var escaped = escapeSelectorClassTokens(alt || selector);
5162
5236
  // Prefer sanitized + nth relax FIRST: raw selectors often still contain .vve-* from
5163
5237
  // save-time selection; those only match after clicking (we re-add vve-selected).
5164
- var el = walkRelax(alt || selector);
5238
+ var el = walkRelax(escaped || alt || selector);
5165
5239
  if (el) return el;
5166
- if (alt !== selector) {
5240
+ if (alt !== selector || escaped !== selector) {
5167
5241
  el = walkRelax(selector);
5168
5242
  if (el) return el;
5169
5243
  }
@@ -5830,6 +5904,12 @@ window.addEventListener('load', function() {
5830
5904
  if (!d || d.channel !== 'vvveb-proxy-url' || d.type !== 'editor-url-changed') return;
5831
5905
  if (!iframe || !iframe.contentWindow || ev.source !== iframe.contentWindow) return;
5832
5906
  var payload = d.payload || {};
5907
+ try {
5908
+ console.info('[V2 iframe-url bridge]', {
5909
+ url: payload.url || '',
5910
+ passwordPresent: !!payload.password,
5911
+ });
5912
+ } catch(_) {}
5833
5913
  emitEditorUrlChangedPayload({
5834
5914
  url: payload.url || undefined,
5835
5915
  password: payload.password || undefined,
@@ -6119,8 +6199,6 @@ function createVisualEditorMiddleware(options) {
6119
6199
  extraTrackingMarkersForRequest
6120
6200
  );
6121
6201
  const strictFreezeParam = (url.searchParams.get("strictObserverFreeze") || "").toLowerCase();
6122
- const proxyParam = (url.searchParams.get("proxy") || "").toLowerCase();
6123
- const useScraperProxy = proxyParam === "1" || proxyParam === "true" || proxyParam === "yes";
6124
6202
  const strictObserverFreezeForRequest = strictFreezeParam === "1" || strictFreezeParam === "true" || strictFreezeParam === "yes" ? true : strictFreezeParam === "0" || strictFreezeParam === "false" || strictFreezeParam === "no" ? false : strictObserverFreeze;
6125
6203
  if (!targetUrl) {
6126
6204
  res.statusCode = 400;
@@ -6130,40 +6208,7 @@ function createVisualEditorMiddleware(options) {
6130
6208
  const parsed = new URL(targetUrl);
6131
6209
  const origin = parsed.origin;
6132
6210
  const method = (req.method || "GET").toUpperCase();
6133
- const scraperProxyClient = useScraperProxy ? await getScraperProxyClient() : null;
6134
- if (useScraperProxy && !scraperProxyClient) {
6135
- res.statusCode = 500;
6136
- res.setHeader("Content-Type", "application/json");
6137
- res.end(
6138
- JSON.stringify({
6139
- error: "ScraperAPI proxy is not configured. Set SCRAPERAPI_PROXY_PASSWORD or SCRAPERAPI_API_KEY."
6140
- })
6141
- );
6142
- return;
6143
- }
6144
6211
  const directFetch = (input, init = {}) => fetch(input, init);
6145
- const scraperFetch = (input, init = {}) => {
6146
- if (!SCRAPER_PROXY_PASSWORD) return directFetch(input, init);
6147
- if (!useScraperProxy || !scraperProxyClient) {
6148
- const scraperUrl = new URL(SCRAPER_API_ENDPOINT);
6149
- scraperUrl.searchParams.set("api_key", SCRAPER_PROXY_PASSWORD);
6150
- scraperUrl.searchParams.set("url", input);
6151
- return fetch(scraperUrl.toString(), init);
6152
- }
6153
- return scraperProxyClient.fetchFn(input, {
6154
- ...init,
6155
- dispatcher: scraperProxyClient.dispatcher
6156
- });
6157
- };
6158
- const shouldFallbackFromScraper = async (resp) => {
6159
- try {
6160
- if (resp.ok) return false;
6161
- const text = await resp.clone().text();
6162
- return SCRAPER_BILLING_ERROR_RE.test(String(text || ""));
6163
- } catch (_) {
6164
- return false;
6165
- }
6166
- };
6167
6212
  const workerRawFetch = (input, init = {}) => {
6168
6213
  const workerUrl = new URL("/api/conversion-proxy", conversionProxyBaseUrlForRequest);
6169
6214
  workerUrl.searchParams.set("url", input);
@@ -6196,17 +6241,6 @@ function createVisualEditorMiddleware(options) {
6196
6241
  }
6197
6242
  return workerRawFetch(input, init);
6198
6243
  }
6199
- const primary = await scraperFetch(input, init);
6200
- if (!await shouldFallbackFromScraper(primary)) {
6201
- return primary;
6202
- }
6203
- try {
6204
- console.warn("[conversion-proxy] ScraperAPI billing/quota detected; falling back to direct fetch", {
6205
- url: input,
6206
- mode: useScraperProxy ? "proxy" : "simple"
6207
- });
6208
- } catch (_) {
6209
- }
6210
6244
  return directFetch(input, init);
6211
6245
  };
6212
6246
  const headers = {
@@ -6278,16 +6312,8 @@ function createVisualEditorMiddleware(options) {
6278
6312
  res.end(
6279
6313
  JSON.stringify({
6280
6314
  error: aborted ? `Upstream request timed out after ${upstreamTimeoutMs / 1e3}s` : fetchErr?.message || "Upstream fetch failed",
6281
- phase: useScraperProxy ? "scraperapi-proxy-fetch" : "scraperapi-simple-fetch",
6282
- fetchMode: useScraperProxy ? "proxy" : "simple",
6283
- scraperapi: {
6284
- host: SCRAPER_PROXY_HOST,
6285
- port: SCRAPER_PROXY_PORT,
6286
- username: SCRAPER_PROXY_USERNAME,
6287
- endpoint: SCRAPER_API_ENDPOINT,
6288
- hasProxyPassword: Boolean(SCRAPER_PROXY_PASSWORD),
6289
- requestTlsRejectUnauthorized: SCRAPER_REQUEST_TLS_REJECT_UNAUTHORIZED
6290
- },
6315
+ phase: conversionProxyBaseUrlForRequest ? "worker-raw-fetch" : "direct-upstream-fetch",
6316
+ fetchMode: conversionProxyBaseUrlForRequest ? "worker" : "direct",
6291
6317
  cause: fetchCause && typeof fetchCause === "object" ? {
6292
6318
  name: fetchCause.name,
6293
6319
  code: fetchCause.code,
@@ -6473,8 +6499,51 @@ try{
6473
6499
  }
6474
6500
  }
6475
6501
  }catch(_){}
6476
- function getEditorUrlPayload(){try{var u=new URL(window.location.href);var nested=u.searchParams.get("url");return{url:nested||u.toString(),password:u.searchParams.get("password")||undefined};}catch(_){return null;}}
6477
- function notifyEditorUrlChanged(){try{var payload=getEditorUrlPayload();if(!payload)return;if(window.parent){window.parent.postMessage({channel:PARENT_URL_CHANNEL,type:"editor-url-changed",payload:payload},"*");}}catch(_){}}
6502
+ function getEditorUrlPayload(){
6503
+ try{
6504
+ var u=new URL(window.location.href);
6505
+ var nested=u.searchParams.get("url");
6506
+ if(nested){
6507
+ return {url:nested,password:u.searchParams.get("password")||undefined};
6508
+ }
6509
+ var p=u.pathname||"";
6510
+ var isProxyPath=p==="/api/conversion-proxy"||p.indexOf("/api/conversion-proxy/")===0||p.indexOf("api/conversion-proxy")!==-1;
6511
+ if(isProxyPath){
6512
+ var recoveredBase=new URL(TARGET_PAGE_URL,window.location.href);
6513
+ var qp=new URLSearchParams(u.search||"");
6514
+ qp.delete("url");
6515
+ qp.delete("password");
6516
+ qp.delete("conversionProxyBaseUrl");
6517
+ qp.delete("trackingMarkers");
6518
+ qp.delete("strictObserverFreeze");
6519
+ qp.delete("proxy");
6520
+ qp.delete("raw");
6521
+ recoveredBase.search="";
6522
+ qp.forEach(function(v,k){recoveredBase.searchParams.set(k,v);});
6523
+ recoveredBase.hash=u.hash||"";
6524
+ return {url:recoveredBase.toString(),password:u.searchParams.get("password")||undefined};
6525
+ }
6526
+ return {url:u.toString(),password:u.searchParams.get("password")||undefined};
6527
+ }catch(_){
6528
+ return null;
6529
+ }
6530
+ }
6531
+ function notifyEditorUrlChanged(){
6532
+ try{
6533
+ var payload=getEditorUrlPayload();
6534
+ if(!payload) return;
6535
+ try{
6536
+ console.info("[conversion-proxy] iframe url rendered",{
6537
+ current:window.location.href,
6538
+ target:payload.url||"",
6539
+ passwordPresent:!!payload.password
6540
+ });
6541
+ }catch(_){}
6542
+ if(window.parent){
6543
+ window.parent.postMessage({channel:PARENT_URL_CHANNEL,type:"editor-url-changed",payload:payload},"*");
6544
+ }
6545
+ }catch(_){}
6546
+ }
6478
6547
  try{notifyEditorUrlChanged();}catch(_){}
6479
6548
  try{if(window.history&&typeof window.history.pushState==="function"){var nativePushState=window.history.pushState;window.history.pushState=function(){var ret=nativePushState.apply(window.history,arguments);setTimeout(notifyEditorUrlChanged,0);return ret;};}}catch(_){}
6480
6549
  try{if(window.history&&typeof window.history.replaceState==="function"){var nativeReplaceState=window.history.replaceState;window.history.replaceState=function(){var ret=nativeReplaceState.apply(window.history,arguments);setTimeout(notifyEditorUrlChanged,0);return ret;};}}catch(_){}
@@ -6482,7 +6551,29 @@ try{window.addEventListener("popstate",notifyEditorUrlChanged,true);}catch(_){}
6482
6551
  try{window.addEventListener("hashchange",notifyEditorUrlChanged,true);}catch(_){}
6483
6552
  function isSkippable(raw){if(!raw||typeof raw!=="string")return true;return raw.startsWith("data:")||raw.startsWith("blob:")||raw.startsWith("javascript:")||raw.startsWith("#");}
6484
6553
  function toAbsolute(raw){if(isSkippable(raw))return raw;try{var base=raw.startsWith("/")||raw.startsWith("//")?TARGET_ORIGIN:TARGET_PAGE_URL;return new URL(raw,base).toString();}catch(_){return raw;}}
6485
- function toProxy(raw){if(isSkippable(raw))return null;var abs=toAbsolute(raw);if(!abs||typeof abs!=="string")return null;try{var parsed=new URL(abs);if(parsed.origin!==TARGET_ORIGIN)return null;var root="/api/conversion-proxy";return root+"?password="+encodeURIComponent(PROXY_PASSWORD||"")+"&url="+encodeURIComponent(parsed.toString());}catch(_){return null;}}
6554
+ function toProxy(raw){
6555
+ if(isSkippable(raw))return null;
6556
+ var abs=toAbsolute(raw);
6557
+ if(!abs||typeof abs!=="string")return null;
6558
+ try{
6559
+ var parsed=new URL(abs);
6560
+ // If input is already a proxy URL, unwrap to its target url first
6561
+ // to avoid nested /api/conversion-proxy?url=/api/conversion-proxy?... chains.
6562
+ var p=(parsed.pathname||"");
6563
+ var isProxyPath=p==="/api/conversion-proxy"||p.indexOf("/api/conversion-proxy/")===0||p.indexOf("api/conversion-proxy")!==-1;
6564
+ if(isProxyPath){
6565
+ var nested=parsed.searchParams.get("url")||"";
6566
+ if(nested){
6567
+ try{parsed=new URL(nested);}catch(_){}
6568
+ }
6569
+ }
6570
+ if(parsed.origin!==TARGET_ORIGIN)return null;
6571
+ var root="/api/conversion-proxy";
6572
+ return root+"?password="+encodeURIComponent(PROXY_PASSWORD||"")+"&url="+encodeURIComponent(parsed.toString());
6573
+ }catch(_){
6574
+ return null;
6575
+ }
6576
+ }
6486
6577
  var nativeAssign=window.location.assign?window.location.assign.bind(window.location):null;
6487
6578
  var nativeReplace=window.location.replace?window.location.replace.bind(window.location):null;
6488
6579
  function safeNavigate(raw,mode){var abs=toAbsolute(raw);var prox=toProxy(raw);if(!prox){try{console.warn("[conversion-proxy] redirect blocked",{mode:mode,requested:raw,resolved:abs,origin:TARGET_ORIGIN});}catch(_){}return false;}try{console.info("[conversion-proxy] redirect intercepted",{mode:mode,requested:raw,resolved:abs,proxied:prox});if(mode==="replace"&&nativeReplace){nativeReplace(prox);return true;}if(nativeAssign){nativeAssign(prox);return true;}window.location.href=prox;return true;}catch(err){try{console.warn("[conversion-proxy] redirect interception failed",{mode:mode,requested:raw,resolved:abs,proxied:prox,error:err&&err.message?err.message:String(err)});}catch(_){}return false;}}
@@ -6556,6 +6647,15 @@ function toProxyNetworkUrl(raw){
6556
6647
  try{
6557
6648
  var base=raw.startsWith("/")?TARGET_ORIGIN:TARGET_PAGE_URL;
6558
6649
  var abs=new URL(raw,base);
6650
+ // Unwrap already-proxied URLs first to avoid nested proxy urls.
6651
+ var p0=abs.pathname||"";
6652
+ var isProxyPath0=p0==="/api/conversion-proxy"||p0.indexOf("/api/conversion-proxy/")===0||p0.indexOf("api/conversion-proxy")!==-1;
6653
+ if(isProxyPath0){
6654
+ var nested0=abs.searchParams.get("url")||"";
6655
+ if(nested0){
6656
+ try{abs=new URL(nested0);}catch(_){}
6657
+ }
6658
+ }
6559
6659
  // Some embedded scripts build absolute requests against editor origin
6560
6660
  // (e.g. https://localhost:4001/api/unstable/graphql.json). Remap those
6561
6661
  // paths to target origin first, then proxy as usual.
package/dist/vite.js CHANGED
@@ -3,20 +3,6 @@ import path from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
 
5
5
  // src/visualEditorProxyPlugin.ts
6
- var SCRAPER_PROXY_HOST = process.env.SCRAPERAPI_PROXY_HOST || "proxy-server.scraperapi.com";
7
- var SCRAPER_PROXY_PORT = Number(process.env.SCRAPERAPI_PROXY_PORT || "8001");
8
- var SCRAPER_PROXY_USERNAME_BASE = process.env.SCRAPERAPI_PROXY_USERNAME_BASE || "scraperapi";
9
- var SCRAPER_PROXY_USERNAME_PARAMS = process.env.SCRAPERAPI_PROXY_USERNAME_PARAMS || "render=true.wait_for_selector=body.follow_redirect=false.keep_headers=true";
10
- var SCRAPER_PROXY_USERNAME = process.env.SCRAPERAPI_PROXY_USERNAME || [
11
- SCRAPER_PROXY_USERNAME_BASE,
12
- SCRAPER_PROXY_USERNAME_PARAMS
13
- ].filter(Boolean).join(".");
14
- var SCRAPER_PROXY_PASSWORD = process.env.SCRAPERAPI_PROXY_PASSWORD || process.env.SCRAPERAPI_API_KEY;
15
- var SCRAPER_API_ENDPOINT = process.env.SCRAPERAPI_ENDPOINT || "https://api.scraperapi.com/";
16
- var SCRAPER_REQUEST_TLS_REJECT_UNAUTHORIZED_RAW = process.env.SCRAPERAPI_REQUEST_TLS_REJECT_UNAUTHORIZED || "false";
17
- var SCRAPER_REQUEST_TLS_REJECT_UNAUTHORIZED = !["0", "false", "no"].includes(
18
- SCRAPER_REQUEST_TLS_REJECT_UNAUTHORIZED_RAW.toLowerCase()
19
- );
20
6
  var DEFAULT_TRACKING_MARKERS = [
21
7
  "snowplow",
22
8
  "taboola",
@@ -114,30 +100,6 @@ function stripTrackingScriptsFromScrapedHtml(html, markers) {
114
100
  }
115
101
  return out;
116
102
  }
117
- var SCRAPER_BILLING_ERROR_RE = /exhausted the api credits|upgrade your subscription|enable overages|dashboard\.scraperapi\.com\/billing|insufficient credits|billing/i;
118
- var scraperProxyClientPromise = null;
119
- async function getScraperProxyClient() {
120
- if (scraperProxyClientPromise) return scraperProxyClientPromise;
121
- scraperProxyClientPromise = (async () => {
122
- if (!SCRAPER_PROXY_PASSWORD) return null;
123
- try {
124
- const undici = await import('undici');
125
- const proxyUrl = "http://" + encodeURIComponent(SCRAPER_PROXY_USERNAME) + ":" + encodeURIComponent(SCRAPER_PROXY_PASSWORD) + "@" + SCRAPER_PROXY_HOST + ":" + String(SCRAPER_PROXY_PORT);
126
- return {
127
- dispatcher: new undici.ProxyAgent({
128
- uri: proxyUrl,
129
- requestTls: {
130
- rejectUnauthorized: SCRAPER_REQUEST_TLS_REJECT_UNAUTHORIZED
131
- }
132
- }),
133
- fetchFn: undici.fetch
134
- };
135
- } catch (_) {
136
- return null;
137
- }
138
- })();
139
- return scraperProxyClientPromise;
140
- }
141
103
  var iframeAlwaysShowCss = `<style id="__ce_force_show">
142
104
 
143
105
  </style>`;
@@ -3275,6 +3237,35 @@ function parseEditorUrlPayload(rawUrl) {
3275
3237
  password: nestedPassword || undefined,
3276
3238
  };
3277
3239
  }
3240
+ var path = parsed.pathname || '';
3241
+ var isProxyPath =
3242
+ path === '/api/conversion-proxy' ||
3243
+ path.indexOf('/api/conversion-proxy/') === 0 ||
3244
+ path.indexOf('api/conversion-proxy') !== -1;
3245
+ if (isProxyPath) {
3246
+ var fallbackUrl = (experimentData && experimentData.pageUrl) ? String(experimentData.pageUrl) : '';
3247
+ if (fallbackUrl) {
3248
+ var recovered = new URL(fallbackUrl, window.location.href);
3249
+ var qp = new URLSearchParams(parsed.search || '');
3250
+ qp.delete('url');
3251
+ qp.delete('password');
3252
+ qp.delete('conversionProxyBaseUrl');
3253
+ qp.delete('trackingMarkers');
3254
+ qp.delete('strictObserverFreeze');
3255
+ qp.delete('proxy');
3256
+ qp.delete('raw');
3257
+ recovered.search = '';
3258
+ qp.forEach(function(v, k) { recovered.searchParams.set(k, v); });
3259
+ recovered.hash = parsed.hash || '';
3260
+ return {
3261
+ url: recovered.toString(),
3262
+ password:
3263
+ (experimentData && experimentData.editorPassword)
3264
+ ? String(experimentData.editorPassword)
3265
+ : undefined,
3266
+ };
3267
+ }
3268
+ }
3278
3269
  return {
3279
3270
  url: parsed.toString(),
3280
3271
  password:
@@ -3293,6 +3284,12 @@ function emitEditorUrlChangedPayload(payload) {
3293
3284
  var key = String(payload.url || '') + '|' + String(payload.password || '');
3294
3285
  if (key === lastEditorUrlPayloadKey) return;
3295
3286
  lastEditorUrlPayloadKey = key;
3287
+ try {
3288
+ console.info('[V2 iframe-url]', {
3289
+ url: payload.url || '',
3290
+ passwordPresent: !!payload.password,
3291
+ });
3292
+ } catch(_) {}
3296
3293
  send('editor-url-changed', payload);
3297
3294
  }
3298
3295
  function emitEditorUrlChanged(rawUrl) {
@@ -5065,6 +5062,67 @@ function stripDataVveInstanceSubtree(root) {
5065
5062
  }
5066
5063
  }
5067
5064
 
5065
+ function cssEscapeIdent(raw) {
5066
+ var s = raw == null ? '' : String(raw);
5067
+ if (!s) return '';
5068
+ try {
5069
+ if (typeof CSS !== 'undefined' && CSS && typeof CSS.escape === 'function') {
5070
+ return CSS.escape(s);
5071
+ }
5072
+ } catch(_) {}
5073
+ // Fallback escape for identifiers when CSS.escape is unavailable.
5074
+ return s.replace(/[^a-zA-Z0-9_-]/g, function(ch) { return '\\\\' + ch; });
5075
+ }
5076
+
5077
+ function unescapeCssToken(token) {
5078
+ if (!token) return '';
5079
+ var s = String(token);
5080
+ var out = '';
5081
+ for (var i = 0; i < s.length; i++) {
5082
+ var ch = s.charAt(i);
5083
+ if (ch === '\\\\' && i + 1 < s.length) {
5084
+ out += s.charAt(i + 1);
5085
+ i += 1;
5086
+ continue;
5087
+ }
5088
+ out += ch;
5089
+ }
5090
+ return out;
5091
+ }
5092
+
5093
+ function isGeneratedClassToken(token) {
5094
+ var t = token == null ? '' : String(token).trim();
5095
+ if (!t) return true;
5096
+ if (t.indexOf('vve-') === 0) return true;
5097
+ // Tailwind arbitrary values / variant-heavy utilities are often unstable in selectors.
5098
+ if (
5099
+ t.indexOf('[') !== -1 ||
5100
+ t.indexOf(']') !== -1 ||
5101
+ t.indexOf('(') !== -1 ||
5102
+ t.indexOf(')') !== -1 ||
5103
+ t.indexOf('{') !== -1 ||
5104
+ t.indexOf('}') !== -1 ||
5105
+ t.indexOf(':') !== -1
5106
+ ) return true;
5107
+ // CSS-in-JS / runtime hash prefixes.
5108
+ if (/^(css|jsx|sc|emotion|styled|chakra|mantine|mui|ant)-/i.test(t)) return true;
5109
+ // Classnames with long hashy suffixes (framework/runtime generated).
5110
+ if (/[a-f0-9]{8,}/i.test(t)) return true;
5111
+ if (/^[a-zA-Z_-]+[0-9]{4,}[a-zA-Z0-9_-]*$/.test(t)) return true;
5112
+ if (/^[a-zA-Z0-9_-]{36,}$/.test(t)) return true;
5113
+ return false;
5114
+ }
5115
+
5116
+ function escapeSelectorClassTokens(sel) {
5117
+ if (!sel || typeof sel !== 'string') return '';
5118
+ return sel.replace(/.((?:\\.|[^s>+~:#.])+)/g, function(_m, cls) {
5119
+ if (!cls) return _m;
5120
+ // Already escaped token; keep it as-is.
5121
+ if (cls.indexOf('\\\\') >= 0) return '.' + cls;
5122
+ return '.' + cssEscapeIdent(cls);
5123
+ });
5124
+ }
5125
+
5068
5126
  function buildSelector(el) {
5069
5127
  if (!el) return '';
5070
5128
  var doc = el.ownerDocument || document;
@@ -5076,16 +5134,16 @@ function buildSelector(el) {
5076
5134
  if (doc.querySelectorAll(attrSel).length === 1) return attrSel;
5077
5135
  } catch(_) {}
5078
5136
  }
5079
- if (el.id) return '#' + el.id;
5137
+ if (el.id) return '#' + cssEscapeIdent(el.id);
5080
5138
  var parts = [], node = el, depth = 0;
5081
5139
  while (node && node.nodeType === 1 && depth < 5) {
5082
- if (node.id) { parts.unshift('#' + node.id); break; }
5140
+ if (node.id) { parts.unshift('#' + cssEscapeIdent(node.id)); break; }
5083
5141
  var p = node.tagName.toLowerCase();
5084
5142
  if (node.classList && node.classList.length) {
5085
5143
  var clsArr = Array.from(node.classList).filter(function(c) {
5086
- return c.indexOf('vve-') !== 0;
5144
+ return c.indexOf('vve-') !== 0 && !isGeneratedClassToken(c);
5087
5145
  });
5088
- if (clsArr.length) p += '.' + clsArr.slice(0, 2).join('.');
5146
+ if (clsArr.length) p += '.' + clsArr.slice(0, 2).map(function(c) { return cssEscapeIdent(c); }).join('.');
5089
5147
  }
5090
5148
  var idx = 1, sib = node.previousElementSibling;
5091
5149
  while (sib) { if (sib.tagName === node.tagName) idx++; sib = sib.previousElementSibling; }
@@ -5097,17 +5155,32 @@ function buildSelector(el) {
5097
5155
  return parts.join(' > ');
5098
5156
  }
5099
5157
 
5158
+ function stripGeneratedSelectorClassTokens(sel) {
5159
+ if (!sel || typeof sel !== 'string') return '';
5160
+ var s = sel.replace(/.((?:\\.|[^s>+~:#.])+)/g, function(_m, cls) {
5161
+ var raw = unescapeCssToken(cls);
5162
+ if (isGeneratedClassToken(raw)) return '';
5163
+ return '.' + cssEscapeIdent(raw);
5164
+ });
5165
+ return s
5166
+ .replace(/.{2,}/g, '.')
5167
+ .replace(/s{2,}/g, ' ')
5168
+ .replace(/s*>s*>/g, ' > ')
5169
+ .trim();
5170
+ }
5171
+
5100
5172
  /**
5101
5173
  * Strip editor-only .vve-* class tokens from a selector string (fixes DB rows saved while an element was selected).
5102
5174
  */
5103
5175
  function sanitizeSelectorForMatch(sel) {
5104
5176
  if (!sel || typeof sel !== 'string') return '';
5105
5177
  var s0 = sel.replace(/.vve-[a-zA-Z0-9_-]+/gi, '');
5178
+ s0 = stripGeneratedSelectorClassTokens(s0);
5106
5179
  var parts = s0.split(/s*>s*/).map(function(seg) {
5107
5180
  var t = seg.replace(/.+/g, '.').replace(/.$/, '');
5108
5181
  return t.trim();
5109
5182
  });
5110
- return parts.filter(Boolean).join(' > ');
5183
+ return escapeSelectorClassTokens(parts.filter(Boolean).join(' > '));
5111
5184
  }
5112
5185
 
5113
5186
  /** Drop the rightmost :nth-of-type(n) (hydration / layout often shifts sibling indices). */
@@ -5151,11 +5224,12 @@ function querySelectorResolved(iframeDoc, selector) {
5151
5224
  return null;
5152
5225
  }
5153
5226
  var alt = sanitizeSelectorForMatch(selector);
5227
+ var escaped = escapeSelectorClassTokens(alt || selector);
5154
5228
  // Prefer sanitized + nth relax FIRST: raw selectors often still contain .vve-* from
5155
5229
  // save-time selection; those only match after clicking (we re-add vve-selected).
5156
- var el = walkRelax(alt || selector);
5230
+ var el = walkRelax(escaped || alt || selector);
5157
5231
  if (el) return el;
5158
- if (alt !== selector) {
5232
+ if (alt !== selector || escaped !== selector) {
5159
5233
  el = walkRelax(selector);
5160
5234
  if (el) return el;
5161
5235
  }
@@ -5822,6 +5896,12 @@ window.addEventListener('load', function() {
5822
5896
  if (!d || d.channel !== 'vvveb-proxy-url' || d.type !== 'editor-url-changed') return;
5823
5897
  if (!iframe || !iframe.contentWindow || ev.source !== iframe.contentWindow) return;
5824
5898
  var payload = d.payload || {};
5899
+ try {
5900
+ console.info('[V2 iframe-url bridge]', {
5901
+ url: payload.url || '',
5902
+ passwordPresent: !!payload.password,
5903
+ });
5904
+ } catch(_) {}
5825
5905
  emitEditorUrlChangedPayload({
5826
5906
  url: payload.url || undefined,
5827
5907
  password: payload.password || undefined,
@@ -6111,8 +6191,6 @@ function createVisualEditorMiddleware(options) {
6111
6191
  extraTrackingMarkersForRequest
6112
6192
  );
6113
6193
  const strictFreezeParam = (url.searchParams.get("strictObserverFreeze") || "").toLowerCase();
6114
- const proxyParam = (url.searchParams.get("proxy") || "").toLowerCase();
6115
- const useScraperProxy = proxyParam === "1" || proxyParam === "true" || proxyParam === "yes";
6116
6194
  const strictObserverFreezeForRequest = strictFreezeParam === "1" || strictFreezeParam === "true" || strictFreezeParam === "yes" ? true : strictFreezeParam === "0" || strictFreezeParam === "false" || strictFreezeParam === "no" ? false : strictObserverFreeze;
6117
6195
  if (!targetUrl) {
6118
6196
  res.statusCode = 400;
@@ -6122,40 +6200,7 @@ function createVisualEditorMiddleware(options) {
6122
6200
  const parsed = new URL(targetUrl);
6123
6201
  const origin = parsed.origin;
6124
6202
  const method = (req.method || "GET").toUpperCase();
6125
- const scraperProxyClient = useScraperProxy ? await getScraperProxyClient() : null;
6126
- if (useScraperProxy && !scraperProxyClient) {
6127
- res.statusCode = 500;
6128
- res.setHeader("Content-Type", "application/json");
6129
- res.end(
6130
- JSON.stringify({
6131
- error: "ScraperAPI proxy is not configured. Set SCRAPERAPI_PROXY_PASSWORD or SCRAPERAPI_API_KEY."
6132
- })
6133
- );
6134
- return;
6135
- }
6136
6203
  const directFetch = (input, init = {}) => fetch(input, init);
6137
- const scraperFetch = (input, init = {}) => {
6138
- if (!SCRAPER_PROXY_PASSWORD) return directFetch(input, init);
6139
- if (!useScraperProxy || !scraperProxyClient) {
6140
- const scraperUrl = new URL(SCRAPER_API_ENDPOINT);
6141
- scraperUrl.searchParams.set("api_key", SCRAPER_PROXY_PASSWORD);
6142
- scraperUrl.searchParams.set("url", input);
6143
- return fetch(scraperUrl.toString(), init);
6144
- }
6145
- return scraperProxyClient.fetchFn(input, {
6146
- ...init,
6147
- dispatcher: scraperProxyClient.dispatcher
6148
- });
6149
- };
6150
- const shouldFallbackFromScraper = async (resp) => {
6151
- try {
6152
- if (resp.ok) return false;
6153
- const text = await resp.clone().text();
6154
- return SCRAPER_BILLING_ERROR_RE.test(String(text || ""));
6155
- } catch (_) {
6156
- return false;
6157
- }
6158
- };
6159
6204
  const workerRawFetch = (input, init = {}) => {
6160
6205
  const workerUrl = new URL("/api/conversion-proxy", conversionProxyBaseUrlForRequest);
6161
6206
  workerUrl.searchParams.set("url", input);
@@ -6188,17 +6233,6 @@ function createVisualEditorMiddleware(options) {
6188
6233
  }
6189
6234
  return workerRawFetch(input, init);
6190
6235
  }
6191
- const primary = await scraperFetch(input, init);
6192
- if (!await shouldFallbackFromScraper(primary)) {
6193
- return primary;
6194
- }
6195
- try {
6196
- console.warn("[conversion-proxy] ScraperAPI billing/quota detected; falling back to direct fetch", {
6197
- url: input,
6198
- mode: useScraperProxy ? "proxy" : "simple"
6199
- });
6200
- } catch (_) {
6201
- }
6202
6236
  return directFetch(input, init);
6203
6237
  };
6204
6238
  const headers = {
@@ -6270,16 +6304,8 @@ function createVisualEditorMiddleware(options) {
6270
6304
  res.end(
6271
6305
  JSON.stringify({
6272
6306
  error: aborted ? `Upstream request timed out after ${upstreamTimeoutMs / 1e3}s` : fetchErr?.message || "Upstream fetch failed",
6273
- phase: useScraperProxy ? "scraperapi-proxy-fetch" : "scraperapi-simple-fetch",
6274
- fetchMode: useScraperProxy ? "proxy" : "simple",
6275
- scraperapi: {
6276
- host: SCRAPER_PROXY_HOST,
6277
- port: SCRAPER_PROXY_PORT,
6278
- username: SCRAPER_PROXY_USERNAME,
6279
- endpoint: SCRAPER_API_ENDPOINT,
6280
- hasProxyPassword: Boolean(SCRAPER_PROXY_PASSWORD),
6281
- requestTlsRejectUnauthorized: SCRAPER_REQUEST_TLS_REJECT_UNAUTHORIZED
6282
- },
6307
+ phase: conversionProxyBaseUrlForRequest ? "worker-raw-fetch" : "direct-upstream-fetch",
6308
+ fetchMode: conversionProxyBaseUrlForRequest ? "worker" : "direct",
6283
6309
  cause: fetchCause && typeof fetchCause === "object" ? {
6284
6310
  name: fetchCause.name,
6285
6311
  code: fetchCause.code,
@@ -6465,8 +6491,51 @@ try{
6465
6491
  }
6466
6492
  }
6467
6493
  }catch(_){}
6468
- function getEditorUrlPayload(){try{var u=new URL(window.location.href);var nested=u.searchParams.get("url");return{url:nested||u.toString(),password:u.searchParams.get("password")||undefined};}catch(_){return null;}}
6469
- function notifyEditorUrlChanged(){try{var payload=getEditorUrlPayload();if(!payload)return;if(window.parent){window.parent.postMessage({channel:PARENT_URL_CHANNEL,type:"editor-url-changed",payload:payload},"*");}}catch(_){}}
6494
+ function getEditorUrlPayload(){
6495
+ try{
6496
+ var u=new URL(window.location.href);
6497
+ var nested=u.searchParams.get("url");
6498
+ if(nested){
6499
+ return {url:nested,password:u.searchParams.get("password")||undefined};
6500
+ }
6501
+ var p=u.pathname||"";
6502
+ var isProxyPath=p==="/api/conversion-proxy"||p.indexOf("/api/conversion-proxy/")===0||p.indexOf("api/conversion-proxy")!==-1;
6503
+ if(isProxyPath){
6504
+ var recoveredBase=new URL(TARGET_PAGE_URL,window.location.href);
6505
+ var qp=new URLSearchParams(u.search||"");
6506
+ qp.delete("url");
6507
+ qp.delete("password");
6508
+ qp.delete("conversionProxyBaseUrl");
6509
+ qp.delete("trackingMarkers");
6510
+ qp.delete("strictObserverFreeze");
6511
+ qp.delete("proxy");
6512
+ qp.delete("raw");
6513
+ recoveredBase.search="";
6514
+ qp.forEach(function(v,k){recoveredBase.searchParams.set(k,v);});
6515
+ recoveredBase.hash=u.hash||"";
6516
+ return {url:recoveredBase.toString(),password:u.searchParams.get("password")||undefined};
6517
+ }
6518
+ return {url:u.toString(),password:u.searchParams.get("password")||undefined};
6519
+ }catch(_){
6520
+ return null;
6521
+ }
6522
+ }
6523
+ function notifyEditorUrlChanged(){
6524
+ try{
6525
+ var payload=getEditorUrlPayload();
6526
+ if(!payload) return;
6527
+ try{
6528
+ console.info("[conversion-proxy] iframe url rendered",{
6529
+ current:window.location.href,
6530
+ target:payload.url||"",
6531
+ passwordPresent:!!payload.password
6532
+ });
6533
+ }catch(_){}
6534
+ if(window.parent){
6535
+ window.parent.postMessage({channel:PARENT_URL_CHANNEL,type:"editor-url-changed",payload:payload},"*");
6536
+ }
6537
+ }catch(_){}
6538
+ }
6470
6539
  try{notifyEditorUrlChanged();}catch(_){}
6471
6540
  try{if(window.history&&typeof window.history.pushState==="function"){var nativePushState=window.history.pushState;window.history.pushState=function(){var ret=nativePushState.apply(window.history,arguments);setTimeout(notifyEditorUrlChanged,0);return ret;};}}catch(_){}
6472
6541
  try{if(window.history&&typeof window.history.replaceState==="function"){var nativeReplaceState=window.history.replaceState;window.history.replaceState=function(){var ret=nativeReplaceState.apply(window.history,arguments);setTimeout(notifyEditorUrlChanged,0);return ret;};}}catch(_){}
@@ -6474,7 +6543,29 @@ try{window.addEventListener("popstate",notifyEditorUrlChanged,true);}catch(_){}
6474
6543
  try{window.addEventListener("hashchange",notifyEditorUrlChanged,true);}catch(_){}
6475
6544
  function isSkippable(raw){if(!raw||typeof raw!=="string")return true;return raw.startsWith("data:")||raw.startsWith("blob:")||raw.startsWith("javascript:")||raw.startsWith("#");}
6476
6545
  function toAbsolute(raw){if(isSkippable(raw))return raw;try{var base=raw.startsWith("/")||raw.startsWith("//")?TARGET_ORIGIN:TARGET_PAGE_URL;return new URL(raw,base).toString();}catch(_){return raw;}}
6477
- function toProxy(raw){if(isSkippable(raw))return null;var abs=toAbsolute(raw);if(!abs||typeof abs!=="string")return null;try{var parsed=new URL(abs);if(parsed.origin!==TARGET_ORIGIN)return null;var root="/api/conversion-proxy";return root+"?password="+encodeURIComponent(PROXY_PASSWORD||"")+"&url="+encodeURIComponent(parsed.toString());}catch(_){return null;}}
6546
+ function toProxy(raw){
6547
+ if(isSkippable(raw))return null;
6548
+ var abs=toAbsolute(raw);
6549
+ if(!abs||typeof abs!=="string")return null;
6550
+ try{
6551
+ var parsed=new URL(abs);
6552
+ // If input is already a proxy URL, unwrap to its target url first
6553
+ // to avoid nested /api/conversion-proxy?url=/api/conversion-proxy?... chains.
6554
+ var p=(parsed.pathname||"");
6555
+ var isProxyPath=p==="/api/conversion-proxy"||p.indexOf("/api/conversion-proxy/")===0||p.indexOf("api/conversion-proxy")!==-1;
6556
+ if(isProxyPath){
6557
+ var nested=parsed.searchParams.get("url")||"";
6558
+ if(nested){
6559
+ try{parsed=new URL(nested);}catch(_){}
6560
+ }
6561
+ }
6562
+ if(parsed.origin!==TARGET_ORIGIN)return null;
6563
+ var root="/api/conversion-proxy";
6564
+ return root+"?password="+encodeURIComponent(PROXY_PASSWORD||"")+"&url="+encodeURIComponent(parsed.toString());
6565
+ }catch(_){
6566
+ return null;
6567
+ }
6568
+ }
6478
6569
  var nativeAssign=window.location.assign?window.location.assign.bind(window.location):null;
6479
6570
  var nativeReplace=window.location.replace?window.location.replace.bind(window.location):null;
6480
6571
  function safeNavigate(raw,mode){var abs=toAbsolute(raw);var prox=toProxy(raw);if(!prox){try{console.warn("[conversion-proxy] redirect blocked",{mode:mode,requested:raw,resolved:abs,origin:TARGET_ORIGIN});}catch(_){}return false;}try{console.info("[conversion-proxy] redirect intercepted",{mode:mode,requested:raw,resolved:abs,proxied:prox});if(mode==="replace"&&nativeReplace){nativeReplace(prox);return true;}if(nativeAssign){nativeAssign(prox);return true;}window.location.href=prox;return true;}catch(err){try{console.warn("[conversion-proxy] redirect interception failed",{mode:mode,requested:raw,resolved:abs,proxied:prox,error:err&&err.message?err.message:String(err)});}catch(_){}return false;}}
@@ -6548,6 +6639,15 @@ function toProxyNetworkUrl(raw){
6548
6639
  try{
6549
6640
  var base=raw.startsWith("/")?TARGET_ORIGIN:TARGET_PAGE_URL;
6550
6641
  var abs=new URL(raw,base);
6642
+ // Unwrap already-proxied URLs first to avoid nested proxy urls.
6643
+ var p0=abs.pathname||"";
6644
+ var isProxyPath0=p0==="/api/conversion-proxy"||p0.indexOf("/api/conversion-proxy/")===0||p0.indexOf("api/conversion-proxy")!==-1;
6645
+ if(isProxyPath0){
6646
+ var nested0=abs.searchParams.get("url")||"";
6647
+ if(nested0){
6648
+ try{abs=new URL(nested0);}catch(_){}
6649
+ }
6650
+ }
6551
6651
  // Some embedded scripts build absolute requests against editor origin
6552
6652
  // (e.g. https://localhost:4001/api/unstable/graphql.json). Remap those
6553
6653
  // paths to target origin first, then proxy as usual.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@accelerated-agency/visual-editor",
3
- "version": "0.4.6",
3
+ "version": "0.4.7",
4
4
  "private": false,
5
5
  "description": "Conversion visual editor as a reusable React package",
6
6
  "type": "module",