# 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.

- Level: Deployment
- Uses: `Kernstra.assetDigests`, `engineUrl`, `shaperUrl`, `subsetterUrl`, `workerUrl`, `fonts.fontsBaseUrl`, `fonts.download`, `connect-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'` and `worker-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.ts`

```ts
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.sh`

```sh
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.ts`

```ts
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.

`headers`

```http
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: false` keeps 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.

## Related

- [Engine, binaries and fonts](https://kernstra.com/docs/web-sdk/guides/engine-assets)
- [Initialization](https://kernstra.com/docs/web-sdk/config/init/engineurl)
- [Fonts](https://kernstra.com/docs/web-sdk/config/fonts/fontsbaseurl)
- [Web worker rendering](https://kernstra.com/docs/web-sdk/guides/worker)
- [Installation and support](https://kernstra.com/docs/web-sdk/guides/installation)
- [Kernstra.assetDigests](https://kernstra.com/docs/web-sdk/api/kernstra/assetdigests)

Web page: https://kernstra.com/docs/web-sdk/recipes/self-host-engine
