VerifyKitv0.13.3

Migration Guide

This guide covers breaking changes, deprecated APIs, and upgrade paths between VerifyKit SDK versions.


Deprecated APIs

The following APIs in @trexolab/verifykit-core are retained for backward compatibility but should no longer be used in new code.

computeUnsignedFields(signatures, detectedFields?)

Status: Stub — always returns an empty array.

The WASM engine does not expose unsigned field detection through this function. Unsigned signature fields are now reported directly in the VerificationResult when present.

Migration: Remove calls to computeUnsignedFields. If you need to detect unsigned form fields, inspect the VerificationResult returned by verifyPdf() or verifier.verify().

getTrustStore()

Status: Stub — returns an object with empty arrays.

Trust store contents are managed internally by the WASM engine and are no longer exposed to JavaScript at runtime.

Migration: Remove calls to getTrustStore(). To configure the trust store, use setTrustStoreConfig() or pass a trustStore config to createVerifier().

ensureCryptoEngine()

Status: No-op.

The Rust/WASM engine ships its own cryptographic implementation and does not depend on the Web Crypto API. This function existed for the legacy JavaScript engine and now does nothing.

Migration: Remove calls to ensureCryptoEngine(). No replacement is needed.


Version History

v0.11.0 to v0.12.0

Release: the engine ships as a .wasm file instead of a base64 string

Drop-in for anyone using a bundler. Vite, webpack, Next.js, Rollup and Parcel resolve the .wasm from the reference the engine makes and emit it as a hashed asset with no configuration — the same "nothing to do" as before, arrived at differently. Node, Deno and Bun read it from the installed package. Nothing in the API changed and no call sites move.

Between v0.3.1 and v0.11.0 the binary was base64-encoded into the JavaScript. A string cannot be split out of the chunk that holds it, so every route importing the package carried the whole 1.5 MB engine whether or not it verified anything, and the bytes had to be decoded before compilation could start. As a file it is 1.1 MB rather than 1.5 MB, compiles while it downloads, and is cached separately from the JS.

What to check:

  1. Your server serves .wasm as application/wasm. Most do. If yours does not, verification still works but the browser cannot stream-compile, and logs a warning naming the content type it received instead.
  2. Anything copying build output by extension. A deploy step that copies *.js and *.css out of a build directory will now leave the engine behind.
  3. CSP. Unchanged in practice — wasm-unsafe-eval was already required — but the binary is now fetched from your own origin rather than read from a string, so a restrictive connect-src needs to allow it.

If you serve the vanilla build from a CDN, dist/ now contains verifykit_core_wasm_bg.wasm and it must be uploaded alongside verifykit.umd.js. Kept in the same directory it is found automatically; otherwise pass wasmUrl to VerifyKit.create(). In exchange verifykit.umd.js drops from 2.9 MB to 1.3 MB, and the engine is fetched only when a document is verified.

Packaging changes in the same release. @trexolab/verifykit-core gained a node export condition, so bundlers that read neither exports nor browser and fall back to main now get the Node build; any bundler from the last several years reads one of them. Sourcemaps are no longer published — they were shipping the SDK's original TypeScript.

v0.10.0 to v0.11.0

Release: the horizontal wheel follows the pages, and 'wrapped' is gone from ScrollMode

Drop-in unless you name 'wrapped'. Every other scroll mode, and every other API, is unchanged.

What changes for you:

  • ScrollMode no longer includes 'wrapped'. The union is now 'vertical' | 'horizontal' | 'page'. If you call setScrollMode('wrapped'), pass initialState={{ scrollMode: 'wrapped' }}, or hold the value in your own state, TypeScript will now reject it — switch to 'vertical', which is what wrapped rendered as in every case a reader was likely to see.

    It went because it could not be told apart from vertical. Wrapped flowed pages left-to-right and broke onto a new row when the column ran out, so the number of pages per row was decided by the zoom — and at the fit-width zoom the viewer opens on, a page is by definition as wide as its column, so exactly one fits. You had to zoom out to roughly 60% before a second page joined the row. A mode that is invisible until you go looking for it is not a mode, and the thumbnail sidebar already gives the overview a page grid was there to provide.

    Nothing throws if the string reaches the viewer anyway — from persisted user settings, say. An unrecognised scroll mode lays out as vertical, so a stale 'wrapped' degrades rather than breaks, and you can migrate stored values at your own pace.

  • In 'horizontal', a plain wheel now scrolls across and Shift+wheel scrolls down. Previously the wheel followed the browser's default, so on a page zoomed taller than the window a plain wheel scrolled down and never advanced through the document. In a layout that reads left-to-right that felt like a broken wheel. The axes are now: plain wheel moves through the pages, Shift+wheel moves within a tall page, Ctrl/Cmd+wheel still zooms.

    This is a deliberate departure from pdf.js, which does no wheel remapping at all. It applies only in horizontal mode — vertical and single-page keep the platform behaviour untouched, as does every other scroll container in your app. If you had built your own wheel handling on top of horizontal mode, remove it or the two will fight over the same event.

v0.8.0 to v0.9.0

Release: pinch-to-zoom on touch devices, and the legacy PdfViewer is gone

Drop-in unless you import PdfViewer or usePinchZoom. Both were removed; everything else is unchanged and takes the same arguments.

What changes for you:

  • Two-finger pinch zoom now works on touch devices. This is new behaviour, not a repair: Viewer / CoreViewer never had any pinch handling. A pinch landed on the scroll container, whose touch-action: pan-x pan-y also suppresses the browser's own pinch, so nothing happened at all. zoomPlugin now installs the gesture, which means defaultLayoutPlugin and the vanilla and Vue packages get it with no change on your side.

    It honours the plugin's existing minScale / maxScale, zooms about the midpoint between the fingers, and sets fitMode to none the same way Ctrl+wheel does. One-finger scrolling is untouched — the touchmove listener is bound only while two fingers are down, so ordinary scrolling stays on the compositor.

    If you built your own pinch handling on top of the viewer to work around its absence, remove it: two handlers will fight over the same gesture.

  • PdfViewer and PdfViewerHandle were removed. The monolithic viewer was superseded by the plugin-based Viewer / CoreViewer, and by 0.8.0 nothing in the SDK, the demo, or the documented quick-start rendered it any more — the vanilla package included. Keeping it meant shipping ~2,100 lines of a second viewer that received no fixes: the pinch code above lived there, on a path nothing executed. See Legacy PdfViewerViewer / CoreViewer below for the swap; it is a two-line change in most apps.

  • usePinchZoom was removed. It was only ever imported by PdfViewer, and it did not work: it set touch-action: none after the second finger was already down, which the browser latches at the start of a gesture and so ignores. There is no replacement hook — zoomPlugin handles the gesture, so nothing is left to wire up.

  • SigWidget, Highlight, ScrollMode, SpreadMode and CursorTool are unchanged. They were declared elsewhere and merely re-exported through PdfViewer; they are still exported from the package root under the same names.

v0.7.3 to v0.8.0

Release: the right-click menu obeys disable, and toolbar menus take clicks over the signature sheet

Drop-in for most consumers. Nothing was removed from the public API and nothing you call takes different arguments. Two changes are the reason this is a minor rather than a patch, and both are confined to code written against the plugin API.

What changes for you:

  • The context menu no longer offers actions whose plugin is not installed. It used to draw a hardcoded list — Save As…, Print, the zoom entries, Rotate, Find, Fullscreen, Document Properties, Keyboard Shortcuts — and call those actions itself, so defaultLayoutPlugin({ disable: { download: true } }) removed the toolbar button, the overflow-menu entry and Ctrl+S while right-click → Save As… went on writing the full PDF to disk. Entries now come from the plugins that own them, exactly as the overflow menu's have since 0.7.0.

    If you disabled a plugin to take a feature away, you were not getting what you configured before and you are now; no action needed. If you were relying on a context-menu entry while its plugin was disabled, install the plugin. Copy Selected Text is drawn by the menu itself and is always present — it is the browser's own clipboard and no feature toggle governs it.

    toolbar: { download: false } is a separate knob and is unchanged: it hides the button and nothing else, because it says where a control appears, not whether the feature exists.

  • The context menu's order shifts. Both menus now sort by the same menuOrder / menuGroup a plugin already declares, so an action holds the same relative place on both surfaces. Find in Document moves to the top, Copy Selected Text sits in a group of its own, and Save As… comes before Print. Nothing was removed. If you assert on menu order in a test, re-record it.

  • ViewerPluginContext gained a required getPlugins() member. It returns every plugin resolved into the viewer, composed sub-plugins included. A plugin that only receives the context in install(ctx) is unaffected and needs no change. If you build a ViewerPluginContext object literal by hand — a test double, most likely — TypeScript will now ask for the method:

    ts
    const ctx: ViewerPluginContext = {
      store,
      registerShortcut,
      // …
      getPlugins: () => [],   // ← add this
    }
  • New optional plugin API: renderContextMenuItems. Additive — existing plugins compile and run unchanged. Implement it if you want your plugin to appear on right-click:

    tsx
    renderContextMenuItems: ({ onClose, store }) => (
      <MenuItem label="My Thing" onClick={() => { doIt(store); onClose() }} />
    )

    The props are deliberately smaller than renderMenuItems': nothing can overflow into the context menu, so there is no isOverflowed to ask. ContextMenuItemRenderProps, ContextMenuItemsRender and collectContextMenuItems are exported for typing and composing it.

  • The toolbar now sits above the signature sheet (z-index: 30 inline, against the sheet's 20). This is the fix for menus that drew over the sheet on a narrow viewer and did nothing when tapped: a z-index on the toolbar makes it a stacking context, which pinned every popover inside it — the More menu, the search popover, the zoom and text-size dropdowns — to the toolbar's old level of 10, below the sheet. The toolbar's own box never overlaps the sheet, so nothing moved visually. If you overrode z-index on .verifykit-toolbar or .verifykit-sig-panel, re-check it: the sheet has to stay below the toolbar or it takes the clicks back. Dialogs (50–100), the right-click menu (50) and tooltips (99999) are still above both.

v0.7.2 to v0.7.3

Release: upgrading the viewer now upgrades the engine with it

Drop-in. No API changed. What changed is how the packages ask npm for each other, and the reason it matters is that the old way could leave you running a version you did not think you were running.

Up to 0.7.2, @trexolab/verifykit-react declared its dependency on @trexolab/verifykit-core as "*" (as did @trexolab/verifykit-plugin-revocation; @trexolab/verifykit-vanilla did the same for its dependency on the React package). Every version satisfies "*", so if your project already had an older core installed, npm kept it and upgraded only the shell:

├── @trexolab/verifykit-core@0.5.13        ← left untouched
└─┬ @trexolab/verifykit-react@0.7.2
  └── @trexolab/verifykit-core@0.5.13 deduped

Nothing failed. The shell imports 18 symbols from core and all 18 exist in 0.5.13, so it loaded and ran, and the About dialog shows the React package's own version — it read 0.7.2. But verification was being done by the 0.5.13 engine, without the core-side fixes released since: DocMDP/certified detection (0.6.3) and the Node ESM entry (0.6.5).

Only fixes that landed in core were lost this way. Anything fixed in the React package — the DocumentPermissions mapping in 0.6.8, for instance — came through normally, because that is the package being upgraded.

From 0.7.3 each of those edges is a real range (^0.7.3), so npm fetches a matching engine instead of reusing whatever it finds.

What to do. Nothing, if you install fresh — a clean install always resolved correctly, so this never affected you. If you are upgrading a project that has been on VerifyKit for a while, check what you actually ended up with:

bash
npm ls @trexolab/verifykit-core

If the version there is behind your viewer, upgrading to 0.7.3 fixes it. On a release before 0.7.3 you have to force it by hand:

bash
npm install @trexolab/verifykit-core@0.7.3

Tarballs published before 0.7.3 still carry "*" — they are unchanged, so pinning to one keeps the old behaviour.

One thing to know about the range. It is ^, not an exact pin. On a 0.x version ^ locks the minor, which is where this SDK makes its breaking changes, and it lets a project already on a compatible core keep the single copy it has. If you deliberately pin core to an older minor while installing a newer viewer, npm will now give the viewer its own nested copy — which is what makes the viewer correct, but it does mean two engines in the tree, each with its own trust store and algorithm policy. npm ls @trexolab/verifykit-core will show both. If you configure the trust store, import setTrustStoreConfig from @trexolab/verifykit-react rather than from core directly; the React package re-exports it, and that way you are configuring the engine the viewer is using.

v0.7.1 to v0.7.2

Release: the documented plugin API is now the exported one

Drop-in. No runtime behaviour changed and nothing was removed — this release only adds exports. It is worth reading anyway, because if you followed the plugin docs before now, three things you were told will not have compiled.

  • Type the viewer context as ViewerPluginContext, not PluginContext. The package exports two: the core engine's under the plain name, and the viewer's under ViewerPluginContext. A plain import { PluginContext } resolves to the core one, which has .config and no .store, so ctx.store fails to type-check. The docs said PluginContext throughout; they now say ViewerPluginContext.

    ts
    // Before — resolves to the core context; ctx.store does not exist
    import type { PluginContext } from '@trexolab/verifykit-react'
    // After
    import type { ViewerPluginContext } from '@trexolab/verifykit-react'
  • The store has no setState, and subscribe takes a key first. Samples taught both. The real API:

    ts
    store.update({ currentPage: 5 })                       // not setState
    store.subscribe('currentPage', (page, prev) => { … })  // key first, then that key's values
    store.subscribeAll((state, prev) => { … })             // every change, whole snapshots

    Note the two listeners differ in more than arity: subscribe hands you the one key's new and old value, while subscribeAll hands you the whole state before and after — it does not tell you which key changed.

  • ZoomChangeEvent.fitMode is 'none' | 'width' | 'page'. It was documented with literals belonging to the scroll and spread unions. 'page' and 'none' appear in more than one view-mode union and do not mean the same thing in each.

  • Four plugin types are now importable: SidebarTabProps, MenuItemRenderProps, MenuItemsRender and CollectedMenuItems. They were always part of the API surface — renderMenuItems has received MenuItemRenderProps since 0.7.0 — but had no exported name, so annotating the parameter failed with TS2724. If you worked around this with any or a local copy of the shape, you can now import the real type.

  • The menu primitives are now importable: MenuItem, MenuDivider, MenuSection and MenuContainer. Use them in renderMenuItems so your entry matches the built-in ones.

    tsx
    import { MenuItem } from '@trexolab/verifykit-react'
     
    renderMenuItems({ isOverflowed, onClose }) {
      if (!isOverflowed('Download')) return null
      return <MenuItem label="Download" hint="Ctrl+S" onClick={() => { save(); onClose() }} />
    }
  • If you override --btn-size or --toolbar-height, override the bases instead. Both are derived — calc(var(--btn-base) * var(--ui-scale, 1)) — and the base is reassigned by pointer type and viewport height (26/36 default, 40/48 coarse pointer, 24/32 short viewport, 36/42 both). Setting the derived variable wins on desktop and then loses to the media rules on a phone. Set --btn-base and --toolbar-h-base, or --ui-scale to move everything at once. Nothing changed in the stylesheet here; the docs were describing it wrongly.

  • Node.js users: the revocation plugin export is revocationPlugin, and direct CRL/OCSP mode is selected by omitting endpoint. A blog post showed createRevocationPlugin({ mode: 'direct' }); neither the export nor the option exists.

v0.7.0 to v0.7.1

Release: the page fits the column it renders into, not the viewer

Drop-in. No API changed. One behaviour is broader than it was in 0.7.0: a viewer may now adopt Fit Width at any width, not only on a narrow viewer.

  • Auto fit-width is decided by the document column, not the viewer breakpoint. The space a page has is what is left after the sidebar and the signature panel take theirs, which can be far less than the viewer: at 1024×768 the viewer is 644px — md, not narrow by any measure — while the column is 336px for a 735px page. 0.7.0 fitted only xs/sm viewers and so left 419px of that page out of view. The test is now the page against its own column, re-run when the column changes — on resize, or when the signature panel opens.

    Nothing here overrides a zoom you named. Pass initialState.scale or initialState.fitMode (initialScale / initialFitMode on the legacy <PdfViewer>) and auto-fit does not run at all. Without one, the viewer opens at 120% as before and only fits when the page does not fit; once the reader zooms, the zoom is theirs and auto-fit stays out of it.

v0.6.8 to v0.7.0

Release: every enabled control reachable at every width, disabled plugins actually disabled

Drop-in for most consumers. No API was removed and nothing you call takes different arguments. One behaviour change is deliberate and is the reason this is a minor rather than a patch.

What changes for you:

  • The overflow menu no longer offers actions whose plugin is not installed. It used to call downloadPdf, print, toggleFullscreen and Document Properties directly out of actions, independently of which plugins you had installed — so a viewer configured with the Download plugin disabled still handed the user a working Download entry. Menu entries now come from the plugins that own them, so disabling a plugin removes its menu entry too.

    If you were relying on that, install the plugin. If you disabled Download precisely to prevent saving, you were not getting what you configured before and you are now; no action needed. This is the one change that can remove a control you were counting on.

  • Toolbar buttons are no longer hidden by viewport width. The toolbar measures itself and moves what does not fit into the overflow menu. If you wrote CSS against .verifykit-toolbar__collapsible or the :nth-child(n+3) { display: none } rule to control what collapsed, those rules are gone — delete your overrides. Overflowed slots are hidden inline and carry a [data-slot] wrapper you can select instead.

  • Width-dependent styling keys off the viewer's box, not the window. .verifykit-viewer carries data-breakpoint="xs|sm|md|lg" (under 400 / under 560 / under 840 / rest), fed by a ResizeObserver. If you had @media (max-width: …) rules targeting viewer internals, switch them to .verifykit-viewer[data-breakpoint="xs"] …. The upside is that an embedded viewer in a narrow column now adapts on a wide screen, which a media query could never do. Height still uses media queries — the viewer fills it.

  • Touch targets grow on any coarse pointer, including phones. Buttons go to 40px and the toolbar to 48px where they previously stayed at 26px/36px below 769px. Chrome is taller on touch devices; if you sized a container around the old toolbar height, re-check it.

  • The signature panel is a bottom sheet on narrow viewers. It opens at 35% of the viewer height instead of a fixed 70%, and the drag grip is visible and usable where it used to be hidden. If you snapshot-test the panel on a narrow viewport, expect a different height and an extra grip element.

  • Narrow viewers default to fit-width. Only when you have not set fitMode. An explicit fitMode in initialState is still respected exactly as before.

  • New optional plugin API: renderMenuItems. Additive — existing plugins compile and run unchanged. Implement it if you want your plugin to appear in the overflow menu when its toolbar slot does not fit:

    tsx
    renderMenuItems: ({ isOverflowed, onClose, store }) =>
      isOverflowed('MyThing') ? (
        <MenuItem label="My Thing" onClick={() => { doIt(store); onClose() }} />
      ) : null

    Note isOverflowed returns false for a slot that was never registered: if the consumer removed it through toolbar.transform, they do not want it in the menu either. A plugin with no toolbar slot at all should skip the check and always contribute.

  • ToolbarSlotProps gained two optional fields (overflowSlots, renderMenuItems) and ViewerStoreState gained breakpoint and viewerHeight. Both are additive. If you construct a ViewerStoreState literal by hand rather than starting from INITIAL_STATE, TypeScript will now ask for the two new fields.

v0.6.7 to v0.6.8

Release: the Printing and Copying rows work in both viewers, and DocumentPermissions is filled in

Drop-in. No API changed. What changes is what the Document Properties dialog shows, and what a DocumentPermissions value coming out of the viewer contains.

What changes for you:

  • <PdfViewer> now shows Printing and Copying at all. The two rows were wired to a state value nothing could set, so they were absent from every document. If you were reaching past the dialog to display permissions yourself, you no longer have to.
  • Unencrypted documents show "Allowed" for both rows instead of showing neither. A PDF with no /Encrypt dictionary restricts nothing; the dialog used to omit the rows as if the answer were unknown. If you snapshot-test the dialog, expect two more rows on unencrypted files.
  • DocumentPermissions from the viewer now has all ten fields. It carried only printing and copying, with the rest undefined behind a cast. Code that read permissions.modification, .formFilling, .accessibility, .assembly, .annotations, .encrypted or .permissionFlags was getting undefined and, if it treated that as a boolean, reading every one of them as forbidden. Those fields now hold the real values, so such code will start behaving differently — correctly, but differently.
  • encryptionMethod from the viewer is a filter name, not an algorithm. pdf.js exposes /Filter ("Standard") but not /V, so this reads "Standard", "None" for unencrypted documents, or "Unknown". The fuller string — "Standard (AES)", "Standard (RC4-128)" — comes from the WASM extractor, via useVerification().permissions.

v0.6.6 to v0.6.7

Release: CertificateViewer crash fix, real document permissions, lint enforced

Drop-in for JavaScript consumers. One TypeScript signature changed; nothing you call behaves differently unless you were hitting one of the bugs below.

What changes for you:

  • extractSignaturesFromPdf() returns RawPdfSignature[], not any[]. The runtime value is identical — the shape was always fixed by the Rust side — but it is now described by an exported interface. If you assigned the result to a hand-written type, TypeScript will now check that assignment instead of waving it through. Import the type if you need to name it:

    ts
    import type { RawPdfSignature } from '@trexolab/verifykit-core'

    If your own type disagrees with the real shape, that is the error telling you so. Delete yours and use RawPdfSignature.

  • <CertificateViewer certs={[]} /> no longer crashes when you populate it. If you render this public export before your certificates have loaded, you were getting Rendered more hooks than during the previous render. and probably working around it with a hasCerts && guard. The guard is now unnecessary; it is also harmless to keep.

  • Document Properties may now show different permission text. Documents that forbid printing showed "Allowed"; they now show "Not Allowed", and print-at-low-resolution-only documents show "Low resolution only" rather than collapsing into a yes/no. If you snapshot-test that dialog, expect the change.

  • decompressFlate() can now return bytes where it used to return null. Headerless deflate streams were never actually attempted. Callers that treated null as "not compressed" will now receive the decompressed content instead.

  • Nothing to do about the lint change. npm run lint is at zero errors and CI enforces it; that affects contributors to this repository, not consumers.


v0.6.5 to v0.6.6

Release: ./package.json is exported; Node consumer tests in CI

Drop-in — no API changes. Package metadata and repository tooling only; the viewer and the verification engine are byte-for-byte what 0.6.5 shipped.

What changes for you:

  • require('@trexolab/verifykit-core/package.json') works. It previously threw ERR_PACKAGE_PATH_NOT_EXPORTED — a package with an exports map has to list that subpath explicitly, and it was missing. If a bundler plugin or a version-reporting tool failed on your setup for that reason, it will now resolve. Applies to all four packages.
  • Nothing else to do. No imports, props, or behaviour changed.

v0.6.4 to v0.6.5

Release: the core loads under plain Node.js

Drop-in — no API changes. Only @trexolab/verifykit-core changed; the other three packages are republished at 0.6.5 with no code changes so the versions stay aligned.

What changes for you:

  • @trexolab/verifykit-core now initialises under Node.js, Deno and Bun. The built bundle imported its wasm-bindgen glue without a file extension. Bundlers resolve that; Node's ESM resolver does not, so await initWasm() — and with it createVerifier() and verifyPdf() — threw ERR_MODULE_NOT_FOUND: Cannot find module '…/pkg/verifykit_core_wasm' for any consumer not running the package through a bundler. Every release up to and including 0.6.4 is affected; if you verify signatures in a Node service, a CLI, a test runner that does not bundle, or a serverless function, upgrade.
  • Browser and framework integrations are unaffected and always were — Vite, webpack and Next.js all resolved the extensionless specifier. If the viewer works for you today, this release changes nothing about it.
  • The console no longer logs using deprecated parameters for the initialization function on every WASM initialisation. If you were filtering that warning out of your logs, you can drop the filter.

v0.6.3 to v0.6.4

Release: certification banner fits the space it has

Drop-in — no API changes. Only @trexolab/verifykit-react changed; the core, vanilla and revocation packages are republished at 0.6.4 with no code changes so the four versions stay aligned.

What changes for you:

  • The certification banner no longer overflows the "Signature Panel" button. Its message was white-space: nowrap without min-width: 0, so as a flex item it refused to shrink and ran underneath the button. Latent since the bar was written; visible from 0.6.3, when the Adobe-worded sentence reached ~140 characters.

  • The banner picks its wording to fit the width it has. Rather than clipping the sentence mid-email, it measures three complete phrasings and renders the longest that fits:

    Sentence
    fullCertified by <CN> <email>, <OU>, certificate issued by <issuer CN>.
    compactCertified by <CN>, <OU>.
    minimalCertified by <CN>.

    The permission sentence is dropped before the message loses characters, and the untrimmed text is always on the element's title.

If you render <DocumentMessageBar> yourself, its props are unchanged — but the text it renders now depends on the width of the bar, so a test asserting the exact banner string should either set a wide viewport or read the title instead. The signature panel is unaffected.


v0.6.2 to v0.6.3

Release: DocMDP certification detected regardless of key order

Drop-in — no API changes. Some documents that reported as merely "signed" will now correctly report as certified.

What changes for you:

  • Certified (DocMDP) documents written by iText and similar tools are now recognised. The permission extractor required /TransformParams to follow /DocMDP immediately; PDF dictionaries are unordered, and iText writes /Reference[<</TransformMethod/DocMDP/Type/SigRef/TransformParams<</P 1…>>…]. Those files read as uncertified: mdpPermission stayed null, the message bar showed "Signed and all signatures are valid.", and the panel's CERTIFIED badges never appeared.
  • PdfSignature.mdpPermission is now populated for those documents (1, 2 or 3). If your code branches on it, a document that previously took the "not certified" path may now take the certified one — that is the fix, but it is worth checking any snapshot tests or analytics that counted certified documents.
  • The banner wording changed to match Adobe, from Certified by <name>. to Certified by <CN> <email>, <OU>, certificate issued by <issuer CN>. Every part degrades independently, so a certificate without an email or organisation still yields a clean sentence. See 0.6.4 above for how it adapts to width.

Signature validity is unaffected. apply_doc_mdp_rules only invalidates a P=1 certification when the certifying signature does not cover the whole file, and that is checked independently of this extraction — no document changes from valid to invalid because of this release.


v0.6.1 to v0.6.2

Release: registry keeps every version; docs corrections

No SDK code changed. Installing 0.6.2 gets you the same viewer as 0.6.1 — the fixes are to publishing infrastructure and documentation.

What changes for you:

  • Pinning an older version now works. The registry previously served only the newest release, so npm install @trexolab/verifykit-react@0.6.0 returned 404 as soon as 0.6.1 shipped. Every published version is now available, and 0.5.13, 0.5.14 and 0.6.0 have been restored. A plain npm install still gives you the latest.
  • pdfjs-dist is no longer pulled in through the registry. The manifest kept advertising it as a dependency of @trexolab/verifykit-react after v0.6.0 bundled it, so resolving through the registry installed ~10 MB of pdf.js the viewer does not use. If you upgraded to 0.6.x already and still see pdfjs-dist in your lockfile with nothing else depending on it, delete the lockfile entry and reinstall.

Each version now advertises the dependencies it actually shipped with, so 0.5.x correctly declares pdfjs-dist and 0.6.x does not.


v0.6.0 to v0.6.1

Release: correct signature Field Name on encrypted PDFs

Drop-in — no breaking changes.

What changes for you:

  • Signature Properties no longer shows ciphertext as the Field Name. On a password-protected PDF the "Field Name" row rendered mojibake such as {:žhe*K÷#ΘoIT{…. A signature field's AcroForm /T is encrypted in an encrypted document and the Rust/WASM core cannot decrypt PDF strings, so PdfSignature.fieldName carries raw ciphertext there. The dialog now displays the name PDF.js reports for the signature's widget, already decrypted, and omits the row when no decrypted name is obtainable rather than printing bytes.
  • PdfSignature.fieldName is deliberately unchanged. appearance-swapper matches it against the /T it reads through pdf-lib — both encrypted — and that pairing is what lets the on-page icon swap resolve by name on encrypted documents. Do not "fix" it in your own code by decrypting it.
  • SignatureDetailsTab accepts an optional fieldName prop, the decrypted name to display, overriding sig.fieldName. Backward compatible: when omitted, sig.fieldName is used if displayable and the row is hidden if it is not.

v0.5.14 to v0.6.0

Release: Next.js works without ssr: false; pdf.js bundled and lazy-loaded

Drop-in for application code — no source changes are required. The changes are to how the package is built and shipped, and one is worth acting on.

What you can now delete

  • dynamic(() => import('./Viewer'), { ssr: false }). The package ships a 'use client' directive and no longer evaluates pdf.js at module scope, so it can be imported directly — including from a Server Component. Leaving your existing ssr: false wrapper in place still works; removing it is optional.
  • Any config.resolve.alias.canvas = false webpack workaround. VerifyKit renders through PDF.js's browser canvas and never imports the Node canvas package. This alias was never needed and can go.

What you should act on

  • pdfjs-dist is no longer a dependency of @trexolab/verifykit-react. pdf.js is bundled into the package (in a lazily-loaded chunk) and pdfjs-dist moved to devDependencies. Consequences:
    • Remove pdfjs-dist from your own package.json unless you import it directly yourself — installing it no longer affects which pdf.js the viewer uses.

    • Any script that copies assets out of node_modules/pdfjs-dist will break, because it may no longer be installed. CMaps and standard fonts ship inside the SDK instead:

      bash
      # before
      cp -r node_modules/pdfjs-dist/cmaps public/cmaps
       
      # after
      cp -r node_modules/@trexolab/verifykit-react/cmaps public/cmaps
      cp -r node_modules/@trexolab/verifykit-react/standard_fonts public/standard_fonts
    • workerUrl is unchanged and still required — the worker is still fetched at runtime and must match 5.5.207 exactly.

  • The package now ships unminified. Your bundler minifies it as normal; this only affects the raw tarball size and makes stack traces readable. (esbuild's name mangling corrupts pdf.js's private class fields, so minifying here was not an option.)

Behaviour change

  • waitForVerifier() no longer throws Error: Verifier not configured. It now waits for WASM initialisation instead of rejecting when called early, which means useVerification().load() works when called from a mount effect. If you added a retry loop or a readiness guard to work around this, you can remove it. Nothing catches this error in practice, so this is safe — but if you were matching on that message, it will no longer appear.

v0.5.13 to v0.5.14

Release: Encrypted-PDF signature icon now correct in every integration

No API changes. Drop-in upgrade.

  • The on-page signature icon for encrypted PDFs now shows verified status in every integration, not only in apps that happened to wire onPasswordAccepted={verification.applyPassword} by hand. The accepted password now travels automatically through VerifyKitProvider, so React (Viewer, CoreViewer, legacy PdfViewer), @trexolab/verifykit-vanilla and Vue all swap Adobe's yellow "?" to the validated icon with no host changes.
  • onPasswordAccepted and applyPassword() still work and are still exported — they are now an opt-in escape hatch rather than a requirement. Keep them if you drive your own PDF.js instance; a password reported twice is ignored, so having both wired is harmless.
  • Owner-password-only PDFs (encrypted, but opening with an empty user password) are no longer skipped.
  • Requirement to be aware of: <Viewer> and useVerification() must be under the same <VerifyKitProvider> — that provider is what carries the password between them. This is already true of any normal setup.

v0.5.2 to v0.5.3

Release: Adobe-parity improvements + configurable zoom + certificate chain export

Two small visible behavior changes plus a lot of additive API. No required code changes; most integrators can drop-in upgrade.

Behavior changes (may require attention)

  1. SHA-1 signatures now default to valid. Previously, any signature whose only issue was a SHA-1 digest produced overallStatus: 'warning'. As of v0.5.3 the default mirrors Adobe Reader: SHA-1 signatures pass the algorithm check (and SignatureCheckResult.algorithmName is set to "SHA-1" so UIs can still disclose the algorithm). If your app routed the old warning into a specific UI state or metric, either update that logic or restore the strict policy:

    ts
    import { createVerifier } from '@trexolab/verifykit-core'
    const verifier = await createVerifier({ algorithmPolicy: { sha1: 'warn' } })
    // — or globally —
    import { setAlgorithmPolicy } from '@trexolab/verifykit-core'
    await setAlgorithmPolicy({ sha1: 'warn' })

    MD5 / MD2 / MD4 remain hardcoded Invalid — not configurable.

  2. Viewer zoom range is now 25 %–1000 % (was 40 %–500 %). Matches pdf.js and lets users inspect fine detail in signature stamps / seals. The zoom dropdown filters to the configured range, so no UI breaks. Restore the old range with:

    tsx
    import { defaultLayoutPlugin } from '@trexolab/verifykit-react'
    const layout = defaultLayoutPlugin({ zoom: { minScale: 0.4, maxScale: 5 } })

    Vanilla: VerifyKit.create(el, { zoom: { minScale: 0.4, maxScale: 5 } }).

New features

  • Legacy adbe.pkcs7.sha1 signatures now verify (previously produced false "document modified" INVALID on PDF 1.3 signatures with no signedAttrs). No migration needed — you'll just see more PDFs verify correctly.
  • Certificate chain export from the Signature Properties dialog's Certificates tab (Export Chain ▾ → PEM / PKCS#7 / ZIP). Also exposed programmatically: new core helper buildCertChainPkcs7 and new React helpers exportCertChainAsPem / exportCertChainAsPkcs7 / exportCertChainAsZip.
  • CertificateInfo.rawDer (Uint8Array) — raw DER bytes now exposed on every CertificateInfo. Never read by the verification pipeline. Per-cert PEM / DER buttons in CertificateViewer.DetailPanel previously rendered only if you populated this field; they now render automatically.
  • AlgorithmPolicy + setAlgorithmPolicy / resetAlgorithmPolicy runtime API — see above.
  • SignatureCheckResult.algorithmName — optional field on every check, populated by the algorithm-strength check so UIs can display the algorithm regardless of overall status.
  • ZoomPluginOptionszoomPlugin(), defaultLayoutPlugin({ zoom }), and vanilla VerifyKit.create({ zoom }) all accept { minScale?, maxScale?, step? }.
  • Docs-site: new /faq route + IndexNow protocol + richer JSON-LD schemas. Does not affect SDK consumers.

Repository layout

  • Internal reorganisation on our side. No published package name, entry point or API changed, so nothing here affects your integration.

Upgrade path: npm install + rebuild. Review the two behavior changes above; apply the opt-out snippets if your app depends on the previous behavior.

v0.4.6 to v0.5.0

Release: Full codebase audit, type safety, and Rust/WASM cleanup

This is a non-breaking quality release. No API changes, no new exports, no removed exports. Drop-in upgrade from v0.4.x.

Key changes:

  • Full TypeScript audit -- resolved all type errors and unused variables across all packages.
  • Rust/WASM engine cleanup -- zero clippy warnings, idiomatic Rust patterns, removed dead functions and stale constants.
  • Plugin-revocation type safety -- fixed type mismatches in handler functions.
  • ESLint configuration overhaul -- unified linting across the monorepo.
  • Fresh production builds verified clean -- all four packages build without warnings.

Upgrade path: Drop-in replacement. Run npm install and rebuild. No code changes required.

v0.4.0 to v0.4.5

Release: Display status fix, Adobe Reader parity improvements, PAdES tab, document timestamp indicators

Key changes:

  • padesLevel is now optional -- PdfSignature.padesLevel has changed from PAdESLevel to PAdESLevel | null. For non-ETSI signatures (e.g., adbe.pkcs7.detached), padesLevel is null. ETSI signatures (ETSI.CAdES.detached, ETSI.RFC3161) continue to report their PAdES conformance level.

  • attempted field on SignatureCheckResult -- A new optional attempted?: boolean field distinguishes between checks that were actively tried (e.g., online revocation request) and checks that were never attempted (e.g., revocation without the plugin). This enables accurate Adobe Reader parity in getDisplayStatus().

  • getDisplayStatus() Adobe Reader parity changes -- The display status logic now considers the attempted field. Revocation checks that were actively attempted but returned unknown cause the display status to be "unknown", while checks that were never attempted (offline) do not penalize the signature. This matches Adobe Reader DC behavior: offline = valid, online + failed = unknown.

  • PAdES tab in signature properties -- The <SignaturePropertiesModal> now includes a PAdES tab that shows the detected conformance level and related details for ETSI signatures.

  • Document Timestamp visual indicators -- Document timestamps (ETSI.RFC3161 sub-filter) are now visually distinguished from regular signatures in the viewer.

Upgrade path:

  1. If your code reads sig.padesLevel, add a null check:

    ts
    // Before
    console.log(`PAdES: ${sig.padesLevel}`) // may be null now
     
    // After
    if (sig.padesLevel) {
      console.log(`PAdES: ${sig.padesLevel}`)
    } else {
      console.log('Standard PKCS#7 signature (no PAdES level)')
    }
  2. If you check revocationCheck.status for UI display, consider using the new attempted field to differentiate between "not checked" and "checked but unknown."

  3. No breaking changes to the React component API. The PAdES tab and document timestamp indicators are added automatically.

v0.3.2 to v0.4.0

Release: Flicker-free appearance swap, single-render VerificationFloater

Key changes:

  • Flicker-free appearance swap — The viewer now swaps between normal and signature appearance streams without visual flicker. Previously, switching appearances could cause a brief blank frame.
  • Single-render VerificationFloater — The VerificationFloater component now renders in a single pass instead of mounting, measuring, and re-rendering. This eliminates layout shift when the floater appears.

Upgrade path: Drop-in replacement. No API changes.

v0.3.1 to v0.3.2

Release: Embed VerificationFloater, smart context menu, live demo, polyfill fix

Key changes:

  • Embedded VerificationFloater — The verification status floater is now embedded directly in the viewer rather than rendered as a portal. This simplifies integration and avoids z-index conflicts.
  • Smart context menu — Right-clicking a signature field now shows a context menu with signature details, certificate info, and verification status.
  • Live demo — The documentation site gained an interactive demo page at /demo.
  • Polyfill fix — Fixed a compatibility issue with the structuredClone polyfill in older browsers and Node.js environments.

Upgrade path: Drop-in replacement. No API changes.

Pre-v0.3.1 to v0.3.1+

This was the most significant architectural change in the SDK's history.

WASM Base64 Embedding

Before v0.3.1, the WASM binary (verifykit_core_wasm_bg.wasm) was shipped as a separate file. You needed bundler plugins to handle it:

js
// vite.config.ts (no longer needed)
import wasm from 'vite-plugin-wasm'
export default { plugins: [wasm()] }
js
// webpack.config.js (no longer needed)
module.exports = {
  experiments: { asyncWebAssembly: true },
}

From v0.3.1, the WASM binary was base64-embedded directly into the JavaScript bundle, so no external .wasm file had to be served and no bundler configuration was required.

Superseded in v0.12.0. The binary ships as a .wasm file again — but the plugins this section tells you to remove are still not needed, because bundlers now handle it natively. See v0.11.0 to v0.12.0.

Migration:

  1. Remove vite-plugin-wasm or any WASM-related bundler plugins.
  2. Remove webpack asyncWebAssembly experiment configuration.
  3. Remove any static file serving rules for .wasm files.
  4. Update to @trexolab/verifykit-core >= 0.3.1.

If you still need to load the WASM binary from a custom location (e.g., a CDN), you can use setWasmUrl():

ts
import { setWasmUrl } from '@trexolab/verifykit-core'
 
setWasmUrl('/custom/path/verifykit_core_wasm_bg.wasm')

Viewer Migration

Legacy PdfViewerViewer / CoreViewer

The SDK originally shipped a monolithic PdfViewer component that bundled all viewer features (toolbar, zoom, search, sidebar, print) into a single component. The plugin-based architecture replaced it with two components, and PdfViewer was removed in 0.9.0 — if you are on 0.8.x or earlier and still import it, this is the change to make.

ComponentPackagePurpose
CoreViewer@trexolab/verifykit-reactHeadless viewer — renders PDF pages with no built-in UI. All features are added via plugins.
Viewer@trexolab/verifykit-reactPre-configured viewer — wraps CoreViewer with the defaultLayoutPlugin that includes toolbar, zoom, search, page navigation, print, and sidebar.

Migration from PdfViewer to Viewer:

tsx
// Before
import { PdfViewer } from '@trexolab/verifykit-react'
 
<PdfViewer fileUrl="/doc.pdf" />
 
// After
import { Viewer, VerifyKitProvider } from '@trexolab/verifykit-react'
 
<VerifyKitProvider>
  <Viewer fileUrl="/doc.pdf" />
</VerifyKitProvider>

The Viewer component requires a VerifyKitProvider ancestor. The provider initializes the WASM engine, manages verification state, and provides the context that plugins use.

Plugin Architecture Migration

The monolithic PdfViewer included all features by default with no way to remove or customise individual features. The plugin architecture lets you compose only what you need:

tsx
import {
  CoreViewer,
  VerifyKitProvider,
  toolbarPlugin,
  zoomPlugin,
  searchPlugin,
} from '@trexolab/verifykit-react'
 
// Only include zoom and search — no toolbar, no print, no sidebar
const plugins = [zoomPlugin(), searchPlugin()]
 
function App() {
  return (
    <VerifyKitProvider>
      <CoreViewer fileUrl="/doc.pdf" plugins={plugins} />
    </VerifyKitProvider>
  )
}

The defaultLayoutPlugin() is a meta-plugin that composes the standard set of plugins. Use it when you want the full default experience:

tsx
import {
  CoreViewer,
  VerifyKitProvider,
  defaultLayoutPlugin,
} from '@trexolab/verifykit-react'
 
const plugins = [defaultLayoutPlugin()]
 
function App() {
  return (
    <VerifyKitProvider>
      <CoreViewer fileUrl="/doc.pdf" plugins={plugins} />
    </VerifyKitProvider>
  )
}

This is equivalent to using the Viewer component, which applies defaultLayoutPlugin automatically.


Need Help?

If you encounter issues while upgrading, check the Troubleshooting guide or open an issue on the GitHub repository.