VerifyKitv0.13.3

Vanilla JS

@trexolab/verifykit-vanilla is a zero-build, drop-in PDF viewer with signature verification. No React knowledge required -- load from a CDN or install via npm and embed a full-featured PDF viewer in any web page.

The package bundles React internally and wraps @trexolab/verifykit-react to expose all viewer features as imperative JavaScript methods.

CDN Installation

Add the stylesheet and script tags to your HTML:

html
<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="https://verifykit.trexolab.com/cdn/verifykit.css">
  <style>
    html, body { height: 100%; margin: 0; padding: 0; }
  </style>
</head>
<body>
  <div id="viewer" style="width: 100%; height: 100vh;"></div>
 
  <script src="https://verifykit.trexolab.com/cdn/verifykit.umd.js"></script>
  <script>
    const viewer = VerifyKit.create(document.getElementById('viewer'), {
      workerUrl: 'https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs',
      theme: { mode: 'system' },
      features: {
        signatures: true,
        print: true,
        download: true,
        search: true,
        fullscreen: true,
      },
    })
 
    fetch('/document.pdf')
      .then(r => r.arrayBuffer())
      .then(buf => viewer.load(buf, 'document.pdf'))
  </script>
</body>
</html>

The UMD bundle includes the WASM core. The .wasm binary file must be served from the same directory as the JS file. When using the CDN, this is handled automatically.

npm Installation

For bundler-based projects:

bash
npm install @trexolab/verifykit-vanilla
ts
import { create } from '@trexolab/verifykit-vanilla'
import '@trexolab/verifykit-vanilla/verifykit.css'
 
const viewer = create(document.getElementById('viewer'), {
  workerUrl: 'https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs',
  theme: { mode: 'system' },
  features: {
    signatures: true,
    print: true,
    download: true,
  },
})
 
const result = await viewer.load(pdfArrayBuffer, 'report.pdf')
console.log('Signatures:', result.signatures.length)
 
viewer.destroy()

The PDF.js Worker

workerUrl is the one required option — create() throws without it. It has to point at pdf.worker.min.mjs from pdfjs-dist@5.5.207, the legacy/build/ copy.

That version is fixed, not chosen. verifykit.umd.js has the pdf.js library bundled inside it; the worker is a separate file loaded at runtime, and pdf.js refuses to run a library and a worker whose versions differ. The examples above use the unpkg URL, which pins the version for you:

https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs

To serve it yourself instead — a CSP with no external script-src, or an air-gapped deployment — download that file once and point workerUrl at your own copy:

bash
curl -o public/pdf.worker.min.mjs \
  https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs
ts
const viewer = VerifyKit.create(el, { workerUrl: '/pdf.worker.min.mjs' })

Re-download it whenever you upgrade VerifyKit: a mismatched pair fails hard, and the console names both versions when it does. See Installation → PDF.js Worker Setup for the npm-registry alternative and the full explanation.

Creating a Viewer

Use VerifyKit.create(container, options?) to mount a viewer on a DOM element:

ts
const viewer = VerifyKit.create(document.getElementById('viewer'), {
  workerUrl: 'https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs',
  theme: { mode: 'system' },
})

Container requirements: The container element must exist in the DOM and have explicit dimensions (width and height). The viewer fills its parent -- if the parent has zero height, nothing renders. A common pattern is style="width: 100%; height: 100vh;".

If the container is null or undefined, create() throws "requires a valid HTMLElement". Make sure the element exists before calling create(), or wrap the call in a DOMContentLoaded listener.

Loading Documents

Use viewer.load(source, fileName?) to load a PDF. It accepts three input types:

ts
// URL string
await viewer.load('/documents/report.pdf', 'report.pdf')
 
// File object (e.g., from a file input)
const file = inputElement.files[0]
const buffer = await file.arrayBuffer()
await viewer.load(buffer, file.name)
 
// ArrayBuffer
const response = await fetch('/document.pdf')
const buf = await response.arrayBuffer()
await viewer.load(buf, 'document.pdf')

load() returns a Promise<VerificationResult> containing all signature data. You can inspect the result immediately:

ts
const result = await viewer.load(buffer, 'doc.pdf')
for (const sig of result.signatures) {
  console.log(sig.name, sig.overallStatus)
}

All imperative methods (like goToPage, zoomIn) are no-ops until load() has resolved. Always await the load before calling other methods.

Password-protected documents

Encrypted PDFs need no extra handling. The viewer shows its own password prompt, and once the password is accepted the signature drawn on the page swaps from Adobe's yellow "?" to the validated status icon, exactly as it does for an unencrypted file. The original signed bytes are never modified — the swapped copy is display-only and held in memory.

Configuration Options

ts
interface VerifyKitOptions {
  // Worker (REQUIRED)
  workerUrl: string             // URL to the PDF.js worker script (required)
 
  // Engine (optional)
  wasmUrl?: string              // URL to verifykit_core_wasm_bg.wasm.
                                // Defaults to the copy in dist/, resolved
                                // relative to the bundle's own URL.
 
  // Theme
  theme?: {
    mode?: 'light' | 'dark' | 'system'
    overrides?: Record<string, string>
  }
 
  // Features
  features?: VerifyKitFeatures
 
  // Core config
  trustStore?: TrustStoreConfig
  plugins?: VerifyKitPlugin[]
 
  // i18n
  locale?: string
  translations?: Partial<TranslationStrings>
 
  // Callbacks
  onVerified?: (result: VerificationResult) => void
  onError?: (error: LoadError) => void
  onDocumentLoaded?: (info: { pageCount: number; fileName: string }) => void
  onPageChange?: (page: number) => void
  onZoomChange?: (scale: number) => void
  onThemeChange?: (mode: 'light' | 'dark') => void
}

Instance Methods

Once a document is loaded, the viewer instance exposes a full programmatic API.

ts
viewer.goToPage(5)          // Jump to page 5
viewer.goToNextPage()       // Next page
viewer.goToPreviousPage()   // Previous page
viewer.goToFirstPage()      // First page
viewer.goToLastPage()       // Last page
viewer.getCurrentPage()     // Returns current page number
viewer.getPageCount()       // Returns total page count

Zoom

ts
viewer.zoomIn()             // Zoom in one step
viewer.zoomOut()            // Zoom out one step
viewer.zoomTo(2.0)          // Set zoom to 200%
viewer.setFitMode('width')  // Fit to width ('none', 'width', 'page')
viewer.getZoom()            // Returns current zoom scale

Rotation

ts
viewer.rotateCW()           // Rotate 90 degrees clockwise
viewer.rotateCCW()          // Rotate 90 degrees counter-clockwise
viewer.getRotation()        // Returns current rotation in degrees
ts
viewer.openSearch()         // Open the search bar
viewer.closeSearch()        // Close the search bar
ts
viewer.print()              // Open print dialog
viewer.download()           // Download the PDF

Fullscreen

ts
viewer.enterFullscreen()    // Enter fullscreen mode
viewer.exitFullscreen()     // Exit fullscreen mode
viewer.toggleFullscreen()   // Toggle fullscreen

Signature Panel

ts
viewer.openSignaturePanel() // Open the signature details panel
viewer.closeSignaturePanel()// Close the signature panel

Theme

ts
viewer.setTheme('dark')     // Set theme ('light', 'dark', 'system')

Revalidation

ts
viewer.revalidate()             // Re-verify all signatures on the current buffer

State Inspection

ts
viewer.getVerificationResult()  // Returns VerificationResult or null
viewer.getSignatures()          // Returns PdfSignature[]

Event Handling

Subscribe to viewer events using viewer.on(event, callback). Each call returns an unsubscribe function.

ts
// Verification complete
viewer.on('verified', (result) => {
  console.log('Signatures:', result.signatures.length)
})
 
// Document loaded
viewer.on('documentLoaded', (info) => {
  console.log(`Loaded ${info.pageCount} pages: ${info.fileName}`)
})
 
// Page navigation
const unsub = viewer.on('pageChange', (page) => {
  console.log('Current page:', page)
})
 
// Zoom changes
viewer.on('zoomChange', (scale) => {
  console.log('Zoom:', Math.round(scale * 100) + '%')
})
 
// Theme changes
viewer.on('themeChange', (mode) => {
  console.log('Theme:', mode)
})
 
// Rotation
viewer.on('rotationChange', (rotation) => {
  console.log('Rotation:', rotation)
})
 
// Fullscreen
viewer.on('fullscreenChange', (isFullscreen) => {
  console.log('Fullscreen:', isFullscreen)
})
 
// Error (receives a LoadError object with name and message)
viewer.on('error', (error) => {
  console.error('Viewer error:', error.name, error.message)
})
 
// Unsubscribe
unsub()

Feature Flags

Toggle individual viewer features via the features option. Each feature defaults to the value shown:

ts
interface VerifyKitFeatures {
  search?: boolean       // default: true
  print?: boolean        // default: true
  download?: boolean     // default: false
  signatures?: boolean   // default: true
  thumbnails?: boolean   // default: true
  fullscreen?: boolean   // default: true
  themeToggle?: boolean  // default: true
  openFile?: boolean     // default: false
  rotation?: boolean     // default: true
  contextMenu?: boolean  // default: true
  properties?: boolean   // default: true
  shortcuts?: boolean    // default: true
  highlights?: boolean   // default: true
}

Example -- enable download and file opening, disable print:

ts
const viewer = VerifyKit.create(el, {
  workerUrl: 'https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs',
  features: {
    download: true,
    openFile: true,
    print: false,
  },
})

Internally, features flags are mapped to defaultLayoutPlugin({ disable: {...} }).

With File Input

A common pattern using a standard HTML file input:

html
<input type="file" id="file-input" accept=".pdf" />
<div id="viewer" style="width: 100%; height: 80vh;"></div>
 
<script src="https://verifykit.trexolab.com/cdn/verifykit.umd.js"></script>
<script>
  const viewer = VerifyKit.create(document.getElementById('viewer'), {
    workerUrl: 'https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs',
  })
 
  document.getElementById('file-input').addEventListener('change', (e) => {
    const file = e.target.files[0]
    if (!file) return
    file.arrayBuffer().then(buf => viewer.load(buf, file.name))
  })
</script>

With Core Plugins

Core verification plugins (like revocation checking) can be passed through to the WASM engine:

ts
import { create } from '@trexolab/verifykit-vanilla'
import { revocationPlugin } from '@trexolab/verifykit-plugin-revocation'
 
const viewer = create(document.getElementById('viewer'), {
  workerUrl: 'https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs',
  plugins: [revocationPlugin()],
  trustStore: {
    certificates: [myRootCaPem],
    mode: 'merge',
  },
})
 
const result = await viewer.load(pdfBuffer, 'document.pdf')
// Revocation checks now include live CRL/OCSP lookups

Self-Hosting

When self-hosting instead of using a CDN, copy the entire dist/ directory:

bash
cp -r node_modules/@trexolab/verifykit-vanilla/dist/ public/verifykit/

Then reference the files from your HTML:

html
<link rel="stylesheet" href="/verifykit/verifykit.css">
<script src="/verifykit/verifykit.umd.js"></script>

dist/ contains verifykit_core_wasm_bg.wasm, the verification engine — about 1.1 MB, and the largest thing in the package. It is a separate file rather than part of the bundle so the browser can compile it while it downloads and cache it apart from the JS, which changes far more often.

It is resolved relative to the bundle's own URL, so serving the two from the same directory needs no configuration — that is what copying the whole of dist/ above gets you. Set wasmUrl when they are split up:

ts
VerifyKit.create(el, {
  workerUrl: '/verifykit/pdf.worker.min.mjs',
  wasmUrl: '/verifykit/verifykit_core_wasm_bg.wasm',
})

Serve it as application/wasm. Browsers refuse to stream-compile anything else, and the engine falls back to a slower non-streaming path with a console warning naming the type it got instead.

Serving it from a different origin than the page needs CORS. A <script src> does not, but the bundle fetches the .wasm, so the host must send Access-Control-Allow-Origin. Without it the viewer loads normally and then fails when it tries to verify, which is a confusing way round. Give the JS and the .wasm the same cache lifetime as well: the wasm-bindgen glue and its binary are generated together and cannot be mixed across releases.

The worker is not in dist/ — it is not part of the package at all, so copying dist/ does not get you it. Download it alongside and point workerUrl at your copy, or the viewer still reaches out to a CDN at runtime:

bash
curl -o public/verifykit/pdf.worker.min.mjs \
  https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs
ts
VerifyKit.create(el, { workerUrl: '/verifykit/pdf.worker.min.mjs' })

The version must be 5.5.207 — see The PDF.js Worker.

Cleanup

Always call destroy() when the viewer is no longer needed. This cleans up store subscriptions, event listeners, and worker blob URLs:

ts
viewer.destroy()

In single-page applications, call destroy() when the viewer's container is removed from the DOM (e.g., on route change or component unmount).