이미지와 글꼴 불러오기
외부 리소스가 페이지 문서에 들어오는 유일한 경로인 assetResolver를 구현합니다.
HTML에 <img>를 추가해도 이미지는 로드되지 않습니다. 버그가 아니라 Imposia가 전제로 삼는 경계이며, assetResolver는 그 경계를 의도적으로 여는 방법입니다.
기본적으로 아무것도 로드되지 않는 이유
페이지를 담는 iframe은 엄격한 Content Security Policy로 샌드박스됩니다. connect-src는 'none'이고, 이미지·글꼴·미디어는 Core가 직접 만든 blob: URL에서만 올 수 있습니다. 문서에 작성된 URL은 네트워크 요청이 되지 않습니다.
이 방식이 보장하는 것
신뢰할 수 없는 마크업이 프레임에서 네트워크에 접근하게 만들 수 없습니다. 페이지에 도달하는 모든 바이트는 먼저 앱을 거치므로, 무엇을 허용할지, 어디에서 가져올지, 자격 증명을 붙일지 여부를 앱이 결정합니다.
Imposia는 HTML과 CSS에서 리소스를 찾아내고, 각 리소스를 리졸버에 묻고, 승인된 바이트를 Core 소유의 blob: URL로 바꾸며, 문서가 교체되거나 실패하거나 제거될 때 그 URL을 폐기합니다.
리졸버를 구현하세요
리졸버는 비동기 함수 하나입니다. 요청을 받아 바이트 또는 거부로 응답합니다.
import type { AssetResolver } from "@imposia/core";
const ALLOWED = new Set(["https://cdn.example.com"]);
export const assetResolver: AssetResolver = async ({ url, kind, baseUrl, signal }) => {
const target = new URL(url, baseUrl);
if (!ALLOWED.has(target.origin)) {
return { status: "blocked", reason: `Origin not allowed: ${target.origin}` };
}
const response = await fetch(target, { signal });
if (!response.ok) {
return { status: "blocked", reason: `HTTP ${response.status}` };
}
return {
status: "resolved",
bytes: new Uint8Array(await response.arrayBuffer()),
mimeType: response.headers.get("content-type") ?? "application/octet-stream",
};
};리졸버는 문서를 마운트할 때 한 번 전달합니다.
<ImposiaPageViewer
source={{ html, baseUrl: "https://cdn.example.com/docs/" }}
documentOptions={{ assetResolver }}
/>요청
| 필드 | 의미 |
|---|---|
url | 작성된 그대로의 URL입니다. 상대 경로일 수 있습니다. |
kind | image, font, media, stylesheet 중 하나입니다. |
baseUrl | source에 지정했을 때 전달되는 문서의 기준 URL입니다. |
signal | 세대가 대체되거나 실패하거나 제거되면 중단됩니다. |
url은 항상 리졸버 안에서 baseUrl을 기준으로 직접 해석하고, signal은 항상 fetch에 전달하세요. signal을 무시하는 리졸버는 아무도 보지 않을 세대를 위해 계속 일하게 됩니다.
응답
리소스를 허용하려면 { status: "resolved", bytes, mimeType }을, 거부하려면 { status: "blocked", reason }을 반환하세요. 거부는 오류가 아니라 정상적인 결과입니다. 문서는 그대로 확정되고, Imposia는 무엇이 거부됐는지 알리는 RESOURCE_BLOCKED 경고를 남깁니다.
반환한 뒤에도 거부되는 경우
바이트를 승인했다고 끝이 아닙니다. Core가 바이트를 검증하며, 값이 맞지 않으면 거부한 것과 같아집니다.
| 검사 | 규칙 |
|---|---|
| MIME 허용 목록 | 이미지는 PNG·JPEG·GIF·WebP·AVIF, 글꼴은 WOFF·WOFF2·TTF·OTF, 스타일시트는 text/css여야 합니다. |
| 매직 바이트 | 글꼴 컨테이너는 선언된 타입이 아니라 실제 시그니처(wOFF, wOF2, OTTO 등)로 확인합니다. |
| 제한 | 참조 수, 총 바이트, 중첩 깊이에 모두 상한이 있습니다. |
실제 PNG에 application/octet-stream을 반환하는 리졸버의 이미지가 차단되는 이유가 이것입니다. 선언한 타입이 정확해야 합니다.
중복 참조도 바이트 제한에 각각 계산됩니다
한 세대 안에서 같은 URL을 여러 번 참조하면 리졸버 호출 하나와 blob: URL 하나를 공유합니다. 그러나 바이트 계산은 의도적으로 중복을 제거하지 않습니다. 모든 참조가 maxAssetBytes에 각각 계산되므로, 이 제한의 의미는 해당 최적화가 생기기 전과 같습니다.
스타일시트는 추가 작업을 불러옵니다
스타일시트를 허용하면 Imposia가 그것을 파싱해, 그 스타일시트가 참조하는 리소스 — @font-face 소스, url() 값, 중첩된 @import 규칙 — 를 다시 찾아냅니다. 각각이 별도의 요청으로 리졸버에 돌아오며, 이때 baseUrl은 그 스타일시트의 위치입니다. 깊이에는 상한이 있으므로 @import 사슬이 무한히 이어질 수는 없습니다.