2.9 KiB
Scalar HTML shell renderer
(no submodules)
Purpose
The render-shell module emits the HTML body returned by the Scalar UI
handler. It embeds the configured OpenAPI document URL into a
<script id="scalar-config" type="application/json"> block (inert under
strict CSP — JSON is not a script-src vector), references the resolved
Scalar runtime URL (CDN or bundled), and renders a small status / path
listing placeholder so even the air-gapped bundled bootstrap has
something to populate. When the operator runs nonce-based CSP, the
renderShell API accepts a nonce value the renderer threads onto the
inline <script> tags.
Features
escapeHtml(input)— replaces& < > " 'with their entity equivalents. Used on every caller-supplied string embedded in the shell.renderShell({ options, openapiUrl, runtimeUrl, nonce? })— emits the full HTML body. The body referencesopenapiUrlexactly once in the Scalar configuration block per the requirement spec.- Bundled-mode shells include a
<link rel="stylesheet">to the local CSS asset; cdn-mode shells omit the link because the upstream Scalar bundle ships its own styles.
Usage: Basic
import { renderShell } from "@suaveplan/api-openapi-ui-scalar/html";
import { parseScalarUiOptions } from "@suaveplan/api-openapi-ui-scalar/options";
const options = parseScalarUiOptions({ openapiUrl: "/openapi.json" });
const html = renderShell({
options,
openapiUrl: "/openapi.json",
runtimeUrl: "https://cdn.jsdelivr.net/npm/@scalar/api-reference",
});
Usage: Advanced
import { renderShell } from "@suaveplan/api-openapi-ui-scalar/html";
import { parseScalarUiOptions } from "@suaveplan/api-openapi-ui-scalar/options";
const options = parseScalarUiOptions({
openapiUrl: "/openapi.json?plugin=billing",
theme: "deepSpace",
assetMode: "bundled",
});
// Per-request nonce for hosts running nonce-based CSP.
const html = renderShell({
options,
openapiUrl: options.openapiUrl,
runtimeUrl: "/docs/ui/scalar/assets/scalar.<hash>.js",
nonce: "n-9b2f7c2a",
});
// The body references the configured OpenAPI URL exactly once.
console.assert(html.split(options.openapiUrl).length - 1 === 1);
API Reference
RenderShellInput—{ options, openapiUrl, runtimeUrl, nonce? }.renderShell(input)— returns the full HTML body string.escapeHtml(input)— entity-escapes the five hazardous characters.
Implementation Notes
The configuration block JSON is post-processed to replace every < with
its unicode escape so string values like "foo</script>" cannot
terminate the script tag. This is the same defence applied by the
sibling api-graphql-ui-graphiql package; the technique is documented
by OWASP and is the strictest path that keeps the policy free of
'unsafe-inline'. The OpenAPI URL is embedded inside the JSON block
exactly once — verified by the handler conformance test.