namespace DeepDrftPublic.Client.Helpers; /// /// Builds the iframe embed snippet the share popover copies. Two targets: a single track /// (FramePlayer?TrackEntryKey=...) and a whole release /// (FramePlayer?ReleaseEntryKey=...). /// /// /// The two snippets diverge in height by design (Phase 17 §4.1, OQ6): a single-track embed has no /// queue, so stays at the compact player height; a release embed renders the /// always-shown queue panel below the controls, so is taller to show it /// without clipping. Other iframe chrome (width, border radius, autoplay permission) is identical and /// defined once in . /// /// /// /// OQ1 Option A — collapse/expand resize handshake. The release snippet carries a tiny host-side /// listener: the embedded player posts its desired height when the viewer collapses/expands the /// queue panel, and the listener sizes this specific iframe to match. It is scoped to the snippet's /// own iframe (matched by id) and ignores any message whose shape does not match, so it cannot be /// driven by foreign frames. It degrades to Option B's behaviour if the host strips the script: the /// panel still renders and toggles inside the iframe at its default (expanded) height — only the /// outer resize is lost. The track snippet needs no script (no panel, no toggle). /// /// /// /// Multi-embed isolation: each call mints a fresh random token (8 hex /// chars). The token is used as the iframe id (deepdrft-embed-{token}) and threaded into /// the iframe src as &EmbedId={token} so the iframe can learn its own id. The host-side /// resize script matches incoming messages on embedId and resizes only the iframe whose id /// matches the token — two releases on one host page resize independently without cross-talk. Two /// calls for the same release still get distinct tokens, ensuring uniqueness even when the same /// release is pasted twice. Older snippets that lack embedId in their postMessage payload are /// silently ignored by the script (backward-compatible degradation). /// /// /// Pure string composition so the snippet shape is unit-testable without rendering the component. /// public static class EmbedSnippetBuilder { // Compact single-track height (the historical embed height — must not change: UC6/AC6). private const int TrackHeight = 196; // Release height: the compact player plus the queue panel (fixed, internally scrollable past N // rows per OQ6). The panel collapses to the track height via the resize handshake below. private const int ReleaseHeight = 384; // baseUri carries a trailing slash (NavigationManager.BaseUri), so "FramePlayer" appends cleanly. public static string ForTrack(string baseUri, string trackEntryKey) => Frame($"{baseUri}FramePlayer?TrackEntryKey={trackEntryKey}", TrackHeight); public static string ForRelease(string baseUri, string releaseEntryKey) { // Mint a fresh random token per call so two embeds on the same host page never share an id, // even when they point at the same release. var token = Guid.NewGuid().ToString("N")[..8]; var iframeId = $"deepdrft-embed-{token}"; var src = $"{baseUri}FramePlayer?ReleaseEntryKey={releaseEntryKey}&EmbedId={token}"; return Frame(src, ReleaseHeight, iframeId, ResizeScript(iframeId, token)); } private static string Frame(string src, int height, string iframeId = "deepdrft-embed", string trailingScript = "") => $"""{trailingScript}"""; // Host-side listener: resize the matching iframe when the embedded player posts its panel height. // The embedId field in the payload is matched against the snippet's own token so only this // iframe is driven — foreign frames or other release embeds on the same page cannot interfere. // The height is clamped to a sane floor so a bad payload can't collapse the player away. // Messages without embedId (older snippets) are silently ignored. private static string ResizeScript(string iframeId, string token) => ""; }