@drvillo/react-browser-e-signing 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +115 -1
- package/dist/index.d.ts +20 -2
- package/dist/index.js +62 -15
- package/dist/index.js.map +1 -1
- package/dist/pdf.worker.min.mjs +21 -0
- package/dist/worker.d.mts +1 -0
- package/dist/worker.mjs +3 -0
- package/package.json +13 -6
package/README.md
CHANGED
|
@@ -59,6 +59,12 @@ pnpm demo
|
|
|
59
59
|
- `SIGNATURE_FONTS`
|
|
60
60
|
- `sha256`
|
|
61
61
|
|
|
62
|
+
### Configuration
|
|
63
|
+
|
|
64
|
+
- `configure(options)` — PDF worker URL, signature font mode (`network` | `local-only`), optional `fontUrlResolver`, optional `onWarning` callback
|
|
65
|
+
- `getPdfWorkerSrc()` — from `@drvillo/react-browser-e-signing/worker`; returns a bundler-resolved URL for the packaged `pdf.worker.min.mjs` (recommended default for `pdfWorkerSrc`)
|
|
66
|
+
- Types: `ESigningConfig`, `SignatureFontWarning`
|
|
67
|
+
|
|
62
68
|
### Types
|
|
63
69
|
|
|
64
70
|
- `FieldPlacement`
|
|
@@ -121,12 +127,120 @@ pnpm test
|
|
|
121
127
|
pnpm typecheck
|
|
122
128
|
```
|
|
123
129
|
|
|
130
|
+
After a fresh clone, run **`pnpm build` once** so `dist/` includes the bundled worker, `worker.mjs`, and types (the copy step runs after tsup’s type emit).
|
|
131
|
+
|
|
124
132
|
## Notes
|
|
125
133
|
|
|
126
|
-
- `
|
|
134
|
+
- **PDF worker:** PDF.js **must** load its worker from a URL. This package ships `pdf.worker.min.mjs` built from the same `pdfjs-dist` version as `react-pdf` (see **Bundled PDF.js worker** below). The package does **not** inject a CDN URL by default; call `configure({ pdfWorkerSrc: getPdfWorkerSrc() })` or set `workerSrc` on `PdfViewer` so the worker loads (recommended for production). See **Production hardening** below.
|
|
127
135
|
- Browser test config skips execution when Playwright Chromium is not available in the environment.
|
|
128
136
|
- Demo theme switcher (`default` / `custom`) shows how a container app can fully re-theme the same components.
|
|
129
137
|
|
|
138
|
+
## Production hardening
|
|
139
|
+
|
|
140
|
+
Runtime calls to external CDNs (PDF.js worker, Google Fonts) often fail in real apps: **CSP** (`worker-src`, `connect-src`, `font-src`), ad blockers, corporate proxies, or offline users. They also add noisy console errors (`Failed to fetch`) even when the rest of the UI works. This library defaults to **no injected worker URL** and lets you control loading explicitly.
|
|
141
|
+
|
|
142
|
+
### Bundled PDF.js worker (recommended)
|
|
143
|
+
|
|
144
|
+
The npm package includes `pdf.worker.min.mjs` at the same path layout as `react-pdf`’s `pdfjs-dist` dependency, so you do **not** need unpkg or a manual copy script for the default flow.
|
|
145
|
+
|
|
146
|
+
Configure once (e.g. app entry or a client-only module):
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
import { configure } from '@drvillo/react-browser-e-signing'
|
|
150
|
+
import { getPdfWorkerSrc } from '@drvillo/react-browser-e-signing/worker'
|
|
151
|
+
|
|
152
|
+
configure({ pdfWorkerSrc: getPdfWorkerSrc() })
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`getPdfWorkerSrc()` uses `new URL('./pdf.worker.min.mjs', import.meta.url)` so Vite, webpack 5, and similar bundlers emit the worker as an asset and rewrite the URL. **Do not** point at a third-party CDN in production if you can avoid it.
|
|
156
|
+
|
|
157
|
+
Or per viewer:
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
import { getPdfWorkerSrc } from '@drvillo/react-browser-e-signing/worker'
|
|
161
|
+
|
|
162
|
+
<PdfViewer workerSrc={getPdfWorkerSrc()} {...pdfViewerProps} />
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`workerSrc` on `PdfViewer` overrides `configure({ pdfWorkerSrc })`.
|
|
166
|
+
|
|
167
|
+
**Advanced:** import the raw file (e.g. Vite):
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
import workerUrl from '@drvillo/react-browser-e-signing/pdf.worker.min.mjs?url'
|
|
171
|
+
|
|
172
|
+
configure({ pdfWorkerSrc: workerUrl })
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Versioning:** when this library upgrades `react-pdf` / `pdfjs-dist`, the published worker file is regenerated from that `pdfjs-dist` version. Keep your app on the published package version you intend; do not mix a different `pdfjs-dist` worker binary with another API version.
|
|
176
|
+
|
|
177
|
+
**SSR / Next.js App Router:** PDF.js runs in the browser. Set `pdfWorkerSrc` only on the client — e.g. in `useEffect`, or in a file loaded via `dynamic(..., { ssr: false })`, or in a client-only entry — so `getPdfWorkerSrc()` and worker loading are not evaluated during SSR.
|
|
178
|
+
|
|
179
|
+
### Manual self-host (fallback)
|
|
180
|
+
|
|
181
|
+
If you prefer serving the worker from your own `public/` folder, copy the file that matches the `pdfjs-dist` version used by `react-pdf` (see `node_modules/react-pdf/package.json`):
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
cp node_modules/pdfjs-dist/build/pdf.worker.min.mjs public/pdf.worker.min.mjs
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Then:
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
import { configure } from '@drvillo/react-browser-e-signing'
|
|
191
|
+
|
|
192
|
+
configure({ pdfWorkerSrc: '/pdf.worker.min.mjs' })
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Typed signatures: local-only fonts (no network)
|
|
196
|
+
|
|
197
|
+
Skip all font fetches (handwriting fonts won’t load from Google; the browser uses whatever faces are already available, with sensible fallback):
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
configure({ fontMode: 'local-only' })
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Default is `fontMode: 'network'`, which keeps the previous Google Fonts behavior but **does not throw** on failure; failures are reported via `onWarning` when set.
|
|
204
|
+
|
|
205
|
+
### Custom font URLs (self-hosted woff/woff2)
|
|
206
|
+
|
|
207
|
+
```tsx
|
|
208
|
+
configure({
|
|
209
|
+
fontUrlResolver: (family) => `/fonts/${family.replace(/\s+/g, '-')}.woff2`,
|
|
210
|
+
})
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Return `null` to fall back to Google Fonts for that family (when `fontMode` is `'network'`).
|
|
214
|
+
|
|
215
|
+
### Observability
|
|
216
|
+
|
|
217
|
+
```tsx
|
|
218
|
+
configure({
|
|
219
|
+
onWarning: (w) => {
|
|
220
|
+
console.warn(`[react-browser-e-signing] ${w.code}: ${w.message}`)
|
|
221
|
+
},
|
|
222
|
+
})
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Warnings are non-throwing; signing flow should remain usable.
|
|
226
|
+
|
|
227
|
+
### CSP-oriented example
|
|
228
|
+
|
|
229
|
+
If everything is same-origin (including the worker URL after your bundler emits it):
|
|
230
|
+
|
|
231
|
+
```
|
|
232
|
+
Content-Security-Policy: worker-src 'self'; script-src 'self'; connect-src 'self'; font-src 'self';
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
A self-hosted worker loaded from the same origin as your app typically satisfies `worker-src 'self'`. Adjust `connect-src` / `font-src` if you still use Google Fonts or load scripts from elsewhere.
|
|
236
|
+
|
|
237
|
+
### Migration from v0.1.2 and earlier
|
|
238
|
+
|
|
239
|
+
Previously, `PdfViewer` set the worker to unpkg at module load, and typed fonts fetched Google Fonts CSS + files at runtime.
|
|
240
|
+
|
|
241
|
+
- **Worker:** to restore the old CDN behavior (not recommended for production), set `pdfWorkerSrc` to the unpkg URL for your PDF.js version, e.g. `https://unpkg.com/pdfjs-dist@<version>/build/pdf.worker.min.mjs` (match `pdfjs.version` from `react-pdf` / `pdfjs-dist`).
|
|
242
|
+
- **Fonts:** behavior is unchanged when you omit `configure()` except that network failures no longer surface as thrown errors from `loadSignatureFont`; use `fontMode: 'local-only'` for strict no-network deployments.
|
|
243
|
+
|
|
130
244
|
## Limitations
|
|
131
245
|
|
|
132
246
|
- Single signer workflow in v0.1
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,22 @@ import { ReactNode } from 'react';
|
|
|
5
5
|
declare const SIGNATURE_FONTS: readonly ["Caveat", "Homemade Apple", "Reenie Beanie", "Mr Dafoe", "Pacifico", "Qwitcher Grypen"];
|
|
6
6
|
declare function loadSignatureFont(fontFamily: string): Promise<void>;
|
|
7
7
|
|
|
8
|
+
interface SignatureFontWarning {
|
|
9
|
+
code: string;
|
|
10
|
+
message: string;
|
|
11
|
+
}
|
|
12
|
+
interface ESigningConfig {
|
|
13
|
+
/** PDF.js worker script URL (e.g. self-hosted `/pdf.worker.min.mjs`). When unset, `PdfViewer` does not set `GlobalWorkerOptions.workerSrc`. */
|
|
14
|
+
pdfWorkerSrc?: string;
|
|
15
|
+
/** `network`: fetch Google Fonts when loading typed signature fonts (default). `local-only`: no fetches; browser uses available/system fonts. */
|
|
16
|
+
fontMode?: 'network' | 'local-only';
|
|
17
|
+
/** Return a woff/woff2 URL for a font family, or `null` to fall back to Google Fonts (when `fontMode` is `network`). */
|
|
18
|
+
fontUrlResolver?: (fontFamily: string) => string | null;
|
|
19
|
+
/** Non-throwing callback for recoverable issues (font/worker setup). */
|
|
20
|
+
onWarning?: (warning: SignatureFontWarning) => void;
|
|
21
|
+
}
|
|
22
|
+
declare function configure(options: ESigningConfig): void;
|
|
23
|
+
|
|
8
24
|
interface PdfViewerProps {
|
|
9
25
|
pdfData: ArrayBuffer | null;
|
|
10
26
|
numPages: number;
|
|
@@ -18,8 +34,10 @@ interface PdfViewerProps {
|
|
|
18
34
|
}) => void;
|
|
19
35
|
renderOverlay?: (pageIndex: number) => ReactNode;
|
|
20
36
|
className?: string;
|
|
37
|
+
/** PDF.js worker script URL. Overrides `configure({ pdfWorkerSrc })`. When neither is set, worker URL is left unset (no CDN injection). */
|
|
38
|
+
workerSrc?: string;
|
|
21
39
|
}
|
|
22
|
-
declare function PdfViewer({ pdfData, numPages, scale, onScaleChange, onDocumentLoadSuccess, onPageDimensions, renderOverlay, className, }: PdfViewerProps): react_jsx_runtime.JSX.Element;
|
|
40
|
+
declare function PdfViewer({ pdfData, numPages, scale, onScaleChange, onDocumentLoadSuccess, onPageDimensions, renderOverlay, className, workerSrc, }: PdfViewerProps): react_jsx_runtime.JSX.Element;
|
|
23
41
|
|
|
24
42
|
type FieldType = 'signature' | 'fullName' | 'title' | 'date';
|
|
25
43
|
interface FieldPlacement {
|
|
@@ -248,4 +266,4 @@ declare const defaults: {
|
|
|
248
266
|
readonly DEFAULT_FIELD_HEIGHT_PERCENT: 5;
|
|
249
267
|
};
|
|
250
268
|
|
|
251
|
-
export { FieldOverlay, FieldPalette, type FieldPlacement, type FieldType, type PdfPageDimensions, PdfViewer, SIGNATURE_FONTS, SLOTS, SignatureField, type SignatureFieldPreview, SignaturePad, SignaturePreview, type SignatureStyle, SignerDetailsPanel, type SignerInfo, SigningComplete, type SigningResult, defaults, loadSignatureFont, mapFromPoints, mapToPoints, modifyPdf, sha256, useFieldPlacement, usePdfDocument, useSignatureRenderer };
|
|
269
|
+
export { type ESigningConfig, FieldOverlay, FieldPalette, type FieldPlacement, type FieldType, type PdfPageDimensions, PdfViewer, SIGNATURE_FONTS, SLOTS, SignatureField, type SignatureFieldPreview, type SignatureFontWarning, SignaturePad, SignaturePreview, type SignatureStyle, SignerDetailsPanel, type SignerInfo, SigningComplete, type SigningResult, configure, defaults, loadSignatureFont, mapFromPoints, mapToPoints, modifyPdf, sha256, useFieldPlacement, usePdfDocument, useSignatureRenderer };
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import { pdfjs, Document, Page } from 'react-pdf';
|
|
2
|
+
import { useEffect, useRef, useMemo, useState, useCallback } from 'react';
|
|
2
3
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
|
-
import { useRef, useEffect, useMemo, useState, useCallback } from 'react';
|
|
4
4
|
import SignaturePadLibrary from 'signature_pad';
|
|
5
5
|
import fontkit from '@pdf-lib/fontkit';
|
|
6
6
|
import { PDFDocument, StandardFonts } from 'pdf-lib';
|
|
7
7
|
|
|
8
|
+
// src/lib/config.ts
|
|
9
|
+
var _config = {};
|
|
10
|
+
function configure(options) {
|
|
11
|
+
_config = { ..._config, ...options };
|
|
12
|
+
}
|
|
13
|
+
function getConfig() {
|
|
14
|
+
return _config;
|
|
15
|
+
}
|
|
16
|
+
|
|
8
17
|
// src/lib/signature-fonts.ts
|
|
9
18
|
var loadedFonts = /* @__PURE__ */ new Set();
|
|
10
19
|
var SIGNATURE_FONTS = [
|
|
@@ -29,18 +38,51 @@ function extractFontSource(cssText) {
|
|
|
29
38
|
if (!sourceMatch) return null;
|
|
30
39
|
return sourceMatch[1].replace(/['"]/g, "");
|
|
31
40
|
}
|
|
41
|
+
function warnFontLoad(message) {
|
|
42
|
+
try {
|
|
43
|
+
getConfig().onWarning?.({ code: "FONT_LOAD_FAILED", message });
|
|
44
|
+
} catch {
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function loadFontFaceFromUrl(fontFamily, url) {
|
|
48
|
+
if (typeof FontFace === "undefined" || typeof document === "undefined") return;
|
|
49
|
+
const fontFace = new FontFace(fontFamily, `url(${url})`);
|
|
50
|
+
await fontFace.load();
|
|
51
|
+
const fontSet = document.fonts;
|
|
52
|
+
fontSet.add(fontFace);
|
|
53
|
+
}
|
|
32
54
|
async function loadSignatureFont(fontFamily) {
|
|
33
55
|
if (loadedFonts.has(fontFamily)) return;
|
|
34
56
|
if (typeof document === "undefined") return;
|
|
35
57
|
if (typeof FontFace === "undefined") return;
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
58
|
+
const { fontMode = "network", fontUrlResolver } = getConfig();
|
|
59
|
+
if (fontMode === "local-only") {
|
|
60
|
+
loadedFonts.add(fontFamily);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const tryLoadFromGoogle = async () => {
|
|
64
|
+
try {
|
|
65
|
+
const cssUrl = buildGoogleFontsCssUrl(fontFamily);
|
|
66
|
+
const cssText = await loadCssFromGoogleFonts(cssUrl);
|
|
67
|
+
const fontSource = extractFontSource(cssText);
|
|
68
|
+
if (!fontSource) throw new Error(`Unable to extract font source for ${fontFamily}`);
|
|
69
|
+
await loadFontFaceFromUrl(fontFamily, fontSource);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
72
|
+
warnFontLoad(`Google Fonts load failed for "${fontFamily}": ${detail}`);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
if (fontUrlResolver) {
|
|
76
|
+
try {
|
|
77
|
+
const resolved = fontUrlResolver(fontFamily);
|
|
78
|
+
if (resolved) await loadFontFaceFromUrl(fontFamily, resolved);
|
|
79
|
+
else await tryLoadFromGoogle();
|
|
80
|
+
} catch (error) {
|
|
81
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
82
|
+
warnFontLoad(`Custom font load failed for "${fontFamily}": ${detail}`);
|
|
83
|
+
await tryLoadFromGoogle();
|
|
84
|
+
}
|
|
85
|
+
} else await tryLoadFromGoogle();
|
|
44
86
|
loadedFonts.add(fontFamily);
|
|
45
87
|
}
|
|
46
88
|
|
|
@@ -48,9 +90,6 @@ async function loadSignatureFont(fontFamily) {
|
|
|
48
90
|
function cn(...values) {
|
|
49
91
|
return values.filter(Boolean).join(" ");
|
|
50
92
|
}
|
|
51
|
-
if (typeof window !== "undefined") {
|
|
52
|
-
pdfjs.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`;
|
|
53
|
-
}
|
|
54
93
|
var MIN_SCALE = 0.5;
|
|
55
94
|
var MAX_SCALE = 2;
|
|
56
95
|
var SCALE_STEP = 0.1;
|
|
@@ -62,8 +101,15 @@ function PdfViewer({
|
|
|
62
101
|
onDocumentLoadSuccess,
|
|
63
102
|
onPageDimensions,
|
|
64
103
|
renderOverlay,
|
|
65
|
-
className
|
|
104
|
+
className,
|
|
105
|
+
workerSrc
|
|
66
106
|
}) {
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
if (typeof window === "undefined") return;
|
|
109
|
+
const fromConfig = getConfig().pdfWorkerSrc;
|
|
110
|
+
const next = workerSrc ?? fromConfig;
|
|
111
|
+
if (next) pdfjs.GlobalWorkerOptions.workerSrc = next;
|
|
112
|
+
}, [workerSrc]);
|
|
67
113
|
if (!pdfData)
|
|
68
114
|
return /* @__PURE__ */ jsx("div", { "data-slot": "pdf-viewer-empty", className: cn(className), children: "Upload a PDF to begin" });
|
|
69
115
|
return /* @__PURE__ */ jsxs("div", { "data-slot": "pdf-viewer", className: cn(className), children: [
|
|
@@ -802,7 +848,8 @@ function useSignatureRenderer({ signerName, style }) {
|
|
|
802
848
|
setSignatureDataUrl(dataUrl || null);
|
|
803
849
|
}).catch(() => {
|
|
804
850
|
if (!isActive) return;
|
|
805
|
-
|
|
851
|
+
const dataUrl = drawTypedSignature({ signerName: normalizedName, fontFamily: style.fontFamily });
|
|
852
|
+
setSignatureDataUrl(dataUrl || null);
|
|
806
853
|
}).finally(() => {
|
|
807
854
|
if (!isActive) return;
|
|
808
855
|
setIsRendering(false);
|
|
@@ -969,6 +1016,6 @@ var defaults = {
|
|
|
969
1016
|
DEFAULT_FIELD_HEIGHT_PERCENT: 5
|
|
970
1017
|
};
|
|
971
1018
|
|
|
972
|
-
export { FieldOverlay, FieldPalette, PdfViewer, SIGNATURE_FONTS, SLOTS, SignatureField, SignaturePad, SignaturePreview, SignerDetailsPanel, SigningComplete, defaults, loadSignatureFont, mapFromPoints, mapToPoints, modifyPdf, sha256, useFieldPlacement, usePdfDocument, useSignatureRenderer };
|
|
1019
|
+
export { FieldOverlay, FieldPalette, PdfViewer, SIGNATURE_FONTS, SLOTS, SignatureField, SignaturePad, SignaturePreview, SignerDetailsPanel, SigningComplete, configure, defaults, loadSignatureFont, mapFromPoints, mapToPoints, modifyPdf, sha256, useFieldPlacement, usePdfDocument, useSignatureRenderer };
|
|
973
1020
|
//# sourceMappingURL=index.js.map
|
|
974
1021
|
//# sourceMappingURL=index.js.map
|