/** * Turns a message's sanitized HTML into something a frame can actually show. * * Two things were missing and both looked like "images are broken": a message * written by any normal mail client references its own pictures as * src="cid:...", which resolves against nothing in a browser; and the frame was * handed a bare fragment with no document around it, so mail written for a * white page rendered as dark text on a dark surface with no width limit. */ /** The parts the API returns alongside the body, keyed by Content-ID. */ export type InlineImages = Record; /** * Replaces src="cid:x" with the data: URI the server delivered for that part. * * A reference with no matching part loses its source entirely rather than * keeping a cid: the browser cannot fetch: an unresolvable src draws a * broken-image icon, while no src at all falls back to the alt text. */ export function resolveInlineImages(html: string, images: InlineImages): string { if (!html) return ""; return html.replace( /(]*?)\ssrc\s*=\s*("|')cid:([^"']*)\2/gi, (_match, head: string, _quote: string, reference: string) => { // Clients disagree about whether the reference keeps the angle brackets // the Content-ID header carries, so both spellings resolve. const key = decodeURIComponent(reference.trim()).replace(/^<|>$/g, ""); const resolved = images[key]; return resolved ? `${head} src="${resolved}"` : head; }, ); } /** * Wraps a message body in the document the reader frame renders. * * The frame is sandboxed with an opaque origin, so it cannot inherit anything * from the app: every style, the light surface mail assumes, and the height * report the host page sizes the frame from all have to travel inside it. */ export function buildReaderDocument(bodyHtml: string): string { return ` ${bodyHtml} `; }