VerifyKitv0.5.1

Troubleshooting

Common issues and solutions when working with the VerifyKit SDK.


WASM Loading Failures

Symptom: CompileError: WebAssembly.instantiate() or error during WASM initialization.

Possible causes and solutions:

  1. Content Security Policy (CSP) blocks WASM compilation. WebAssembly compilation requires permission in script-src. Add wasm-unsafe-eval to your CSP header:

    Content-Security-Policy: script-src 'self' 'wasm-unsafe-eval' blob:;
    
  2. Using a version older than 0.3.1. Prior to v0.3.1, the WASM binary was loaded as an external .wasm file, which required bundler configuration. Update to v0.3.1+ where the WASM binary is base64-embedded. No external .wasm file, no vite-plugin-wasm, no webpack asyncWebAssembly, and no import.meta.url issues.

  3. Custom setWasmUrl() pointing to a missing file. If you are using setWasmUrl() or wasmUrl config to load WASM from a custom location, ensure the file is accessible and served with application/wasm MIME type.

Note (v0.3.1+): The default WASM loading path is base64-embedded and requires no bundler configuration. If you are experiencing WASM loading issues and are on v0.3.1+, the most likely cause is CSP. Remove any older WASM bundler configuration (it is no longer needed).


Registry Not Configured

Symptom: npm ERR! 404 Not Found or npm ERR! code E404 when installing @trexolab packages.

Cause: npm does not know where to find the @trexolab scoped packages.

Solution: Configure .npmrc with the VerifyKit registry. Create a .npmrc file in your project root:

@trexolab:registry=https://verifykit.trexolab.com/api/registry

Or set it globally:

bash
npm config set @trexolab:registry https://verifykit.trexolab.com/api/registry

PowerShell users: Do not use > to create the .npmrc file -- it writes UTF-16 with a BOM, which npm cannot parse. Use [System.IO.File]::WriteAllText() instead:

powershell
[System.IO.File]::WriteAllText("$PWD\.npmrc", "@trexolab:registry=https://verifykit.trexolab.com/api/registry`n")

pdfjs-dist Version Mismatch

Symptom: Worker errors, rendering failures, or unexpected behavior after upgrading pdfjs-dist.

Cause: The pdfjs-dist version is pinned to 5.5.207. The worker and main library must match exactly, and newer versions may introduce breaking API changes or require additional polyfills not yet covered by VerifyKit.

Solution: Do not upgrade pdfjs-dist independently. If you see worker errors, check that the installed pdfjs-dist version matches the pinned version:

bash
npm ls pdfjs-dist

If the version does not match, reinstall:

bash
npm install pdfjs-dist@5.5.207

"workerUrl is required" Error

Symptom: Error message stating that workerUrl is required when initializing VerifyKitProvider or calling VerifyKit.create().

Cause: The workerUrl option is mandatory and was not provided in the configuration.

Solution: Pass the workerUrl option with the URL to the PDF.js worker script.

React:

tsx
<VerifyKitProvider config={{
  workerUrl: 'https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs',
  theme: { mode: 'system' },
}}>

Vanilla JS:

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

Worker Version Mismatch

Symptom: Worker errors, rendering failures, or unexpected behavior after changing the workerUrl.

Cause: The worker version does not match the pdfjs-dist version used by VerifyKit (5.5.207). The worker and main library must match exactly.

Solution: Use the recommended CDN URL which pins the correct version:

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

If self-hosting, copy the worker from the correct version of pdfjs-dist:

bash
cp node_modules/pdfjs-dist/legacy/build/pdf.worker.min.mjs public/

Worker Initialization Issues

Symptom: Blank viewer, console errors about worker initialization, or pdf.worker.min.mjs returning 404.

Possible causes and solutions:

  1. workerUrl is missing or incorrect. Ensure you are passing the workerUrl option with a valid URL to the PDF.js worker script. The recommended CDN URL is:

    https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs
    
  2. Self-hosted worker file is missing. If using a local path, ensure the file has been copied to your public directory:

    bash
    cp node_modules/pdfjs-dist/legacy/build/pdf.worker.min.mjs public/

    Then pass the local path:

    tsx
    <VerifyKitProvider config={{ workerUrl: '/pdf.worker.min.mjs' }}>
  3. CSP blocks the worker URL. If the CDN is blocked by your Content Security Policy, either self-host the worker or add the CDN origin to your CSP:

    Content-Security-Policy: script-src 'self' https://unpkg.com; worker-src 'self' https://unpkg.com;
    

Tailwind CSS Conflicts

Symptom: Viewer UI elements look broken -- buttons have no background, borders are missing, images are stretched, SVG icons display as block elements.

Cause: Tailwind's preflight (CSS reset) overrides default browser styles for button, img, svg, and border properties. Since the VerifyKit viewer relies on default browser styles for many elements, the reset breaks the UI.

Solution: Add CSS overrides scoped to .verifykit-root to restore the expected styles:

css
.verifykit-root img {
  display: inline;
  max-width: none;
}
 
.verifykit-root button {
  background: revert;
  border: revert;
  padding: revert;
  font: revert;
  color: revert;
  cursor: pointer;
}
 
.verifykit-root svg {
  display: inline;
}
 
.verifykit-root *,
.verifykit-root *::before,
.verifykit-root *::after {
  border-style: revert;
  border-width: revert;
}

Place these rules after Tailwind's @tailwind base import so they take precedence.


Signatures Showing as "unknown"

Symptom: Signature overallStatus is "unknown" and the certificate chain check fails, even though the PDF is validly signed.

Cause: The signing certificate does not chain to any of the 119 embedded AATL root CA certificates. This is common with enterprise or self-signed certificates.

Solution: Add your organization's root CA certificate via the trust store configuration:

typescript
const verifier = await createVerifier({
  trustStore: {
    certificates: [pemString],
    mode: 'merge', // adds your cert alongside the built-in 119 AATL certificates
  },
})

In React:

tsx
<VerifyKitProvider config={{
  workerUrl: 'https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs',
  trustStore: {
    certificates: [myCompanyRootCaPem],
    mode: 'merge',
  },
}}>

Use mode: 'replace' if you want only your certificates to be trusted and want to ignore the built-in AATL store entirely.


Signature Shows "valid" in Engine But "unknown" in UI

Symptom: sig.overallStatus is "valid" but the UI displays "Signature validity is unknown."

Cause: The UI layer uses getDisplayStatus() for Adobe Reader parity. When the chain is trusted but revocation can't be checked (common in browsers due to CORS blocking OCSP/CRL endpoints), the display status is downgraded to "unknown".

Solution: Install the revocation plugin to provide online OCSP/CRL checking via a server-side proxy:

bash
npm install @trexolab/verifykit-plugin-revocation

Or, in the React UI, use sig.overallStatus instead of getDisplayStatus(sig) if you want the raw engine result without Adobe parity adjustments.


Signature Shows as "Certified" When It Shouldn't

Symptom: A non-certified PDF shows "Certified by..." in the signature panel or document message bar.

Cause: The WASM engine serializes Option::None as undefined (not null). If your code checks sig.mdpPermission !== null, it misses undefined and treats the signature as certified.

Solution: Always use loose equality when checking mdpPermission:

ts
// Correct
if (sig.mdpPermission != null) { /* certified */ }
 
// Wrong — misses undefined
if (sig.mdpPermission !== null) { /* bug: treats undefined as certified */ }

Appearance Icon Not Showing in PDF

Symptom: The signature appearance in the PDF canvas doesn't show the verification status icon (green tick, red cross, yellow question mark).

Possible causes:

  1. Non-Acro6/non-Legacy signature field. Icons are only swapped for signature fields that use either the Legacy layer structure (has /n1) or the Acro6 layer structure (has /FRM + /n0). Simple appearance streams are left untouched.

  2. Acro6 field with blank n0. If the signing tool intentionally blanked the /n0 layer (no icon placeholder), the swapper respects this and doesn't inject an icon.

  3. swapSignatureAppearances() not called. The icon swap is a separate step after verification. Ensure you call it and use the modified PDF bytes for display.


SSR / Server-Side Rendering Issues

Symptom: ReferenceError: document is not defined, ReferenceError: window is not defined, or similar errors when rendering VerifyKit components on the server.

Cause: The viewer components require browser APIs (canvas, DOM, WebAssembly, Web Workers) that are not available in Node.js server environments.

Solution: In Next.js, use dynamic import with SSR disabled:

typescript
import dynamic from 'next/dynamic'
 
const Viewer = dynamic(() => import('./MyViewer'), { ssr: false })

Ensure the component that imports @trexolab/verifykit-react is marked as a client component:

tsx
// components/MyViewer.tsx
'use client'
 
import { VerifyKitProvider, Viewer } from '@trexolab/verifykit-react'
import '@trexolab/verifykit-react/styles.css'
 
// ... your viewer component

The headless @trexolab/verifykit-core package works in Node.js without restrictions -- only the viewer UI components require a browser environment.


Dark Mode Not Working

Symptom: Theme does not switch, or the viewer always renders in light mode.

Possible causes and solutions:

  1. <VerifyKitProvider> is not wrapping your viewer components. The provider sets the data-theme attribute on <html>. Without it, theme switching has no effect.

  2. Theme mode is not configured. Set the theme mode explicitly:

    tsx
    <VerifyKitProvider config={{
      workerUrl: 'https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs',
      theme: { mode: 'system' },
    }}>

    Valid values: 'light', 'dark', 'system' (follows OS preference).

  3. Custom CSS does not target the correct attribute. For CSS variable theming, target the data-theme attribute on .verifykit-root:

    css
    .verifykit-root[data-theme="dark"] {
      --bg: #1a1a1a;
      --fg: #e0e0e0;
    }
  4. Multiple viewers conflict. The theme system sets data-theme on <html>, which is global. Multiple VerifyKitProvider instances with different theme modes will conflict. Use a single provider or ensure all viewers use the same theme mode.


Performance Issues

Symptom: Slow initial load, laggy scrolling, or high memory usage.

Possible causes and solutions:

  1. First WASM load takes ~100-200ms. This is expected. The WASM module is cached in memory after the first load, so subsequent calls to createVerifier() are instant. For the best user experience, initialize the verifier early (e.g., on app startup).

  2. Large PDFs (100+ pages) may take longer to render. Use defaultLayoutPlugin() which includes lazy rendering by default -- only visible pages are rendered.

  3. Memory grows in single-page applications. Always call destroy() when removing a vanilla viewer instance. For React, ensure the viewer unmounts properly during navigation. If using the vanilla API inside a React useEffect, return a cleanup function:

    typescript
    useEffect(() => {
      const v = VerifyKit.create(containerRef.current!, {
        workerUrl: 'https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs',
      })
      v.load(buffer, 'doc.pdf')
      return () => v.destroy()
    }, [])

TypeScript "Cannot find module" Errors

Symptom: TypeScript reports Cannot find module '@trexolab/verifykit-react' or Cannot find module '@trexolab/verifykit-react/styles.css'.

Possible causes and solutions:

  1. TypeScript version or module resolution is outdated. Ensure you are using TypeScript 5.0+ with moduleResolution: "bundler" in your tsconfig.json:

    json
    {
      "compilerOptions": {
        "moduleResolution": "bundler"
      }
    }

    The packages export proper type definitions via the exports field in package.json, which requires "bundler" or "node16" module resolution.

  2. CSS import not recognized. TypeScript does not resolve .css imports by default. Add a type declaration:

    typescript
    // src/global.d.ts
    declare module '*.css' {}

"Cannot use import statement outside a module" in Node.js

Symptom: SyntaxError: Cannot use import statement outside a module when running @trexolab/verifykit-core in Node.js.

Cause: Your Node.js project is configured for CommonJS, but the package uses ES module syntax.

Solution: Either:

  • Add "type": "module" to your package.json, or
  • Ensure your bundler resolves require('@trexolab/verifykit-core') to the CJS entry point, or
  • Rename your file to .mjs

Revocation Check Shows "Not checked (offline)"

Symptom: revocationCheck.status is "unknown" and revocationCheck.detail says "Not checked (offline)."

Cause: The base @trexolab/verifykit-core does not perform online revocation checking. It only reads embedded CRL/OCSP data from the PDF.

Solution: Install and configure the revocation plugin:

bash
npm install @trexolab/verifykit-plugin-revocation
typescript
import { createVerifier } from '@trexolab/verifykit-core'
import { revocationPlugin } from '@trexolab/verifykit-plugin-revocation'
 
const verifier = await createVerifier({
  plugins: [revocationPlugin()],
})

"VerifyKit viewer failed to initialize within 10 seconds"

Symptom: Timeout error when calling viewer.load() on a vanilla instance.

Cause: The internal React root did not mount in time. This can happen if:

  • The container element is not visible or is detached from the DOM
  • The WASM module failed to load silently
  • JavaScript execution is blocked

Solution:

  • Ensure the container element is attached to the document and visible before calling VerifyKit.create()
  • Check the browser console for WASM loading errors
  • Verify that .wasm files are accessible from your hosting environment

CJK Text Not Rendering

Symptom: CJK (Chinese, Japanese, Korean) characters display as squares or boxes.

Cause: PDF.js requires CMap files for CJK text rendering. These files are not bundled by VerifyKit.

Solution: Copy CMap files from pdfjs-dist to your public directory:

bash
cp -r node_modules/pdfjs-dist/cmaps public/cmaps
cp -r node_modules/pdfjs-dist/standard_fonts public/standard_fonts

AIA Certificate Fetching Failures

Symptom: Certificate chain check fails with unknown status, even though the signer's root CA is in the trust store. The PDF does not embed intermediate certificates.

Cause: AIA (Authority Information Access) resolution is enabled by default, but the AIA URL may be unreachable due to network restrictions, firewalls, or DNS issues.

Possible solutions:

  1. Check network access. Ensure outbound HTTP is allowed to CA infrastructure URLs. Test with curl <aia-url>.

  2. Inspect the certificate's AIA URLs. Check cert.caIssuersUrls in the verification result:

    ts
    const result = await verifier.verify(buffer)
    for (const sig of result.signatures) {
      console.log('CA Issuers:', sig.signerCertificate?.caIssuersUrls)
    }
  3. Disable AIA for air-gapped environments. If no outbound network access is available, disable AIA and ensure PDFs embed the full chain:

    ts
    const verifier = await createVerifier({ enableAIA: false })
  4. Timeout issues. AIA fetches use a default timeout. If endpoints are slow, the fetch may time out silently. Check browser or Node.js console for network errors.


Encrypted or Password-Protected PDFs

Symptom: The viewer prompts for a password, or verification fails with an error about encrypted content.

Cause: The PDF is encrypted with a user or owner password. VerifyKit can verify signatures in encrypted PDFs, but requires the correct password to render the document.

Possible solutions:

  1. Enter the password when prompted. The React viewer shows a <PasswordDialog> automatically when an encrypted PDF is loaded. Enter the user password to unlock the document.

  2. Programmatic password supply. When using the headless core API, pass the password as part of the input:

    ts
    // Encrypted PDFs are supported for signature verification
    // The WASM engine can extract and verify signatures from
    // encrypted PDFs without needing the password.
    const result = await verifier.verify(buffer, 'encrypted.pdf')
  3. Owner password only. If the PDF has an owner password (for permission restrictions) but no user password, it can be opened without a password. The permissions object in the verification result will reflect the restrictions.

  4. Check permissions. Use extractPdfMetadata() to inspect the encryption status:

    ts
    const { permissions } = await extractPdfMetadata(buffer)
    console.log('Encrypted:', permissions.encrypted)
    console.log('Method:', permissions.encryptionMethod)

"structuredClone is not defined"

Symptom: ReferenceError: structuredClone is not defined at runtime.

Cause: Your browser or Node.js version is too old. structuredClone requires Node.js 17+ or a modern browser from 2022+.

Solution: Upgrade to Node.js 20+ or a supported browser (Chrome 109+, Firefox 115+, Safari 16.4+, Edge 109+). There is no polyfill path for structuredClone in VerifyKit.