# Installation and support

Install `@kernstra/pdf-web-sdk` with `npm install @kernstra/pdf-web-sdk`. The SDK ships as dual ESM + CJS with TypeScript declarations. It works with any framework (React, Vue, Angular, Svelte, vanilla JS) and any bundler that reads `package.json` `exports` (Vite, webpack 5, esbuild, Rollup; the support table has the floors).

**Bundle it at ES2022 or later.** The package is ES2022; a build that transpiles it to an older target breaks the SDK. Set your bundler's target for this package to `es2022` or later — Vite `build.target: 'es2022'` (some Vite versions default to an older target), esbuild `--target=es2022` — and leave it out of Babel or SWC down-leveling of `node_modules`.

## Example

```text
script-src  'self' 'wasm-unsafe-eval';
worker-src  'self';                               where render.worker.js is served, when workerUrl is set
connect-src 'self' https://<assets store> https://<licensing api>;
img-src     'self' data:;                         stamps, signatures, thumbnails and print pages are data: images
style-src   'self' 'nonce-<nonce>';               or 'unsafe-inline'
```

## Browser and toolchain support

|  | Supported |
| --- | --- |
| Browsers | The last two major versions of Chrome, Edge, Firefox and Safari, and Firefox ESR. At the least Chrome and Edge 111, Firefox 114 (ESR 128), Safari 16.4: the viewer's stylesheet uses `color-mix()` and dynamic viewport units, which older versions draw wrong. On a phone or tablet, Safari on iOS and iPadOS 16.4 or newer and Chrome on Android — they update themselves, so the latest stable release is what is tested. An older browser is not refused; nothing is promised for it |
| Page | A secure context (HTTPS, or `localhost`); WebAssembly; Web Workers of the module kind when `workerUrl` is set. No cross-origin isolation (COOP/COEP) is needed |
| Bundler | One that reads `package.json` `exports` and emits ES2022 or later: Vite 5+, webpack 5, esbuild, Rollup 3+, Parcel 2, Next.js 13+, Nuxt 3+, Angular 16+. Webpack 4 cannot parse the package; a build that transpiles `node_modules` to an older target breaks the SDK (above). None is needed: see **Without a bundler** below |
| TypeScript | 5.0 or later, any `moduleResolution`. The declarations are served for `import` and `require` alike |
| Server-side rendering | The package may be imported on a server: nothing touches `window` or `document` until `Kernstra.init()` or a viewer is constructed, which belongs in browser-only code (`useEffect`, `onMounted`, `afterNextRender`); called on a server, `Kernstra.init()` rejects with `INVALID_STATE` |
| Shadow DOM | Not supported: the SDK's stylesheets go into `document.head`, and its dialogs and popovers into `document.body` |
| Node | 20 or later, to install |
| Size | `index.js` is about 1.7 MB gzipped as published; your bundler's minifier brings it to about 1.15 MB gzipped. The engine (about 4.4 MB) is downloaded once per browser from the asset store, not bundled |

## Notes

**Loading it lazily.** Nothing needs the SDK before a document is shown, so a page that opens PDFs on demand keeps it out of its first load with a dynamic `import()`: every bundler above splits it into a chunk of its own (esbuild with `--splitting`), fetched when the import runs. `Kernstra.init()` downloads and starts the engine, so call it as soon as a PDF becomes likely (a hover over the link, a route that will show one) rather than at the click: the second call gets the same promise back. In code: `const { Kernstra, PDFDocument, PDFViewer } = await import('@kernstra/pdf-web-sdk');` then `await Kernstra.init({ license: import.meta.env.KERNSTRA_LICENSE });`, `const doc = await PDFDocument.fromUrl(url);` and `new PDFViewer(container, doc)`.

**Without a bundler.** A page with no build step — plain HTML, a server-rendered site, a CMS template — loads the SDK as an ES module by its URL. `dist/index.js` is one self-contained module that imports nothing, so no import map is needed, and `dist/render.worker.js` is the optional render worker. Take both from the same version: copy them from `node_modules/@kernstra/pdf-web-sdk/dist/` (or the package tarball) into a folder of your site. In code, inside a `<script type="module">` on a page whose `<div id="viewer">` has a height: `import { Kernstra, PDFDocument, PDFViewer } from '/vendor/kernstra/index.js';` then `await Kernstra.init({ license: '<your license key>', workerUrl: '/vendor/kernstra/render.worker.js' });` (the `workerUrl` is optional), `const doc = await PDFDocument.fromUrl('/files/invoice.pdf');` and `new PDFViewer(document.getElementById('viewer'), doc);`. Everything else — the options, the API, the events — is the same as in a bundled app; the TypeScript declarations (`dist/index.d.ts`) are optional and only serve your editor.

**The render worker is served by your own site.** A browser starts a worker only from the page's own origin, so a `workerUrl` on another origin is refused: the SDK then logs a `RENDER_FAILED` warning and renders on the main thread, as it does without `workerUrl`. Keep `render.worker.js` of the same version next to the SDK in your folder.

**Serve the page over HTTPS** (or from `localhost` while developing), as every page that runs the SDK must be; a page opened from disk (`file://`) is not supported.

**The license is checked against the page's origin**, as in a bundled app.

**A content security policy** needs nothing more than the Content security policy notes below list: `script-src 'self'` covers the module and `worker-src 'self'` the worker.

**Content security policy.** The SDK makes requests only to the places its license names, and draws its own interface with stylesheets it adds to the page. The policy in the example above covers it; the points below say why each line is there.

`'wasm-unsafe-eval'` lets the browser compile the engine; it is narrower than `'unsafe-eval'` and every supported browser has it.

The assets store and the licensing API are the two origins the license token names; the first serves the engine, the shaping and subsetting binaries and the bundled fonts, the second answers the status check on each load. An offline license names neither, and a self-hosted deployment replaces the store with your own origin — see [Engine, binaries and fonts](https://kernstra.com/docs/web-sdk/guides/engine-assets).

With `tools.sign.webFonts: true` add `https://fonts.googleapis.com` to `style-src` and `https://fonts.gstatic.com` to `font-src`. Nothing else is loaded from a third party.

A `style-src` without `'unsafe-inline'` refuses every `<style>` the SDK adds unless it carries the page's nonce. Pass that nonce once, as `styleNonce` in [`Kernstra.init()`](https://kernstra.com/docs/web-sdk/api/kernstra/init), and every sheet the SDK injects — the print frame's included — carries it: `await Kernstra.init({ license, styleNonce: nonceFromYourServer });`. When `styleNonce` is not given, the SDK reads the nonce a page declares for its tooling as `<meta property="csp-nonce" nonce="…">`, the convention Vite and others follow. With a nonce in force the Google Fonts link carries it too, so `style-src` needs no Google origin. The SDK styles its elements through the CSSOM, never with `style="…"` markup, so `style-src-attr` needs nothing. A value that is not base64 or base64url is refused with `INVALID_ARGUMENT`.

A policy that enforces Trusted Types (`require-trusted-types-for 'script'`) is not supported in 1.0.

One thing comes from your own build rather than the license: the render worker, whose 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'`, since the SDK's engine is compiled inside the worker too. Anything you point the SDK at yourself — a document URL, `customFonts` URLs, an `uploadTo()` destination, a stamp image `url` — needs its origin in `connect-src` or `img-src` as well. See [Web worker rendering](https://kernstra.com/docs/web-sdk/guides/worker).


Web page: https://kernstra.com/docs/web-sdk/guides/installation
