Serve the engine from your own origin
Under a license that includes self-hosting, fetch the engine, the shaping and subsetting binaries and the bundled fonts once by their digests, serve them from your own servers, and point
Kernstra.init() and the viewer at them. The browser then makes no request outside your origin.DeploymentKernstra.assetDigestsengineUrlshaperUrlsubsetterUrlworkerUrlfonts.fontsBaseUrlfonts.downloadconnect-src 'self'
Online licenses download the binaries and the bundled fonts from the asset store the license names. A license that includes self-hosting may serve them from its own infrastructure instead; today that is an offline (air-gapped) license, which names no licensing API and no asset store, carries its own expiry, and always self-hosts.
What you get
- No request leaves your origin: the engine, the shaper, the subsetter, the fonts and the render worker all come from your servers.
- The SDK verifies the bytes exactly as it verifies a download, so a wrong or stale file is refused rather than run.
- A content security policy of
connect-src 'self'andworker-src 'self'.
1. Check the license first
Without self-hosting in the license,
engineUrl, shaperUrl and subsetterUrl are refused by Kernstra.init() with FEATURE_NOT_LICENSED before anything is fetched, and fonts.fontsBaseUrl is refused the same way when the first text tool opens. The license state says whether yours is offline.init.tsTypeScript
import { Kernstra } from '@kernstra/pdf-web-sdk'; const state = Kernstra.getLicense(); if (state.valid && state.offline) { // air-gapped: no status check, no network calls; everything comes from your servers }
2. Fetch each file once, by its digest
Kernstra.assetDigests is the SHA-256 of every file this version downloads: { engine, shaper, subset, fonts }, fonts keyed by font id (BUILTIN_FONTS[i].id). The paths in the store are the digests under a folder per kind (engine, shaper, subsetter, fonts); the subset digest's folder is subsetter. The bundled fonts are served under fonts.fontsBaseUrl by each font's ttfPath.deploy.shBash
STORE=https://<the asset store address that came with your license> curl "$STORE/engine/<engine digest>" -o public/assets/pdf-engine.bin curl "$STORE/shaper/<shaper digest>" -o public/assets/pdf-shaper.bin curl "$STORE/subsetter/<subset digest>" -o public/assets/pdf-subset.bin cp node_modules/@kernstra/pdf-web-sdk/dist/render.worker.js public/assets/render.worker.js
A deployment script prints the font lines from the digests: for each
BUILTIN_FONTS entry whose source is 'bundled', fetch $STORE/fonts/<Kernstra.assetDigests.fonts[font.id]> and save it as public/assets/fonts/<font.ttfPath>.3. Point the SDK at your copies
Pass the URLs to
init, and the fonts folder to the viewer. The render worker is optional and makes no request of its own; without it the SDK renders on the main thread. fonts.download: false is the alternative to hosting the fonts: it keeps the standard fourteen faces, the fonts embedded in each document and your own customFonts given as bytes, and refuses any other with a descriptive error.init.tsTypeScript
import { Kernstra, PDFViewer } from '@kernstra/pdf-web-sdk'; await Kernstra.init({ license: import.meta.env.KERNSTRA_LICENSE, engineUrl: '/assets/pdf-engine.bin', shaperUrl: '/assets/pdf-shaper.bin', subsetterUrl: '/assets/pdf-subset.bin', workerUrl: '/assets/render.worker.js', }); const viewer = new PDFViewer(container, doc, { fonts: { fontsBaseUrl: '/assets/fonts/' }, // or { download: false } });
The shaping binary is needed only if you use the text tools, the subsetting binary only if
fonts.subset is on. Fonts are verified too, unless you turn that off with fonts.verifyIntegrity: false.4. The policy
connect-src 'self' and worker-src 'self' are enough for requests: the SDK has no telemetry and no other endpoint. The rest of the policy is as for any deployment. The render worker's policy is the one its own response carries: serve render.worker.js with a Content-Security-Policy that has script-src 'self' 'wasm-unsafe-eval', which lets the browser compile the SDK's engine inside the worker.headersHTTP
Content-Security-Policy: script-src 'self' 'wasm-unsafe-eval'; worker-src 'self'; connect-src 'self'; img-src 'self' data:; style-src 'self' 'nonce-<nonce>';
Gotchas
- The signature dialog's web faces stay off unless you set
tools.sign.webFonts: true: leave it unset. - The "You are offline" strip above the pages follows the browser's own online state, which an intranet host reports as online; where a host's network makes the browser report offline,
toolbar.offlineNotice: falsekeeps the strip away. - Nothing else behaves differently: Edit Text, forms, annotations, signatures and export run in the browser whatever the network.
- An intranet deployment still needs HTTPS: the SDK runs only in a secure context, and a self-signed certificate is enough.