58cdb4d9dc
ForRelease mints a per-call token used as the iframe id and threaded into the src as EmbedId; the host script matches on it so multiple embeds resize independently. ForTrack unchanged.
56 lines
2.9 KiB
TypeScript
56 lines
2.9 KiB
TypeScript
// Embed (iframe) → host resize handshake (Phase 17 wave 17.3, OQ1 Option A).
|
|
//
|
|
// The Fixed-mode player renders an always-shown queue panel below the controls. A collapse/expand
|
|
// toggle lets the embedder's viewer reclaim the panel's vertical space — but collapsing inside the
|
|
// iframe only reclaims space if the *outer* iframe element also shrinks. The iframe cannot resize
|
|
// itself, so it posts its desired pixel height to the host page; the embed snippet (minted by
|
|
// EmbedSnippetBuilder.ForRelease) carries a tiny listener that sets iframe.style.height.
|
|
//
|
|
// Degrades safely: if the host page ignores or strips the snippet's listener (Option B's value), the
|
|
// panel still renders and toggles inside the iframe — only the outer resize is lost. We post nothing
|
|
// when not framed (window === parent), so the docked player is unaffected.
|
|
//
|
|
// Multi-embed isolation: EmbedSnippetBuilder.ForRelease mints a per-snippet random token and passes
|
|
// it as ?EmbedId=<token> in the iframe src. We read it here from window.location.search and include
|
|
// it in the postMessage payload as `embedId`. The host-side resize script matches on this token so
|
|
// only the correct iframe is resized when multiple embeds share a host page. If EmbedId is absent
|
|
// (older already-pasted snippets), embedId is omitted from the payload — those snippets' scripts
|
|
// ignore messages without a matching embedId, so there is no cross-talk either way.
|
|
|
|
const MESSAGE_TYPE = "deepdrft-embed-resize";
|
|
|
|
/** Read the EmbedId query param from the iframe's own URL, if present. */
|
|
function readEmbedId(): string | null {
|
|
try {
|
|
return new URLSearchParams(window.location.search).get("EmbedId");
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Resolved once at module load — the URL does not change while the iframe is alive.
|
|
const embedId: string | null = readEmbedId();
|
|
|
|
/**
|
|
* Measure the live rendered height of the player element and post it to the host page so it can size
|
|
* the iframe to match. No-op when not embedded in a frame, or when the element is unmeasurable.
|
|
*
|
|
* targetOrigin is "*" deliberately: the embedder's origin is unknown (any blog can embed us) and the
|
|
* payload carries no secrets — just a height the host is free to ignore.
|
|
*
|
|
* The payload includes `embedId` when the iframe src carried an EmbedId query param. The host-side
|
|
* resize script matches on this field to isolate multiple embeds on the same page.
|
|
*/
|
|
export function postHeight(element: HTMLElement): void {
|
|
if (window.parent === window) return; // Not framed — nothing to resize.
|
|
if (!element) return;
|
|
|
|
// ceil + a hairline guard against sub-pixel rounding that would otherwise clip the bottom edge.
|
|
const height = Math.ceil(element.getBoundingClientRect().height) + 2;
|
|
if (!Number.isFinite(height) || height <= 0) return;
|
|
|
|
const payload: Record<string, unknown> = { type: MESSAGE_TYPE, height };
|
|
if (embedId !== null) payload.embedId = embedId;
|
|
window.parent.postMessage(payload, "*");
|
|
}
|