updated stuff
This commit is contained in:
@@ -103,6 +103,23 @@ export function LiveArtboard({
|
|||||||
// ── Handle messages from the renderer iframe ──────────────────────────────
|
// ── Handle messages from the renderer iframe ──────────────────────────────
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleMessage(event: MessageEvent) {
|
function handleMessage(event: MessageEvent) {
|
||||||
|
// INIT handshake: the iframe asks us "who am I?". We match by event.source
|
||||||
|
// (the iframe's window) to confirm the request came from OUR iframe, then
|
||||||
|
// reply with this artboard's ID. This is the cross-origin-safe replacement
|
||||||
|
// for the iframe element's name= attribute (which Chrome strips on cross-
|
||||||
|
// origin loads as a Spectre mitigation).
|
||||||
|
const data = event.data as { __om_init_request?: boolean } | null;
|
||||||
|
if (data && data.__om_init_request === true) {
|
||||||
|
const iframe = iframeRef.current;
|
||||||
|
if (iframe && iframe.contentWindow === event.source) {
|
||||||
|
iframe.contentWindow.postMessage(
|
||||||
|
{ __om_init_response: true, artboardId: id },
|
||||||
|
'*',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!isRendererEnvelope(event.data)) return;
|
if (!isRendererEnvelope(event.data)) return;
|
||||||
if (event.data.artboardId !== id) return;
|
if (event.data.artboardId !== id) return;
|
||||||
|
|
||||||
@@ -244,12 +261,16 @@ export function LiveArtboard({
|
|||||||
return (
|
return (
|
||||||
<iframe
|
<iframe
|
||||||
ref={iframeRef}
|
ref={iframeRef}
|
||||||
// The name attribute carries the artboard ID to the fiber hook.
|
// The name= attribute is a legacy fallback. Chrome 88+ strips window.name
|
||||||
// The hook reads window.name to tag postMessage envelopes and to
|
// when an iframe loads cross-origin (Spectre mitigation), so we ALSO
|
||||||
// guard against activating outside Originmain iframes.
|
// append the artboard ID as a URL fragment (#__om_artboard=...) which:
|
||||||
// Format: "om:<artboardId>"
|
// • is not sent to the target server
|
||||||
|
// • survives same-origin SPA navigations
|
||||||
|
// • is readable by the fiber hook synchronously via location.hash
|
||||||
|
// The fiber hook also implements a postMessage handshake as a final
|
||||||
|
// fallback — see fiber-hook.ts.
|
||||||
name={`om:${id}`}
|
name={`om:${id}`}
|
||||||
src={src}
|
src={src + (src.includes('#') ? '&' : '#') + '__om_artboard=' + encodeURIComponent(id)}
|
||||||
title={`artboard-${id}`}
|
title={`artboard-${id}`}
|
||||||
// Security: allow-scripts required for React; allow-same-origin required
|
// Security: allow-scripts required for React; allow-same-origin required
|
||||||
// for postMessage origin validation. Do NOT add allow-top-navigation or
|
// for postMessage origin validation. Do NOT add allow-top-navigation or
|
||||||
|
|||||||
@@ -127,18 +127,45 @@ export function buildProxyFiberHookScript(): string {
|
|||||||
// ── Guard: only activate inside an Originmain iframe ─────────────────────
|
// ── Guard: only activate inside an Originmain iframe ─────────────────────
|
||||||
if (window.parent === window) return;
|
if (window.parent === window) return;
|
||||||
|
|
||||||
var NAME_PREFIX = 'om:';
|
// ── Resolve the artboard ID ──────────────────────────────────────────────
|
||||||
var artboardId = '';
|
// Chrome 88+ clears window.name on cross-origin iframe loads (Spectre / side-
|
||||||
try {
|
// channel mitigation), so we cannot rely on the iframe element's name=
|
||||||
if (typeof window.name === 'string' && window.name.indexOf(NAME_PREFIX) === 0) {
|
// attribute alone. We try four sources in priority order:
|
||||||
artboardId = window.name.slice(NAME_PREFIX.length);
|
//
|
||||||
}
|
// 1. URL fragment #__om_artboard=xxx (set by LiveArtboard on the src URL)
|
||||||
} catch (e) { /* sandboxed context — not our iframe */ }
|
// 2. window.name om:xxx (legacy; works for same-origin)
|
||||||
if (!artboardId) return;
|
// 3. sessionStorage __om_artboard_id__ (preserved across navigations)
|
||||||
|
// 4. postMessage handshake to the parent (last-resort, async)
|
||||||
|
|
||||||
// ── Diagnostic logging (temporary) ───────────────────────────────────────
|
function tryFragment() {
|
||||||
var OM_TAG = '[Originmain hook ' + artboardId.slice(0, 6) + ']';
|
try {
|
||||||
console.log(OM_TAG, 'script active, artboardId=' + artboardId);
|
var m = (window.location.hash || '').match(/__om_artboard=([^&]+)/);
|
||||||
|
return m ? decodeURIComponent(m[1]) : null;
|
||||||
|
} catch(e) { return null; }
|
||||||
|
}
|
||||||
|
function tryWindowName() {
|
||||||
|
try {
|
||||||
|
if (typeof window.name === 'string' && window.name.indexOf('om:') === 0) {
|
||||||
|
return window.name.slice(3);
|
||||||
|
}
|
||||||
|
} catch(e) {}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
function trySessionStorage() {
|
||||||
|
try { return sessionStorage.getItem('__om_artboard_id__'); } catch(e) { return null; }
|
||||||
|
}
|
||||||
|
function persistArtboardId(id) {
|
||||||
|
try { sessionStorage.setItem('__om_artboard_id__', id); } catch(e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
var artboardId = tryFragment() || tryWindowName() || trySessionStorage();
|
||||||
|
|
||||||
|
// Diagnostic tag uses 'pending' before the ID is known so console messages
|
||||||
|
// are still readable during the handshake window.
|
||||||
|
var OM_TAG = '[Originmain hook ' + (artboardId ? artboardId.slice(0, 6) : 'pending') + ']';
|
||||||
|
console.log(OM_TAG, 'script active, artboardId=' + (artboardId || '(awaiting handshake)'));
|
||||||
|
|
||||||
|
if (artboardId) persistArtboardId(artboardId);
|
||||||
|
|
||||||
var RENDERER_SOURCE = ${JSON.stringify(RENDERER_SOURCE)};
|
var RENDERER_SOURCE = ${JSON.stringify(RENDERER_SOURCE)};
|
||||||
var HOST_SOURCE = ${JSON.stringify(HOST_SOURCE)};
|
var HOST_SOURCE = ${JSON.stringify(HOST_SOURCE)};
|
||||||
@@ -170,7 +197,9 @@ export function buildProxyFiberHookScript(): string {
|
|||||||
var _html2canvasLoading = null; // cached Promise<html2canvas> — only inject script once
|
var _html2canvasLoading = null; // cached Promise<html2canvas> — only inject script once
|
||||||
|
|
||||||
// ── postMessage helper ────────────────────────────────────────────────────
|
// ── postMessage helper ────────────────────────────────────────────────────
|
||||||
|
// No-op until artboardId is known (handshake may still be pending).
|
||||||
function post(msg) {
|
function post(msg) {
|
||||||
|
if (!artboardId) return;
|
||||||
try {
|
try {
|
||||||
window.parent.postMessage(
|
window.parent.postMessage(
|
||||||
{ source: RENDERER_SOURCE, artboardId: artboardId, message: msg }, '*'
|
{ source: RENDERER_SOURCE, artboardId: artboardId, message: msg }, '*'
|
||||||
@@ -676,6 +705,20 @@ export function buildProxyFiberHookScript(): string {
|
|||||||
window.addEventListener('message', function(event) {
|
window.addEventListener('message', function(event) {
|
||||||
var data = event.data;
|
var data = event.data;
|
||||||
if (!data || typeof data !== 'object') return;
|
if (!data || typeof data !== 'object') return;
|
||||||
|
|
||||||
|
// INIT_RESPONSE handshake — handled BEFORE the artboardId match because
|
||||||
|
// it is what teaches us our artboardId in the first place.
|
||||||
|
if (data.__om_init_response && typeof data.artboardId === 'string') {
|
||||||
|
if (!artboardId) {
|
||||||
|
artboardId = data.artboardId;
|
||||||
|
persistArtboardId(artboardId);
|
||||||
|
OM_TAG = '[Originmain hook ' + artboardId.slice(0, 6) + ']';
|
||||||
|
console.log(OM_TAG, 'received artboardId via handshake');
|
||||||
|
if (typeof startMainLoop === 'function') startMainLoop();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (data.source !== HOST_SOURCE) return;
|
if (data.source !== HOST_SOURCE) return;
|
||||||
if (data.artboardId !== artboardId) return;
|
if (data.artboardId !== artboardId) return;
|
||||||
var msg = data.message;
|
var msg = data.message;
|
||||||
@@ -1087,31 +1130,18 @@ export function buildProxyFiberHookScript(): string {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Ready signal (includes root font size for rem→px normalisation) ────────
|
// ── Main loop: only runs once we know our artboardId ──────────────────────
|
||||||
var rootFontSizePx = parseFloat(
|
// Posts READY, kicks off polling/MutationObserver discovery, schedules
|
||||||
window.getComputedStyle(document.documentElement).getPropertyValue('font-size') || '16'
|
// route discovery, and wires up SPA navigation handlers.
|
||||||
) || 16;
|
|
||||||
post({ type: 'READY', rootFontSizePx: rootFontSizePx });
|
|
||||||
|
|
||||||
// ── Robust tree discovery: polling + MutationObserver + periodic refresh ──
|
|
||||||
// Don't trust the hook integration. Even if onCommitFiberRoot fires, we want
|
|
||||||
// a fallback for the case where it doesn't (broken DevTools/React Refresh).
|
|
||||||
//
|
|
||||||
// • Initial backoff schedule: 0, 50, 200, 500, 1s, 2s, 4s, 8s, 12s, 16s
|
|
||||||
// Stops as soon as a tree is found.
|
|
||||||
// • MutationObserver on <html>: any DOM change triggers one re-attempt.
|
|
||||||
// • Once found, periodic 3 s rescans pick up future updates that the hook
|
|
||||||
// might have missed.
|
|
||||||
var _treeFound = false;
|
var _treeFound = false;
|
||||||
var _rescanInterval = null;
|
var _rescanInterval = null;
|
||||||
|
var _mainLoopStarted = false;
|
||||||
|
|
||||||
function tryCapture(label) {
|
function tryCapture(label) {
|
||||||
if (captureExistingTree()) {
|
if (captureExistingTree()) {
|
||||||
if (!_treeFound) {
|
if (!_treeFound) {
|
||||||
_treeFound = true;
|
_treeFound = true;
|
||||||
console.log(OM_TAG, 'tree found via ' + label);
|
console.log(OM_TAG, 'tree found via ' + label);
|
||||||
// Set up periodic rescans so future React commits are reflected even
|
|
||||||
// if onCommitFiberRoot is silenced by hook breakage.
|
|
||||||
if (!_rescanInterval) {
|
if (!_rescanInterval) {
|
||||||
_rescanInterval = setInterval(function() { captureExistingTree(); }, 3000);
|
_rescanInterval = setInterval(function() { captureExistingTree(); }, 3000);
|
||||||
}
|
}
|
||||||
@@ -1121,16 +1151,24 @@ export function buildProxyFiberHookScript(): string {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schedule discoverRoutes after the first capture attempts settle.
|
function startMainLoop() {
|
||||||
|
if (_mainLoopStarted) return;
|
||||||
|
_mainLoopStarted = true;
|
||||||
|
|
||||||
|
var rootFontSizePx = parseFloat(
|
||||||
|
window.getComputedStyle(document.documentElement).getPropertyValue('font-size') || '16'
|
||||||
|
) || 16;
|
||||||
|
post({ type: 'READY', rootFontSizePx: rootFontSizePx });
|
||||||
|
|
||||||
setTimeout(discoverRoutes, 800);
|
setTimeout(discoverRoutes, 800);
|
||||||
|
|
||||||
// Initial polling schedule — geometric backoff up to 16 s.
|
// Initial polling — geometric backoff up to 16 s. Stops on first success.
|
||||||
tryCapture('immediate');
|
tryCapture('immediate');
|
||||||
[50, 200, 500, 1000, 2000, 4000, 8000, 12000, 16000].forEach(function(ms) {
|
[50, 200, 500, 1000, 2000, 4000, 8000, 12000, 16000].forEach(function(ms) {
|
||||||
setTimeout(function() { if (!_treeFound) tryCapture('poll-' + ms); }, ms);
|
setTimeout(function() { if (!_treeFound) tryCapture('poll-' + ms); }, ms);
|
||||||
});
|
});
|
||||||
|
|
||||||
// MutationObserver: trip the moment React first writes to the DOM.
|
// MutationObserver — fires the moment React writes to the DOM.
|
||||||
try {
|
try {
|
||||||
var _mo = new MutationObserver(function() {
|
var _mo = new MutationObserver(function() {
|
||||||
if (_treeFound) { _mo.disconnect(); return; }
|
if (_treeFound) { _mo.disconnect(); return; }
|
||||||
@@ -1139,16 +1177,36 @@ export function buildProxyFiberHookScript(): string {
|
|||||||
_mo.observe(document.documentElement || document.body, {
|
_mo.observe(document.documentElement || document.body, {
|
||||||
childList: true, subtree: true, attributes: false,
|
childList: true, subtree: true, attributes: false,
|
||||||
});
|
});
|
||||||
// Disconnect if we still haven't found anything after 30s — at that point
|
|
||||||
// the page is genuinely static or React is too broken to render.
|
|
||||||
setTimeout(function() { try { _mo.disconnect(); } catch(e) {} }, 30000);
|
setTimeout(function() { try { _mo.disconnect(); } catch(e) {} }, 30000);
|
||||||
} catch(e) { /* MutationObserver not available */ }
|
} catch(e) { /* MutationObserver not available */ }
|
||||||
|
|
||||||
// Re-discover on SPA navigation (Next.js App Router fires popstate on push).
|
|
||||||
window.addEventListener('popstate', function() {
|
window.addEventListener('popstate', function() {
|
||||||
setTimeout(discoverRoutes, 100);
|
setTimeout(discoverRoutes, 100);
|
||||||
setTimeout(function() { captureExistingTree(); }, 200);
|
setTimeout(function() { captureExistingTree(); }, 200);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Either start immediately (we know our ID) or run the handshake first.
|
||||||
|
if (artboardId) {
|
||||||
|
startMainLoop();
|
||||||
|
} else {
|
||||||
|
// Handshake: ask the parent who we are. Retry every 250 ms until we get a
|
||||||
|
// response (the parent's message listener may not be wired up yet on the
|
||||||
|
// first iframe-load tick) or until 30 s elapses.
|
||||||
|
console.log(OM_TAG, 'no artboardId from URL/window.name/storage — initiating handshake');
|
||||||
|
var _hsInterval = setInterval(function() {
|
||||||
|
if (artboardId) { clearInterval(_hsInterval); return; }
|
||||||
|
try { window.parent.postMessage({ __om_init_request: true }, '*'); } catch(e) {}
|
||||||
|
}, 250);
|
||||||
|
// First request immediately.
|
||||||
|
try { window.parent.postMessage({ __om_init_request: true }, '*'); } catch(e) {}
|
||||||
|
setTimeout(function() {
|
||||||
|
clearInterval(_hsInterval);
|
||||||
|
if (!artboardId) {
|
||||||
|
console.warn(OM_TAG, 'never received artboardId — page will not be inspectable');
|
||||||
|
}
|
||||||
|
}, 30000);
|
||||||
|
}
|
||||||
})();`;
|
})();`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user