@signiphi/pdf-signer 0.1.1

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.
Files changed (192) hide show
  1. package/INSTALLING_LOCALLY.md +184 -0
  2. package/LICENSE +2 -0
  3. package/README.md +1093 -0
  4. package/assets/viewer.html +314 -0
  5. package/dist/__tests__/helpers/fixtures.d.ts +43 -0
  6. package/dist/__tests__/helpers/fixtures.d.ts.map +1 -0
  7. package/dist/__tests__/helpers/mocks.d.ts +333 -0
  8. package/dist/__tests__/helpers/mocks.d.ts.map +1 -0
  9. package/dist/__tests__/setup.d.ts +6 -0
  10. package/dist/__tests__/setup.d.ts.map +1 -0
  11. package/dist/components/AttachmentUpload.d.ts +17 -0
  12. package/dist/components/AttachmentUpload.d.ts.map +1 -0
  13. package/dist/components/EditableFieldsPanel.d.ts +30 -0
  14. package/dist/components/EditableFieldsPanel.d.ts.map +1 -0
  15. package/dist/components/ErrorBoundary.d.ts +67 -0
  16. package/dist/components/ErrorBoundary.d.ts.map +1 -0
  17. package/dist/components/FormFieldsView.d.ts +42 -0
  18. package/dist/components/FormFieldsView.d.ts.map +1 -0
  19. package/dist/components/PdfViewerStyled.d.ts +16 -0
  20. package/dist/components/PdfViewerStyled.d.ts.map +1 -0
  21. package/dist/components/PoweredBySigniphi.d.ts +11 -0
  22. package/dist/components/PoweredBySigniphi.d.ts.map +1 -0
  23. package/dist/components/SignatureCanvas.d.ts +12 -0
  24. package/dist/components/SignatureCanvas.d.ts.map +1 -0
  25. package/dist/components/SignatureInitialsBox.d.ts +23 -0
  26. package/dist/components/SignatureInitialsBox.d.ts.map +1 -0
  27. package/dist/components/SignatureModal.d.ts +22 -0
  28. package/dist/components/SignatureModal.d.ts.map +1 -0
  29. package/dist/components/SigningInstructions.d.ts +12 -0
  30. package/dist/components/SigningInstructions.d.ts.map +1 -0
  31. package/dist/components/SubmissionForm.d.ts +52 -0
  32. package/dist/components/SubmissionForm.d.ts.map +1 -0
  33. package/dist/components/ViewToggleToolbar.d.ts +30 -0
  34. package/dist/components/ViewToggleToolbar.d.ts.map +1 -0
  35. package/dist/components/form-fields/CheckboxRenderer.d.ts +10 -0
  36. package/dist/components/form-fields/CheckboxRenderer.d.ts.map +1 -0
  37. package/dist/components/form-fields/DateFieldRenderer.d.ts +14 -0
  38. package/dist/components/form-fields/DateFieldRenderer.d.ts.map +1 -0
  39. package/dist/components/form-fields/DropdownRenderer.d.ts +14 -0
  40. package/dist/components/form-fields/DropdownRenderer.d.ts.map +1 -0
  41. package/dist/components/form-fields/FormFieldRenderer.d.ts +20 -0
  42. package/dist/components/form-fields/FormFieldRenderer.d.ts.map +1 -0
  43. package/dist/components/form-fields/InitialsFieldRenderer.d.ts +14 -0
  44. package/dist/components/form-fields/InitialsFieldRenderer.d.ts.map +1 -0
  45. package/dist/components/form-fields/RadioGroupRenderer.d.ts +10 -0
  46. package/dist/components/form-fields/RadioGroupRenderer.d.ts.map +1 -0
  47. package/dist/components/form-fields/SignatureFieldRenderer.d.ts +16 -0
  48. package/dist/components/form-fields/SignatureFieldRenderer.d.ts.map +1 -0
  49. package/dist/components/form-fields/TextFieldRenderer.d.ts +14 -0
  50. package/dist/components/form-fields/TextFieldRenderer.d.ts.map +1 -0
  51. package/dist/components/form-fields/TextLabelRenderer.d.ts +14 -0
  52. package/dist/components/form-fields/TextLabelRenderer.d.ts.map +1 -0
  53. package/dist/components/form-fields/index.d.ts +14 -0
  54. package/dist/components/form-fields/index.d.ts.map +1 -0
  55. package/dist/components/index.d.ts +14 -0
  56. package/dist/components/index.d.ts.map +1 -0
  57. package/dist/components/index.js +6297 -0
  58. package/dist/components/index.js.map +1 -0
  59. package/dist/components/index.mjs +6248 -0
  60. package/dist/components/index.mjs.map +1 -0
  61. package/dist/core/PdfViewerCore.d.ts +19 -0
  62. package/dist/core/PdfViewerCore.d.ts.map +1 -0
  63. package/dist/core/SignatureCaptureCore.d.ts +37 -0
  64. package/dist/core/SignatureCaptureCore.d.ts.map +1 -0
  65. package/dist/core/index.d.ts +3 -0
  66. package/dist/core/index.d.ts.map +1 -0
  67. package/dist/core/index.js +907 -0
  68. package/dist/core/index.js.map +1 -0
  69. package/dist/core/index.mjs +884 -0
  70. package/dist/core/index.mjs.map +1 -0
  71. package/dist/hooks/index.d.ts +8 -0
  72. package/dist/hooks/index.d.ts.map +1 -0
  73. package/dist/hooks/index.js +2167 -0
  74. package/dist/hooks/index.js.map +1 -0
  75. package/dist/hooks/index.mjs +2139 -0
  76. package/dist/hooks/index.mjs.map +1 -0
  77. package/dist/hooks/useAttachments.d.ts +25 -0
  78. package/dist/hooks/useAttachments.d.ts.map +1 -0
  79. package/dist/hooks/useFieldFiltering.d.ts +29 -0
  80. package/dist/hooks/useFieldFiltering.d.ts.map +1 -0
  81. package/dist/hooks/useFormFields.d.ts +23 -0
  82. package/dist/hooks/useFormFields.d.ts.map +1 -0
  83. package/dist/hooks/useMultiSignerContext.d.ts +25 -0
  84. package/dist/hooks/useMultiSignerContext.d.ts.map +1 -0
  85. package/dist/hooks/usePdfViewer.d.ts +52 -0
  86. package/dist/hooks/usePdfViewer.d.ts.map +1 -0
  87. package/dist/hooks/useSignatureCapture.d.ts +17 -0
  88. package/dist/hooks/useSignatureCapture.d.ts.map +1 -0
  89. package/dist/hooks/useSignatures.d.ts +25 -0
  90. package/dist/hooks/useSignatures.d.ts.map +1 -0
  91. package/dist/index.css +4929 -0
  92. package/dist/index.css.map +1 -0
  93. package/dist/index.d.ts +17 -0
  94. package/dist/index.d.ts.map +1 -0
  95. package/dist/index.js +7220 -0
  96. package/dist/index.js.map +1 -0
  97. package/dist/index.mjs +7093 -0
  98. package/dist/index.mjs.map +1 -0
  99. package/dist/integrations/index.d.ts +6 -0
  100. package/dist/integrations/index.d.ts.map +1 -0
  101. package/dist/integrations/index.js +242 -0
  102. package/dist/integrations/index.js.map +1 -0
  103. package/dist/integrations/index.mjs +218 -0
  104. package/dist/integrations/index.mjs.map +1 -0
  105. package/dist/integrations/next-config.d.ts +46 -0
  106. package/dist/integrations/next-config.d.ts.map +1 -0
  107. package/dist/integrations/vite-plugin.d.ts +48 -0
  108. package/dist/integrations/vite-plugin.d.ts.map +1 -0
  109. package/dist/lib/index.d.ts +3 -0
  110. package/dist/lib/index.d.ts.map +1 -0
  111. package/dist/lib/ui/alert.d.ts +9 -0
  112. package/dist/lib/ui/alert.d.ts.map +1 -0
  113. package/dist/lib/ui/button.d.ts +12 -0
  114. package/dist/lib/ui/button.d.ts.map +1 -0
  115. package/dist/lib/ui/calendar.d.ts +10 -0
  116. package/dist/lib/ui/calendar.d.ts.map +1 -0
  117. package/dist/lib/ui/card.d.ts +9 -0
  118. package/dist/lib/ui/card.d.ts.map +1 -0
  119. package/dist/lib/ui/checkbox.d.ts +5 -0
  120. package/dist/lib/ui/checkbox.d.ts.map +1 -0
  121. package/dist/lib/ui/dialog.d.ts +20 -0
  122. package/dist/lib/ui/dialog.d.ts.map +1 -0
  123. package/dist/lib/ui/index.d.ts +12 -0
  124. package/dist/lib/ui/index.d.ts.map +1 -0
  125. package/dist/lib/ui/input.d.ts +6 -0
  126. package/dist/lib/ui/input.d.ts.map +1 -0
  127. package/dist/lib/ui/label.d.ts +6 -0
  128. package/dist/lib/ui/label.d.ts.map +1 -0
  129. package/dist/lib/ui/popover.d.ts +7 -0
  130. package/dist/lib/ui/popover.d.ts.map +1 -0
  131. package/dist/lib/ui/radio-group.d.ts +6 -0
  132. package/dist/lib/ui/radio-group.d.ts.map +1 -0
  133. package/dist/lib/ui/select.d.ts +14 -0
  134. package/dist/lib/ui/select.d.ts.map +1 -0
  135. package/dist/lib/utils.d.ts +7 -0
  136. package/dist/lib/utils.d.ts.map +1 -0
  137. package/dist/styles/index.css +5004 -0
  138. package/dist/types/index.d.ts +265 -0
  139. package/dist/types/index.d.ts.map +1 -0
  140. package/dist/types/index.js +26 -0
  141. package/dist/types/index.js.map +1 -0
  142. package/dist/types/index.mjs +23 -0
  143. package/dist/types/index.mjs.map +1 -0
  144. package/dist/utils/attachment-validators.d.ts +118 -0
  145. package/dist/utils/attachment-validators.d.ts.map +1 -0
  146. package/dist/utils/audit-trail.d.ts +27 -0
  147. package/dist/utils/audit-trail.d.ts.map +1 -0
  148. package/dist/utils/date-validation.d.ts +30 -0
  149. package/dist/utils/date-validation.d.ts.map +1 -0
  150. package/dist/utils/errors.d.ts +106 -0
  151. package/dist/utils/errors.d.ts.map +1 -0
  152. package/dist/utils/field-extraction.d.ts +27 -0
  153. package/dist/utils/field-extraction.d.ts.map +1 -0
  154. package/dist/utils/field-visibility.d.ts +104 -0
  155. package/dist/utils/field-visibility.d.ts.map +1 -0
  156. package/dist/utils/index.d.ts +17 -0
  157. package/dist/utils/index.d.ts.map +1 -0
  158. package/dist/utils/index.js +2501 -0
  159. package/dist/utils/index.js.map +1 -0
  160. package/dist/utils/index.mjs +2404 -0
  161. package/dist/utils/index.mjs.map +1 -0
  162. package/dist/utils/logger.d.ts +16 -0
  163. package/dist/utils/logger.d.ts.map +1 -0
  164. package/dist/utils/pdf-field-type-helpers.d.ts +78 -0
  165. package/dist/utils/pdf-field-type-helpers.d.ts.map +1 -0
  166. package/dist/utils/pdf-helpers.d.ts +38 -0
  167. package/dist/utils/pdf-helpers.d.ts.map +1 -0
  168. package/dist/utils/pdf-lib-loader.d.ts +45 -0
  169. package/dist/utils/pdf-lib-loader.d.ts.map +1 -0
  170. package/dist/utils/pdf-manipulation.d.ts +93 -0
  171. package/dist/utils/pdf-manipulation.d.ts.map +1 -0
  172. package/dist/utils/pdf-validators.d.ts +149 -0
  173. package/dist/utils/pdf-validators.d.ts.map +1 -0
  174. package/dist/utils/pdf-viewer-filter.d.ts +35 -0
  175. package/dist/utils/pdf-viewer-filter.d.ts.map +1 -0
  176. package/dist/utils/pdf-widget-helpers.d.ts +98 -0
  177. package/dist/utils/pdf-widget-helpers.d.ts.map +1 -0
  178. package/dist/utils/pdfjs-config.d.ts +56 -0
  179. package/dist/utils/pdfjs-config.d.ts.map +1 -0
  180. package/dist/utils/pdfjs-version-check.d.ts +28 -0
  181. package/dist/utils/pdfjs-version-check.d.ts.map +1 -0
  182. package/dist/utils/performance-monitor.d.ts +172 -0
  183. package/dist/utils/performance-monitor.d.ts.map +1 -0
  184. package/dist/utils/tracking.d.ts +89 -0
  185. package/dist/utils/tracking.d.ts.map +1 -0
  186. package/package.json +180 -0
  187. package/scripts/analyze-bundle.js +271 -0
  188. package/scripts/copy-utils.js +227 -0
  189. package/scripts/copy-utils.test.js +164 -0
  190. package/scripts/postinstall.js +109 -0
  191. package/scripts/setup.js +108 -0
  192. package/src/styles/index.css +139 -0
@@ -0,0 +1,2139 @@
1
+ import { useRef, useState, useCallback, useMemo } from 'react';
2
+ import * as pdfjsLib2 from 'pdfjs-dist';
3
+ import { parseISO, isValid } from 'date-fns';
4
+
5
+ // src/hooks/usePdfViewer.ts
6
+
7
+ // src/utils/logger.ts
8
+ function isProduction() {
9
+ return typeof process !== "undefined" && process.env.NODE_ENV === "production";
10
+ }
11
+ function createPrefix(level) {
12
+ return `[@signiphi/pdf-signer] [${level.toUpperCase()}]`;
13
+ }
14
+ function createLogger() {
15
+ const isProd = isProduction();
16
+ return {
17
+ debug(message, ...args) {
18
+ if (!isProd) {
19
+ console.log(createPrefix("debug"), message, ...args);
20
+ }
21
+ },
22
+ info(message, ...args) {
23
+ if (!isProd) {
24
+ console.log(createPrefix("info"), message, ...args);
25
+ }
26
+ },
27
+ warn(message, ...args) {
28
+ if (!isProd) {
29
+ console.warn(createPrefix("warn"), message, ...args);
30
+ }
31
+ },
32
+ error(message, ...args) {
33
+ console.error(createPrefix("error"), message, ...args);
34
+ }
35
+ };
36
+ }
37
+ var logger = createLogger();
38
+
39
+ // src/utils/pdf-validators.ts
40
+ function validatePdfBytes(pdfBytes) {
41
+ if (!pdfBytes || pdfBytes.length === 0) {
42
+ return {
43
+ valid: false,
44
+ error: "PDF bytes are empty"
45
+ };
46
+ }
47
+ const header = new TextDecoder().decode(pdfBytes.slice(0, 12));
48
+ if (!header.startsWith("%PDF-")) {
49
+ return {
50
+ valid: false,
51
+ error: "Invalid PDF format: Missing PDF header"
52
+ };
53
+ }
54
+ return { valid: true };
55
+ }
56
+ function isAutoGeneratedLabel(label) {
57
+ if (!label || !label.trim()) return true;
58
+ const autoLabels = [
59
+ "Signature",
60
+ "Initials",
61
+ "Date",
62
+ "Text",
63
+ "Checkbox",
64
+ "Dropdown",
65
+ "Option",
66
+ "Radio"
67
+ ];
68
+ return autoLabels.includes(label.trim());
69
+ }
70
+ function validateFieldValues(values) {
71
+ const errors = [];
72
+ if (!values || typeof values !== "object") {
73
+ errors.push("Field values must be an object");
74
+ return { valid: false, errors };
75
+ }
76
+ const valuesObj = values;
77
+ for (const [key, value] of Object.entries(valuesObj)) {
78
+ if (typeof value !== "string") {
79
+ errors.push(`Field value for "${key}" must be a string, got ${typeof value}`);
80
+ }
81
+ }
82
+ return {
83
+ valid: errors.length === 0,
84
+ errors
85
+ };
86
+ }
87
+ function validateSignatures(signatures) {
88
+ const errors = [];
89
+ if (!signatures || typeof signatures !== "object") {
90
+ errors.push("Signatures must be an object");
91
+ return { valid: false, errors };
92
+ }
93
+ const sigsObj = signatures;
94
+ for (const [key, value] of Object.entries(sigsObj)) {
95
+ if (typeof value !== "string") {
96
+ errors.push(`Signature for "${key}" must be a string, got ${typeof value}`);
97
+ continue;
98
+ }
99
+ if (!value.startsWith("data:image/")) {
100
+ errors.push(`Signature for "${key}" must be a data URL starting with "data:image/"`);
101
+ }
102
+ const parts = value.split(",");
103
+ if (parts.length !== 2 || !parts[1]) {
104
+ errors.push(`Signature for "${key}" has invalid data URL format (missing base64 data)`);
105
+ }
106
+ }
107
+ return {
108
+ valid: errors.length === 0,
109
+ errors
110
+ };
111
+ }
112
+ function validatePdfUrl(url) {
113
+ if (typeof url !== "string") {
114
+ return { valid: false, error: "URL must be a string" };
115
+ }
116
+ if (!url || url.trim().length === 0) {
117
+ return { valid: false, error: "URL cannot be empty" };
118
+ }
119
+ const trimmedUrl = url.trim();
120
+ const validPrefixes = ["http://", "https://", "blob:", "data:", "file://"];
121
+ const hasValidPrefix = validPrefixes.some((prefix) => trimmedUrl.startsWith(prefix));
122
+ if (hasValidPrefix) {
123
+ return { valid: true };
124
+ }
125
+ if (trimmedUrl.startsWith("/") || trimmedUrl.startsWith("./") || trimmedUrl.startsWith("../")) {
126
+ return { valid: true };
127
+ }
128
+ return {
129
+ valid: false,
130
+ error: "URL must be an absolute URL (http://, https://, blob:, data:, file://) or a relative path (/, ./, ../)"
131
+ };
132
+ }
133
+
134
+ // src/utils/pdf-helpers.ts
135
+ function createPdfBlobUrl(pdfBytes) {
136
+ const blob = new Blob([pdfBytes], { type: "application/pdf" });
137
+ return URL.createObjectURL(blob);
138
+ }
139
+ async function urlToPdfBytes(url) {
140
+ try {
141
+ const response = await fetch(url);
142
+ if (!response.ok) {
143
+ throw new Error(`Failed to fetch PDF: ${response.status} ${response.statusText}`);
144
+ }
145
+ const arrayBuffer = await response.arrayBuffer();
146
+ const pdfBytes = new Uint8Array(arrayBuffer.byteLength);
147
+ pdfBytes.set(new Uint8Array(arrayBuffer));
148
+ const validation = validatePdfBytes(pdfBytes);
149
+ if (!validation.valid) {
150
+ throw new Error(validation.error || "Invalid PDF format");
151
+ }
152
+ return pdfBytes;
153
+ } catch (error) {
154
+ logger.error("Error fetching PDF from URL:", error);
155
+ throw error;
156
+ }
157
+ }
158
+
159
+ // src/utils/pdf-lib-loader.ts
160
+ var pdfLibPromise = null;
161
+ async function loadPdfLib() {
162
+ if (!pdfLibPromise) {
163
+ pdfLibPromise = import('pdf-lib');
164
+ }
165
+ return pdfLibPromise;
166
+ }
167
+
168
+ // src/utils/field-visibility.ts
169
+ function isFieldVisibleToSigner(field, multiSignerContext) {
170
+ if (!multiSignerContext.isMultiSigner) {
171
+ return true;
172
+ }
173
+ const { currentSignerEmail, isPrimarySigner, isFinalSigner } = multiSignerContext;
174
+ if (!field.assignedSignerEmail) {
175
+ return isFinalSigner;
176
+ }
177
+ if (field.assignedSignerEmail === currentSignerEmail) {
178
+ return true;
179
+ }
180
+ if (field.assignedSignerEmail.includes("recipients")) {
181
+ return isPrimarySigner;
182
+ }
183
+ if (field.assignedSignerEmail.includes("signers")) {
184
+ return isFinalSigner;
185
+ }
186
+ return false;
187
+ }
188
+ function filterFieldsBySigner(fields, multiSignerContext) {
189
+ if (!multiSignerContext.isMultiSigner) {
190
+ return fields;
191
+ }
192
+ return fields.filter((field) => isFieldVisibleToSigner(field, multiSignerContext));
193
+ }
194
+ function shouldFlattenField(field, multiSignerContext) {
195
+ if (!multiSignerContext.isMultiSigner) {
196
+ return true;
197
+ }
198
+ const { currentSignerEmail, isPrimarySigner, isFinalSigner } = multiSignerContext;
199
+ if (!field.assignedSignerEmail) {
200
+ return isFinalSigner;
201
+ }
202
+ if (field.assignedSignerEmail === currentSignerEmail) {
203
+ return true;
204
+ }
205
+ if (field.assignedSignerEmail.includes("recipients")) {
206
+ return isPrimarySigner;
207
+ }
208
+ if (field.assignedSignerEmail.includes("signers")) {
209
+ return isFinalSigner;
210
+ }
211
+ return false;
212
+ }
213
+
214
+ // src/utils/pdf-widget-helpers.ts
215
+ function findPageIndexByRef(pages, pageRef) {
216
+ if (!pageRef) return -1;
217
+ for (let i = 0; i < pages.length; i++) {
218
+ const page = pages[i];
219
+ if (page.ref === pageRef) {
220
+ return i;
221
+ }
222
+ }
223
+ return -1;
224
+ }
225
+ function getWidgetRectangleAndPage(widget, pages) {
226
+ const rect = widget.getRectangle?.();
227
+ if (!rect) return null;
228
+ const pageRef = widget.P?.();
229
+ if (!pageRef) return null;
230
+ const pageIndex = findPageIndexByRef(pages, pageRef);
231
+ if (pageIndex === -1) return null;
232
+ const page = pages[pageIndex];
233
+ if (!page) return null;
234
+ return { rect, page, pageIndex };
235
+ }
236
+ function findPageIndexWithFallback(pages, pageRef) {
237
+ if (!pageRef) {
238
+ return pages.length === 1 ? 0 : -1;
239
+ }
240
+ const pageIndex = findPageIndexByRef(pages, pageRef);
241
+ if (pageIndex === -1 && pages.length === 1) {
242
+ return 0;
243
+ }
244
+ return pageIndex;
245
+ }
246
+
247
+ // src/utils/pdf-field-type-helpers.ts
248
+ function detectFieldType(field) {
249
+ const typeName = field.constructor.name;
250
+ if (typeName === "PDFTextField") {
251
+ return "text";
252
+ } else if (typeName === "PDFCheckBox") {
253
+ return "checkbox";
254
+ } else if (typeName === "PDFDropdown") {
255
+ return "dropdown";
256
+ } else if (typeName === "PDFOptionList") {
257
+ return "optionlist";
258
+ } else if (typeName === "PDFRadioGroup") {
259
+ return "radiogroup";
260
+ } else if (typeName === "PDFSignature") {
261
+ return "signature";
262
+ }
263
+ return "unknown";
264
+ }
265
+ function extractFieldValue(field, fieldType) {
266
+ try {
267
+ switch (fieldType) {
268
+ case "text":
269
+ return field.getText?.() || "";
270
+ case "checkbox":
271
+ return field.isChecked?.() ? "true" : "false";
272
+ case "dropdown":
273
+ case "optionlist":
274
+ case "radiogroup":
275
+ return field.getSelected?.()?.[0] || "";
276
+ default:
277
+ return "";
278
+ }
279
+ } catch (error) {
280
+ return "";
281
+ }
282
+ }
283
+ function isRequiredField(field, fieldName, fieldType) {
284
+ if (fieldType === "signature") {
285
+ return true;
286
+ }
287
+ try {
288
+ if (field.isRequired?.()) {
289
+ return true;
290
+ }
291
+ } catch {
292
+ }
293
+ if (fieldName.includes("signature") || fieldName.includes("initials") || fieldName.includes("required")) {
294
+ return true;
295
+ }
296
+ return false;
297
+ }
298
+
299
+ // src/utils/pdf-manipulation.ts
300
+ async function readPdfFormFields(pdfBytes) {
301
+ try {
302
+ const validation = validatePdfBytes(pdfBytes);
303
+ if (!validation.valid) {
304
+ return [];
305
+ }
306
+ const { PDFDocument } = await loadPdfLib();
307
+ const pdfDoc = await PDFDocument.load(pdfBytes);
308
+ const form = pdfDoc.getForm();
309
+ const fields = form.getFields();
310
+ if (fields.length === 0) {
311
+ return [];
312
+ }
313
+ const formFields = fields.map((field) => {
314
+ const fieldName = field.getName();
315
+ const pdfField = field;
316
+ try {
317
+ const fieldType = detectFieldType(pdfField);
318
+ const value = extractFieldValue(pdfField, fieldType);
319
+ const required = isRequiredField(pdfField, fieldName, fieldType);
320
+ return {
321
+ name: fieldName,
322
+ type: fieldType,
323
+ required,
324
+ value
325
+ };
326
+ } catch (fieldError) {
327
+ return {
328
+ name: fieldName,
329
+ type: "text",
330
+ required: false,
331
+ value: ""
332
+ };
333
+ }
334
+ });
335
+ return formFields;
336
+ } catch (error) {
337
+ logger.error("Error reading PDF form fields:", error);
338
+ return [];
339
+ }
340
+ }
341
+ async function validatePdfFormFields(pdfBytes, fieldValues, signatures, extractedFields, multiSignerContext) {
342
+ try {
343
+ const pdfFormFields = await readPdfFormFields(pdfBytes);
344
+ const errors = [];
345
+ for (const field of pdfFormFields) {
346
+ if (field.required) {
347
+ if (multiSignerContext?.isMultiSigner && extractedFields) {
348
+ const extractedField = extractedFields.find((f) => f.name === field.name);
349
+ if (!extractedField) {
350
+ continue;
351
+ }
352
+ }
353
+ let hasValue = false;
354
+ const fieldValue = fieldValues[field.name];
355
+ const signatureValue = signatures[field.name];
356
+ const mainSignature = signatures["signature_field_main"];
357
+ const mainInitials = signatures["initials_field_main"] || fieldValues["initials_field_main"];
358
+ if (field.name.includes("signature") || field.type === "signature") {
359
+ hasValue = !!(signatureValue || fieldValue || mainSignature);
360
+ } else if (field.name.includes("initials")) {
361
+ hasValue = !!(signatureValue || fieldValue || mainInitials);
362
+ } else {
363
+ hasValue = !!(fieldValue || field.value);
364
+ }
365
+ if (!hasValue) {
366
+ let friendlyName = field.name.replace(/[_-]/g, " ").replace(/\b\w/g, (l) => l.toUpperCase());
367
+ if (field.name.includes("signature")) {
368
+ friendlyName = "Signature";
369
+ } else if (field.name.includes("initials")) {
370
+ friendlyName = "Initials";
371
+ }
372
+ errors.push(`${friendlyName} is required`);
373
+ }
374
+ }
375
+ }
376
+ return errors;
377
+ } catch (error) {
378
+ logger.error("Error validating PDF form fields:", error);
379
+ return ["Unable to validate form fields. Please ensure all required fields are completed."];
380
+ }
381
+ }
382
+ async function fillPdfWithSignatures(pdfBytes, signatures, formFieldValues = {}, currentSignerEmail, extractedFormFields, metadata, auditTrail, multiSignerContext) {
383
+ try {
384
+ const { PDFDocument, rgb, StandardFonts } = await loadPdfLib();
385
+ const pdfDoc = await PDFDocument.load(pdfBytes);
386
+ const form = pdfDoc.getForm();
387
+ const pages = pdfDoc.getPages();
388
+ const { pageMap: fieldPageMap, positionMap: fieldPositionMap } = await getFieldPageNumbers(pdfBytes);
389
+ const fields = form.getFields();
390
+ for (const field of fields) {
391
+ const fieldName = field.getName();
392
+ const fieldValue = formFieldValues[fieldName];
393
+ if (!fieldValue) {
394
+ if (fieldName.includes("radio")) {
395
+ }
396
+ continue;
397
+ }
398
+ try {
399
+ const fieldTypeName = field.constructor.name;
400
+ if (fieldTypeName === "PDFTextField" || fieldTypeName === "PDFTextField2") {
401
+ const textField = field;
402
+ textField.setText?.(fieldValue);
403
+ } else if (fieldTypeName === "PDFCheckBox" || fieldTypeName === "PDFCheckBox2") {
404
+ const checkBox = field;
405
+ if (fieldValue === "true" || fieldValue === "Yes" || fieldValue === "checked" || fieldValue === "On") {
406
+ checkBox.check?.();
407
+ } else {
408
+ checkBox.uncheck?.();
409
+ }
410
+ } else if (fieldTypeName === "PDFRadioGroup" || fieldTypeName === "PDFRadioGroup2") {
411
+ const radioGroup = field;
412
+ if (fieldValue === "true" || fieldValue === "false") {
413
+ continue;
414
+ }
415
+ const idxMatch = fieldValue.match(/__RADIO_OPTION_INDEX_(\d+)__/);
416
+ if (idxMatch && idxMatch[1]) {
417
+ const selectedIndex = parseInt(idxMatch[1], 10);
418
+ const options = radioGroup.getOptions?.() || [];
419
+ if (selectedIndex >= 0 && selectedIndex < options.length) {
420
+ radioGroup.select?.(options[selectedIndex]);
421
+ }
422
+ } else {
423
+ const options = radioGroup.getOptions?.() || [];
424
+ if (options.includes(fieldValue)) {
425
+ radioGroup.select?.(fieldValue);
426
+ } else {
427
+ }
428
+ }
429
+ } else if (fieldTypeName === "PDFDropdown" || fieldTypeName === "PDFDropdown2") {
430
+ const dropdown = field;
431
+ dropdown.select?.(fieldValue);
432
+ }
433
+ } catch (fieldError) {
434
+ logger.error(`Error setting field "${fieldName}":`, fieldError);
435
+ }
436
+ }
437
+ try {
438
+ form.updateFieldAppearances();
439
+ } catch (appearanceError) {
440
+ }
441
+ const mainSignatureData = signatures["signature_field_main"];
442
+ if (mainSignatureData && mainSignatureData.trim()) {
443
+ const signatureFieldNames = [
444
+ "signature",
445
+ "Signature",
446
+ "SIGNATURE",
447
+ "sign",
448
+ "Sign",
449
+ "SIGN",
450
+ "_es_:signer:signature"
451
+ ];
452
+ let foundSignatureFields = Object.keys(fieldPageMap).filter((fieldName) => {
453
+ const hasSignaturePattern = signatureFieldNames.some(
454
+ (pattern) => fieldName.toLowerCase().includes(pattern.toLowerCase())
455
+ );
456
+ const isNotTextField = !fieldName.toLowerCase().startsWith("text_");
457
+ return hasSignaturePattern && isNotTextField;
458
+ });
459
+ if (extractedFormFields && extractedFormFields.length > 0) {
460
+ const currentSignerSignatureFields = foundSignatureFields.filter((fieldName) => {
461
+ const fieldInfo = extractedFormFields.find((f) => f.name === fieldName);
462
+ if (!fieldInfo) {
463
+ return false;
464
+ }
465
+ const isActualSignatureField = (fieldInfo.type === "signature" || fieldName.toLowerCase().includes("signature")) && !fieldName.toLowerCase().includes("initials");
466
+ if (!isActualSignatureField) {
467
+ return false;
468
+ }
469
+ const isVisible = multiSignerContext ? isFieldVisibleToSigner(fieldInfo, multiSignerContext) : true;
470
+ return isVisible;
471
+ });
472
+ foundSignatureFields = currentSignerSignatureFields;
473
+ }
474
+ if (foundSignatureFields.length > 0) {
475
+ try {
476
+ let signatureImage;
477
+ if (mainSignatureData.includes("data:image/png")) {
478
+ signatureImage = await pdfDoc.embedPng(mainSignatureData);
479
+ } else if (mainSignatureData.includes("data:image/jpeg") || mainSignatureData.includes("data:image/jpg")) {
480
+ signatureImage = await pdfDoc.embedJpg(mainSignatureData);
481
+ } else {
482
+ const base64Data = mainSignatureData.split(",")[1];
483
+ if (!base64Data) {
484
+ throw new Error("Invalid signature data format: missing base64 data");
485
+ }
486
+ const binaryData = atob(base64Data);
487
+ if (binaryData.charCodeAt(0) === 137 && binaryData.charCodeAt(1) === 80 && binaryData.charCodeAt(2) === 78 && binaryData.charCodeAt(3) === 71) {
488
+ signatureImage = await pdfDoc.embedPng(mainSignatureData);
489
+ } else if (binaryData.charCodeAt(0) === 255 && binaryData.charCodeAt(1) === 216 && binaryData.charCodeAt(2) === 255) {
490
+ signatureImage = await pdfDoc.embedJpg(mainSignatureData);
491
+ } else {
492
+ throw new Error("Unsupported image format. Please use PNG or JPEG.");
493
+ }
494
+ }
495
+ for (const fieldName of foundSignatureFields) {
496
+ const pageNumber = fieldPageMap[fieldName];
497
+ if (pageNumber && pageNumber <= pages.length) {
498
+ const page = pages[pageNumber - 1];
499
+ if (!page) continue;
500
+ const fieldPosition = fieldPositionMap[fieldName];
501
+ if (fieldPosition) {
502
+ const x = fieldPosition.x;
503
+ const y = fieldPosition.y;
504
+ const width = Math.max(fieldPosition.width, 80);
505
+ const height = Math.max(fieldPosition.height, 30);
506
+ const signatureDims = signatureImage.scaleToFit(width - 4, height - 4);
507
+ const finalX = x;
508
+ const finalY = y;
509
+ page.drawImage(signatureImage, {
510
+ x: finalX,
511
+ y: finalY,
512
+ width: signatureDims.width,
513
+ height: signatureDims.height
514
+ });
515
+ } else {
516
+ }
517
+ } else {
518
+ }
519
+ }
520
+ } catch (error) {
521
+ logger.error("Error applying signatures:", error);
522
+ }
523
+ }
524
+ }
525
+ const mainInitialsData = formFieldValues["initials_field_main"];
526
+ if (mainInitialsData && mainInitialsData.trim()) {
527
+ const initialsFieldNames = ["initials", "Initials", "INITIALS"];
528
+ let foundInitialsFields = Object.keys(fieldPageMap).filter(
529
+ (fieldName) => initialsFieldNames.some((pattern) => fieldName.toLowerCase().includes(pattern.toLowerCase()))
530
+ );
531
+ if (extractedFormFields && extractedFormFields.length > 0) {
532
+ foundInitialsFields = foundInitialsFields.filter((fieldName) => {
533
+ const fieldInfo = extractedFormFields.find((f) => f.name === fieldName);
534
+ if (!fieldInfo) return false;
535
+ const isActualInitialsField = fieldInfo.type === "initials" || fieldName.toLowerCase().includes("initials");
536
+ if (!isActualInitialsField) return false;
537
+ return !fieldInfo.assignedSignerEmail || fieldInfo.assignedSignerEmail === currentSignerEmail;
538
+ });
539
+ }
540
+ if (foundInitialsFields.length > 0) {
541
+ try {
542
+ const font = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
543
+ for (const fieldName of foundInitialsFields) {
544
+ const pageNumber = fieldPageMap[fieldName];
545
+ if (pageNumber && pageNumber <= pages.length) {
546
+ const page = pages[pageNumber - 1];
547
+ if (!page) continue;
548
+ const fieldPosition = fieldPositionMap[fieldName];
549
+ if (fieldPosition) {
550
+ const x = fieldPosition.x;
551
+ const y = fieldPosition.y;
552
+ const height = Math.max(fieldPosition.height, 20);
553
+ const fontSize = Math.min(height * 0.7, 14);
554
+ const finalX = x + 2;
555
+ const finalY = y + (height - fontSize) / 2;
556
+ page.drawText(mainInitialsData, {
557
+ x: finalX,
558
+ y: finalY,
559
+ size: fontSize,
560
+ font,
561
+ color: rgb(0, 0, 0)
562
+ });
563
+ }
564
+ }
565
+ }
566
+ } catch (error) {
567
+ logger.error("Error applying initials:", error);
568
+ }
569
+ }
570
+ }
571
+ const allFormFields = form.getFields();
572
+ let fieldsToFlatten;
573
+ if (multiSignerContext?.isMultiSigner) {
574
+ fieldsToFlatten = allFormFields.filter((f) => {
575
+ const fieldName = f.getName();
576
+ if (!extractedFormFields || extractedFormFields.length === 0) {
577
+ return true;
578
+ }
579
+ const fieldInfo = extractedFormFields.find((fi) => fi.name === fieldName);
580
+ if (!fieldInfo) {
581
+ return true;
582
+ }
583
+ return shouldFlattenField(fieldInfo, multiSignerContext);
584
+ });
585
+ } else {
586
+ fieldsToFlatten = allFormFields;
587
+ }
588
+ let flattenedCount = 0;
589
+ for (const field of fieldsToFlatten) {
590
+ const fieldName = field.getName();
591
+ const fieldType = field.constructor.name;
592
+ try {
593
+ const userEnteredValue = formFieldValues[fieldName];
594
+ const isSignatureField = fieldName.toLowerCase().includes("signature") || fieldType.includes("Signature");
595
+ const isInitialsField = fieldName.toLowerCase().includes("initials") || fieldType.includes("Initials");
596
+ if (isSignatureField || isInitialsField) {
597
+ form.removeField(field);
598
+ flattenedCount++;
599
+ continue;
600
+ }
601
+ const fieldWithWidgets = field;
602
+ const widgets = fieldWithWidgets.acroField?.getWidgets?.() || [];
603
+ if (fieldType === "PDFCheckBox" || fieldType === "PDFCheckBox2") {
604
+ const stringValue = String(userEnteredValue || "");
605
+ const isChecked = stringValue === "true" || stringValue === "Yes" || stringValue === "On" || stringValue === "checked";
606
+ if (isChecked) {
607
+ for (const widget of widgets) {
608
+ const result = getWidgetRectangleAndPage(widget, pages);
609
+ if (!result) continue;
610
+ const { rect, page } = result;
611
+ const checkboxSize = Math.min(rect.width, rect.height) * 0.6;
612
+ const checkboxX = rect.x + (rect.width - checkboxSize) / 2;
613
+ const checkboxY = rect.y + (rect.height - checkboxSize) / 2;
614
+ page.drawRectangle({
615
+ x: checkboxX,
616
+ y: checkboxY,
617
+ width: checkboxSize,
618
+ height: checkboxSize,
619
+ borderColor: rgb(0, 0, 0),
620
+ borderWidth: 1
621
+ });
622
+ const checkSize = checkboxSize * 0.6;
623
+ const textWidth = checkSize * 0.6;
624
+ const textHeight = checkSize * 0.8;
625
+ page.drawText("X", {
626
+ x: checkboxX + (checkboxSize - textWidth) / 2,
627
+ y: checkboxY + (checkboxSize - textHeight) / 2 + textHeight * 0.2,
628
+ size: checkSize,
629
+ font: await pdfDoc.embedFont(StandardFonts.HelveticaBold),
630
+ color: rgb(0, 0, 0)
631
+ });
632
+ }
633
+ }
634
+ } else if (fieldType === "PDFRadioGroup" || fieldType === "PDFRadioGroup2") {
635
+ let selectedValue = userEnteredValue ? String(userEnteredValue) : "";
636
+ if (selectedValue.startsWith("__RADIO_OPTION_INDEX_")) {
637
+ const indexMatch = selectedValue.match(/__RADIO_OPTION_INDEX_(\d+)__/);
638
+ if (indexMatch && indexMatch[1]) {
639
+ selectedValue = indexMatch[1];
640
+ }
641
+ }
642
+ let selectedIndex = -1;
643
+ if (selectedValue === "Option A") selectedIndex = 0;
644
+ else if (selectedValue === "Option B") selectedIndex = 1;
645
+ else if (selectedValue === "Option C") selectedIndex = 2;
646
+ else if (/^\d+$/.test(selectedValue)) {
647
+ selectedIndex = parseInt(selectedValue, 10);
648
+ }
649
+ const fieldInfo = extractedFormFields?.find((f) => f.name === fieldName);
650
+ const labelFont = await pdfDoc.embedFont("Helvetica-Bold");
651
+ const drawnGroupLabels = /* @__PURE__ */ new Set();
652
+ const drawnOptionLabels = /* @__PURE__ */ new Set();
653
+ for (let i = 0; i < widgets.length; i++) {
654
+ const widget = widgets[i];
655
+ const result = getWidgetRectangleAndPage(widget, pages);
656
+ if (!result) continue;
657
+ const { rect, page, pageIndex } = result;
658
+ if (i === 0 && fieldInfo?.label && fieldInfo.label.trim() && !isAutoGeneratedLabel(fieldInfo.label)) {
659
+ const groupLabelKey = `${pageIndex}-${fieldName}-grouplabel`;
660
+ if (!drawnGroupLabels.has(groupLabelKey)) {
661
+ const labelY = rect.y + rect.height + 3;
662
+ page.drawText(fieldInfo.label, {
663
+ x: rect.x,
664
+ y: labelY,
665
+ size: 8,
666
+ font: labelFont,
667
+ color: rgb(0, 0, 0)
668
+ });
669
+ drawnGroupLabels.add(groupLabelKey);
670
+ }
671
+ } else if (i === 0 && isAutoGeneratedLabel(fieldInfo?.label || "")) {
672
+ }
673
+ const radioSize = Math.min(rect.width, rect.height) * 0.5;
674
+ const radioX = rect.x + (rect.width - radioSize) / 2;
675
+ const radioY = rect.y + (rect.height - radioSize) / 2;
676
+ page.drawCircle({
677
+ x: radioX + radioSize / 2,
678
+ y: radioY + radioSize / 2,
679
+ size: radioSize,
680
+ borderColor: rgb(0, 0, 0),
681
+ borderWidth: 1
682
+ });
683
+ if (i === selectedIndex) {
684
+ const circleSize = radioSize * 0.5;
685
+ page.drawCircle({
686
+ x: radioX + radioSize / 2,
687
+ y: radioY + radioSize / 2,
688
+ size: circleSize,
689
+ color: rgb(0, 0, 0)
690
+ });
691
+ }
692
+ if (fieldInfo?.options && fieldInfo.options.length > i) {
693
+ const optionText = fieldInfo.options[i];
694
+ const optionKey = `${pageIndex}-${fieldName}-opt-${i}`;
695
+ if (optionText && !drawnOptionLabels.has(optionKey)) {
696
+ page.drawText(optionText, {
697
+ x: rect.x + rect.width + 4,
698
+ y: rect.y + (rect.height - 8) / 2,
699
+ size: 8,
700
+ font: labelFont,
701
+ color: rgb(0, 0, 0)
702
+ });
703
+ drawnOptionLabels.add(optionKey);
704
+ }
705
+ }
706
+ }
707
+ if (fieldInfo?.options) {
708
+ }
709
+ } else if (fieldType === "PDFDropdown" || fieldType === "PDFDropdown2") {
710
+ const selectedValue = userEnteredValue ? String(userEnteredValue) : "";
711
+ if (selectedValue && selectedValue.trim()) {
712
+ for (const widget of widgets) {
713
+ const result = getWidgetRectangleAndPage(widget, pages);
714
+ if (!result) continue;
715
+ const { rect, page } = result;
716
+ page.drawText(selectedValue, {
717
+ x: rect.x + 2,
718
+ y: rect.y + 2,
719
+ size: 10,
720
+ font: await pdfDoc.embedFont(StandardFonts.Helvetica),
721
+ color: rgb(0, 0, 0)
722
+ });
723
+ }
724
+ }
725
+ } else {
726
+ const fieldValue = userEnteredValue ? String(userEnteredValue) : "";
727
+ if (fieldValue && fieldValue.trim()) {
728
+ for (const widget of widgets) {
729
+ const result = getWidgetRectangleAndPage(widget, pages);
730
+ if (!result) continue;
731
+ const { rect, page } = result;
732
+ page.drawText(fieldValue, {
733
+ x: rect.x + 2,
734
+ y: rect.y + 2,
735
+ size: 10,
736
+ font: await pdfDoc.embedFont(StandardFonts.Helvetica),
737
+ color: rgb(0, 0, 0)
738
+ });
739
+ }
740
+ }
741
+ }
742
+ form.removeField(field);
743
+ flattenedCount++;
744
+ } catch (error) {
745
+ }
746
+ }
747
+ const remainingFields = form.getFields();
748
+ if (multiSignerContext?.isMultiSigner) {
749
+ const expectedRemainingCount = allFormFields.length - fieldsToFlatten.length;
750
+ if (remainingFields.length === expectedRemainingCount) {
751
+ } else {
752
+ }
753
+ } else {
754
+ if (remainingFields.length > 0) {
755
+ logger.error(`MANUAL FLATTENING INCOMPLETE: ${remainingFields.length} fields still exist!`);
756
+ logger.error(`Remaining fields:`, remainingFields.map((f) => f.getName()));
757
+ try {
758
+ form.flatten();
759
+ const finalRemainingFields = form.getFields();
760
+ if (finalRemainingFields.length > 0) {
761
+ logger.error(`ALL FLATTENING METHODS FAILED: ${finalRemainingFields.length} fields still exist!`);
762
+ logger.error(`Final remaining fields:`, finalRemainingFields.map((f) => f.getName()));
763
+ }
764
+ } catch (fallbackError) {
765
+ logger.error("Fallback flattening failed:", fallbackError);
766
+ }
767
+ } else {
768
+ }
769
+ }
770
+ try {
771
+ const documentTitle = metadata?.documentId ? `Document ID: ${metadata.documentId}` : "Signed Document";
772
+ pdfDoc.setTitle(documentTitle);
773
+ const documentSubject = metadata?.submissionId ? `Submission ID: ${metadata.submissionId}` : metadata?.signerEmail ? `Signed by ${metadata.signerEmail}` : "Digitally Signed Document";
774
+ pdfDoc.setSubject(documentSubject);
775
+ const documentAuthor = metadata?.author || metadata?.signerEmail || "Unknown";
776
+ pdfDoc.setAuthor(documentAuthor);
777
+ pdfDoc.setProducer("Created by signiphi (https://signiphi.ai/)");
778
+ if (metadata?.signerEmail) {
779
+ pdfDoc.setCreator(`Signer: ${metadata.signerEmail}`);
780
+ }
781
+ if (metadata?.createdAt) {
782
+ pdfDoc.setCreationDate(metadata.createdAt);
783
+ }
784
+ pdfDoc.setModificationDate(/* @__PURE__ */ new Date());
785
+ const keywords = [];
786
+ if (metadata?.signerEmail) {
787
+ keywords.push(metadata.signerEmail);
788
+ }
789
+ if (metadata?.signerInitials && metadata.signerInitials.trim()) {
790
+ keywords.push(`Initials:${metadata.signerInitials.trim()}`);
791
+ }
792
+ if (auditTrail) {
793
+ try {
794
+ if (auditTrail.userAgent) {
795
+ keywords.push(`UserAgent:${auditTrail.userAgent}`);
796
+ }
797
+ if (auditTrail.screenResolution) {
798
+ keywords.push(`Screen:${auditTrail.screenResolution}`);
799
+ }
800
+ if (auditTrail.timezone) {
801
+ keywords.push(`Timezone:${auditTrail.timezone}`);
802
+ }
803
+ if (auditTrail.language) {
804
+ keywords.push(`Language:${auditTrail.language}`);
805
+ }
806
+ if (auditTrail.platform) {
807
+ keywords.push(`Platform:${auditTrail.platform}`);
808
+ }
809
+ if (auditTrail.ipAddress) {
810
+ keywords.push(`IP:${auditTrail.ipAddress}`);
811
+ }
812
+ if (auditTrail.geolocation) {
813
+ const { latitude, longitude, accuracy } = auditTrail.geolocation;
814
+ const lat = latitude.toFixed(4);
815
+ const lon = longitude.toFixed(4);
816
+ keywords.push(`Location:${lat},${lon}`);
817
+ if (accuracy !== void 0) {
818
+ keywords.push(`LocationAccuracy:${accuracy}m`);
819
+ }
820
+ }
821
+ } catch (auditError) {
822
+ logger.warn("\u26A0\uFE0F Error adding audit trail to keywords:", auditError);
823
+ }
824
+ }
825
+ if (keywords.length > 0) {
826
+ pdfDoc.setKeywords(keywords);
827
+ }
828
+ } catch (error) {
829
+ logger.error("Failed to set document metadata:", error);
830
+ }
831
+ const finalPdfBytes = await pdfDoc.save();
832
+ return finalPdfBytes;
833
+ } catch (error) {
834
+ logger.error("Error filling PDF with signatures:", error);
835
+ throw error;
836
+ }
837
+ }
838
+ async function getFieldPageNumbers(pdfBytes) {
839
+ try {
840
+ const { PDFDocument } = await loadPdfLib();
841
+ const pdfDoc = await PDFDocument.load(pdfBytes);
842
+ const form = pdfDoc.getForm();
843
+ const fields = form.getFields();
844
+ const pages = pdfDoc.getPages();
845
+ const pageMap = {};
846
+ const positionMap = {};
847
+ for (const field of fields) {
848
+ const fieldName = field.getName();
849
+ const fieldWithAcro = field;
850
+ const widgets = fieldWithAcro.acroField?.getWidgets?.() ?? [];
851
+ if (widgets.length > 0) {
852
+ const widget = widgets[0];
853
+ if (!widget) continue;
854
+ const rect = widget.getRectangle?.();
855
+ const pageRef = widget.P?.();
856
+ if (rect && pageRef) {
857
+ const pageIndex = findPageIndexByRef(pages, pageRef);
858
+ if (pageIndex !== -1) {
859
+ const pageNumber = pageIndex + 1;
860
+ pageMap[fieldName] = pageNumber;
861
+ positionMap[fieldName] = {
862
+ x: rect.x,
863
+ y: rect.y,
864
+ width: rect.width,
865
+ height: rect.height,
866
+ page: pageNumber
867
+ };
868
+ }
869
+ }
870
+ }
871
+ }
872
+ return { pageMap, positionMap };
873
+ } catch (error) {
874
+ logger.error("Error extracting field page numbers:", error);
875
+ return { pageMap: {}, positionMap: {} };
876
+ }
877
+ }
878
+
879
+ // src/utils/field-extraction.ts
880
+ var PDFName;
881
+ async function extractVisibleFormFields(pdfBytes, currentSignerEmail) {
882
+ try {
883
+ const pdfLibModule = await loadPdfLib();
884
+ const { PDFDocument, AnnotationFlags, PDFName: PDFNameClass } = pdfLibModule;
885
+ PDFName = PDFNameClass;
886
+ const pdfDoc = await PDFDocument.load(pdfBytes);
887
+ const form = pdfDoc.getForm();
888
+ const fields = form.getFields();
889
+ const visibleFields = [];
890
+ let hasAnyInitialsFields = false;
891
+ let hasInitialsFieldsForCurrentSigner = false;
892
+ for (const field of fields) {
893
+ const fieldName = field.getName();
894
+ const fieldWithExtensions = field;
895
+ const widgets = fieldWithExtensions.acroField?.getWidgets?.() ?? [];
896
+ let isVisible = true;
897
+ for (const widget of widgets) {
898
+ const hasHiddenFlag = widget.hasFlag?.(AnnotationFlags.Hidden) ?? false;
899
+ const hasNoViewFlag = widget.hasFlag?.(AnnotationFlags.NoView) ?? false;
900
+ if (hasHiddenFlag || hasNoViewFlag) {
901
+ isVisible = false;
902
+ break;
903
+ }
904
+ }
905
+ if (isVisible) {
906
+ const decodedInfo = decodeFieldName(fieldName);
907
+ let fieldType = "text" /* TEXT */;
908
+ const fieldTypeName = field.constructor.name;
909
+ const isActualSignatureField = fieldTypeName.includes("Signature") || decodedInfo.decodedFieldName.toLowerCase().includes("signature") && !fieldTypeName.includes("CheckBox") && !fieldTypeName.includes("Radio");
910
+ const isActualInitialsField = decodedInfo.decodedFieldName.toLowerCase().includes("initials") && !fieldTypeName.includes("CheckBox") && !fieldTypeName.includes("Radio");
911
+ if (isActualSignatureField) {
912
+ fieldType = "signature" /* SIGNATURE */;
913
+ } else if (isActualInitialsField) {
914
+ fieldType = "initials" /* INITIALS */;
915
+ hasAnyInitialsFields = true;
916
+ if (currentSignerEmail && decodedInfo.assignedSignerEmail === currentSignerEmail) {
917
+ hasInitialsFieldsForCurrentSigner = true;
918
+ }
919
+ } else if (decodedInfo.decodedFieldName.toLowerCase().includes("date")) {
920
+ fieldType = "date" /* DATE */;
921
+ } else if (fieldTypeName.includes("CheckBox")) {
922
+ fieldType = "checkbox" /* CHECKBOX */;
923
+ } else if (fieldTypeName.toLowerCase().includes("dropdown")) {
924
+ fieldType = "dropdown" /* DROPDOWN */;
925
+ } else if (fieldTypeName.includes("Radio")) {
926
+ fieldType = "radio" /* RADIO */;
927
+ }
928
+ let displayLabel = decodedInfo.displayLabel;
929
+ if (!displayLabel || !displayLabel.trim()) {
930
+ try {
931
+ const tuLabel = extractTULabel(fieldWithExtensions);
932
+ if (tuLabel && tuLabel.trim()) {
933
+ displayLabel = tuLabel;
934
+ }
935
+ } catch (tuError) {
936
+ }
937
+ }
938
+ if (!displayLabel || !displayLabel.trim()) {
939
+ displayLabel = generateFallbackLabel(decodedInfo.decodedFieldName, fieldType);
940
+ }
941
+ const esignField = {
942
+ id: fieldName,
943
+ // Keep original encoded name as ID
944
+ name: fieldName,
945
+ // Keep original for removal later
946
+ type: fieldType,
947
+ label: displayLabel,
948
+ // Use friendly label for display
949
+ position: { x: 0, y: 0, width: 100, height: 30, page: 1 },
950
+ // Default position
951
+ required: fieldWithExtensions.isRequired?.() ?? false,
952
+ placeholder: decodedInfo.fieldPlaceholder || "",
953
+ // Use extracted placeholder
954
+ assignedSignerEmail: decodedInfo.assignedSignerEmail
955
+ // Add assigned signer
956
+ };
957
+ if (fieldType === "dropdown" /* DROPDOWN */ || fieldType === "radio" /* RADIO */) {
958
+ try {
959
+ const options = fieldWithExtensions.getOptions?.() ?? [];
960
+ esignField.options = options;
961
+ if (options.length > 0) {
962
+ }
963
+ } catch (error) {
964
+ logger.warn("Error extracting options for field:", error);
965
+ }
966
+ }
967
+ visibleFields.push(esignField);
968
+ }
969
+ }
970
+ const mainFields = [];
971
+ mainFields.push({
972
+ id: "signature_field_main",
973
+ name: "signature_field_main",
974
+ type: "signature" /* SIGNATURE */,
975
+ label: "Your Signature (will be applied to all signature fields)",
976
+ position: { x: 0, y: 0, width: 200, height: 60, page: 1 },
977
+ required: true,
978
+ placeholder: "Draw or upload your signature",
979
+ assignedSignerEmail: currentSignerEmail
980
+ });
981
+ const shouldCreateInitialsField = hasInitialsFieldsForCurrentSigner || hasAnyInitialsFields && !currentSignerEmail;
982
+ if (shouldCreateInitialsField) {
983
+ if (hasInitialsFieldsForCurrentSigner) {
984
+ } else {
985
+ }
986
+ mainFields.push({
987
+ id: "initials_field_main",
988
+ name: "initials_field_main",
989
+ type: "initials" /* INITIALS */,
990
+ label: "Your Initials (will be applied to all initials fields)",
991
+ position: { x: 0, y: 0, width: 100, height: 40, page: 1 },
992
+ required: true,
993
+ placeholder: "Enter your initials",
994
+ assignedSignerEmail: currentSignerEmail
995
+ });
996
+ }
997
+ return [...mainFields, ...visibleFields];
998
+ } catch (error) {
999
+ logger.error("Error extracting visible form fields from PDF:", error);
1000
+ return [];
1001
+ }
1002
+ }
1003
+ function decodeFieldName(fieldName) {
1004
+ let decodedFieldName = fieldName;
1005
+ let displayLabel = "";
1006
+ let assignedSignerEmail = void 0;
1007
+ let fieldPlaceholder = "";
1008
+ if (fieldName.includes("__LABEL__")) {
1009
+ const labelMarkerIndex = fieldName.indexOf("__LABEL__");
1010
+ decodedFieldName = fieldName.substring(0, labelMarkerIndex);
1011
+ const afterLabel = fieldName.substring(labelMarkerIndex + 9);
1012
+ if (afterLabel.includes("__SIGNER__")) {
1013
+ const signerMarkerIndex = afterLabel.indexOf("__SIGNER__");
1014
+ displayLabel = afterLabel.substring(0, signerMarkerIndex);
1015
+ const afterSigner = afterLabel.substring(signerMarkerIndex + 10);
1016
+ if (afterSigner.includes("__PLACEHOLDER__")) {
1017
+ const placeholderMarkerIndex = afterSigner.indexOf("__PLACEHOLDER__");
1018
+ assignedSignerEmail = afterSigner.substring(0, placeholderMarkerIndex);
1019
+ fieldPlaceholder = afterSigner.substring(placeholderMarkerIndex + 15);
1020
+ } else {
1021
+ assignedSignerEmail = afterSigner;
1022
+ }
1023
+ } else if (afterLabel.includes("__PLACEHOLDER__")) {
1024
+ const placeholderMarkerIndex = afterLabel.indexOf("__PLACEHOLDER__");
1025
+ displayLabel = afterLabel.substring(0, placeholderMarkerIndex);
1026
+ fieldPlaceholder = afterLabel.substring(placeholderMarkerIndex + 15);
1027
+ } else {
1028
+ displayLabel = afterLabel;
1029
+ }
1030
+ } else if (fieldName.includes("__SIGNER__")) {
1031
+ const signerMarkerIndex = fieldName.indexOf("__SIGNER__");
1032
+ decodedFieldName = fieldName.substring(0, signerMarkerIndex);
1033
+ const afterSigner = fieldName.substring(signerMarkerIndex + 10);
1034
+ if (afterSigner.includes("__PLACEHOLDER__")) {
1035
+ const placeholderMarkerIndex = afterSigner.indexOf("__PLACEHOLDER__");
1036
+ assignedSignerEmail = afterSigner.substring(0, placeholderMarkerIndex);
1037
+ fieldPlaceholder = afterSigner.substring(placeholderMarkerIndex + 15);
1038
+ } else {
1039
+ assignedSignerEmail = afterSigner;
1040
+ }
1041
+ } else if (fieldName.includes("__PLACEHOLDER__")) {
1042
+ const placeholderMarkerIndex = fieldName.indexOf("__PLACEHOLDER__");
1043
+ decodedFieldName = fieldName.substring(0, placeholderMarkerIndex);
1044
+ fieldPlaceholder = fieldName.substring(placeholderMarkerIndex + 15);
1045
+ }
1046
+ return {
1047
+ decodedFieldName,
1048
+ displayLabel,
1049
+ assignedSignerEmail,
1050
+ fieldPlaceholder
1051
+ };
1052
+ }
1053
+ function generateFallbackLabel(decodedFieldName, fieldType) {
1054
+ let displayLabel = decodedFieldName;
1055
+ displayLabel = displayLabel.replace(/_signature$/i, "").replace(/_initials$/i, "").replace(/_date$/i, "");
1056
+ if (/^(text|signature|initials|date)_\d+$/i.test(displayLabel)) {
1057
+ if (fieldType === "signature" /* SIGNATURE */) return "Signature";
1058
+ if (fieldType === "initials" /* INITIALS */) return "Initials";
1059
+ if (fieldType === "date" /* DATE */) return "Date";
1060
+ if (fieldType === "text" /* TEXT */) return "Text";
1061
+ if (fieldType === "checkbox" /* CHECKBOX */) return "Checkbox";
1062
+ if (fieldType === "dropdown" /* DROPDOWN */) return "Dropdown";
1063
+ if (fieldType === "radio" /* RADIO */) return "Option";
1064
+ } else {
1065
+ displayLabel = displayLabel.replace(/_/g, " ").replace(/\s+\d+$/g, "").trim().split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
1066
+ }
1067
+ return displayLabel;
1068
+ }
1069
+ function extractTULabel(field) {
1070
+ try {
1071
+ if (!field.acroField?.dict) {
1072
+ return "";
1073
+ }
1074
+ const tuLabel = field.acroField.dict.get(PDFName.of("TU"));
1075
+ if (!tuLabel) {
1076
+ return "";
1077
+ }
1078
+ const tuValue = tuLabel.toString();
1079
+ if (!tuValue || !tuValue.trim()) {
1080
+ return "";
1081
+ }
1082
+ if (tuValue.startsWith("(") || tuValue.startsWith("<")) {
1083
+ const match = tuValue.match(/^\((.*)\)$/) || tuValue.match(/^<(.*)>$/);
1084
+ if (match && match[1]) {
1085
+ return match[1];
1086
+ }
1087
+ }
1088
+ return tuValue;
1089
+ } catch (error) {
1090
+ return "";
1091
+ }
1092
+ }
1093
+ var _workerSrcComputed = false;
1094
+ var _cachedWorkerSrc = null;
1095
+ function getDefaultWorkerSrc() {
1096
+ if (_workerSrcComputed && _cachedWorkerSrc !== null) {
1097
+ return _cachedWorkerSrc;
1098
+ }
1099
+ let workerSrc = "/pdfjs/build/pdf.worker.mjs";
1100
+ if (typeof window !== "undefined" && typeof URL !== "undefined") {
1101
+ try {
1102
+ const meta = (function() {
1103
+ try {
1104
+ return new Function("return import.meta")();
1105
+ } catch {
1106
+ return null;
1107
+ }
1108
+ })();
1109
+ if (meta && meta.url) {
1110
+ workerSrc = new URL("pdfjs-dist/build/pdf.worker.mjs", meta.url).href;
1111
+ }
1112
+ } catch {
1113
+ }
1114
+ }
1115
+ _workerSrcComputed = true;
1116
+ _cachedWorkerSrc = workerSrc;
1117
+ return workerSrc;
1118
+ }
1119
+ var globalConfig = {
1120
+ workerSrc: ""
1121
+ // Lazy-initialized below
1122
+ };
1123
+ var _configInitialized = false;
1124
+ function ensureConfigInitialized() {
1125
+ if (!_configInitialized) {
1126
+ globalConfig.workerSrc = getDefaultWorkerSrc();
1127
+ _configInitialized = true;
1128
+ if (typeof window !== "undefined") {
1129
+ pdfjsLib2.GlobalWorkerOptions.workerSrc = globalConfig.workerSrc;
1130
+ }
1131
+ }
1132
+ }
1133
+ function initializePdfJs() {
1134
+ ensureConfigInitialized();
1135
+ }
1136
+
1137
+ // src/utils/errors.ts
1138
+ var PdfValidationError = class _PdfValidationError extends Error {
1139
+ constructor(message, details) {
1140
+ super(message);
1141
+ this.details = details;
1142
+ this.name = "PdfValidationError";
1143
+ if (Error.captureStackTrace) {
1144
+ Error.captureStackTrace(this, _PdfValidationError);
1145
+ }
1146
+ }
1147
+ };
1148
+ var PdfProcessingError = class _PdfProcessingError extends Error {
1149
+ constructor(message, details) {
1150
+ super(message);
1151
+ this.details = details;
1152
+ this.name = "PdfProcessingError";
1153
+ if (Error.captureStackTrace) {
1154
+ Error.captureStackTrace(this, _PdfProcessingError);
1155
+ }
1156
+ }
1157
+ };
1158
+
1159
+ // src/utils/attachment-validators.ts
1160
+ var DEFAULT_ATTACHMENT_CONSTRAINTS = {
1161
+ maxFileSize: 10 * 1024 * 1024,
1162
+ // 10MB
1163
+ maxTotalSize: 50 * 1024 * 1024,
1164
+ // 50MB
1165
+ maxFiles: 10,
1166
+ allowedTypes: ["image/*", "application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"],
1167
+ allowedExtensions: [".pdf", ".jpg", ".jpeg", ".png", ".gif", ".doc", ".docx"]
1168
+ };
1169
+ function validateFile(file, constraints = DEFAULT_ATTACHMENT_CONSTRAINTS) {
1170
+ const errors = [];
1171
+ if (file.size > constraints.maxFileSize) {
1172
+ const maxMB = (constraints.maxFileSize / 1024 / 1024).toFixed(1);
1173
+ const fileMB = (file.size / 1024 / 1024).toFixed(1);
1174
+ errors.push(`File "${file.name}" is too large (${fileMB}MB). Maximum size is ${maxMB}MB.`);
1175
+ }
1176
+ const isTypeAllowed = constraints.allowedTypes.some((allowedType) => {
1177
+ if (allowedType.endsWith("/*")) {
1178
+ const prefix = allowedType.slice(0, -2);
1179
+ return file.type.startsWith(prefix);
1180
+ }
1181
+ return file.type === allowedType;
1182
+ });
1183
+ if (!isTypeAllowed && constraints.allowedTypes.length > 0) {
1184
+ errors.push(
1185
+ `File type "${file.type}" is not allowed for "${file.name}". Allowed types: ${constraints.allowedTypes.join(", ")}`
1186
+ );
1187
+ }
1188
+ if (constraints.allowedExtensions && constraints.allowedExtensions.length > 0) {
1189
+ const fileExt = "." + file.name.split(".").pop()?.toLowerCase();
1190
+ const isExtAllowed = constraints.allowedExtensions.some(
1191
+ (ext) => ext.toLowerCase() === fileExt
1192
+ );
1193
+ if (!isExtAllowed) {
1194
+ errors.push(
1195
+ `File extension "${fileExt}" is not allowed for "${file.name}". Allowed extensions: ${constraints.allowedExtensions.join(", ")}`
1196
+ );
1197
+ }
1198
+ }
1199
+ return {
1200
+ valid: errors.length === 0,
1201
+ errors
1202
+ };
1203
+ }
1204
+ function isImageType(fileType) {
1205
+ return fileType.startsWith("image/");
1206
+ }
1207
+ function formatFileSize(bytes) {
1208
+ if (bytes === 0) return "0 Bytes";
1209
+ const k = 1024;
1210
+ const sizes = ["Bytes", "KB", "MB", "GB"];
1211
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
1212
+ return Math.round(bytes / Math.pow(k, i) * 100) / 100 + " " + sizes[i];
1213
+ }
1214
+ function isValidISODate(value) {
1215
+ if (!value || typeof value !== "string") {
1216
+ return false;
1217
+ }
1218
+ const isoPattern = /^\d{4}-\d{2}-\d{2}$/;
1219
+ if (!isoPattern.test(value)) {
1220
+ return false;
1221
+ }
1222
+ try {
1223
+ const date = parseISO(value);
1224
+ return isValid(date) && !isNaN(date.getTime());
1225
+ } catch {
1226
+ return false;
1227
+ }
1228
+ }
1229
+
1230
+ // src/utils/pdf-viewer-filter.ts
1231
+ async function drawFieldLabelsOnPdf(pdfDoc, fieldsToLabel, rgb) {
1232
+ try {
1233
+ const labelFont = await pdfDoc.embedFont("Helvetica-Bold");
1234
+ const pages = pdfDoc.getPages();
1235
+ const form = pdfDoc.getForm();
1236
+ const drawnOnce = /* @__PURE__ */ new Set();
1237
+ for (const field of fieldsToLabel) {
1238
+ const isRadioField = field.type === "radio" /* RADIO */;
1239
+ const hasRadioOptions = isRadioField && field.options && field.options.length > 0;
1240
+ const hasCustomLabel = field.label && field.label.trim() && !isAutoGeneratedLabel(field.label);
1241
+ if (!hasCustomLabel && !hasRadioOptions) {
1242
+ continue;
1243
+ }
1244
+ const pdfField = form.getFieldMaybe(field.name);
1245
+ if (!pdfField) {
1246
+ continue;
1247
+ }
1248
+ const fieldWithExtensions = pdfField;
1249
+ const widgets = fieldWithExtensions.acroField?.getWidgets?.() || [];
1250
+ if (widgets.length === 0) continue;
1251
+ if (isRadioField) {
1252
+ if (field.label && field.label.trim() && !isAutoGeneratedLabel(field.label)) {
1253
+ const firstWidget = widgets[0];
1254
+ if (!firstWidget) continue;
1255
+ const firstRect = firstWidget.getRectangle?.();
1256
+ if (firstRect) {
1257
+ const pageRef = firstWidget.P?.();
1258
+ const pageIndex = findPageIndexWithFallback(pages, pageRef);
1259
+ if (pageIndex >= 0) {
1260
+ const page = pages[pageIndex];
1261
+ if (!page) continue;
1262
+ const groupLabelKey = `${pageIndex}-${field.name}-grouplabel`;
1263
+ if (!drawnOnce.has(groupLabelKey)) {
1264
+ const labelY = firstRect.y + firstRect.height + 5;
1265
+ page.drawText(field.label, {
1266
+ x: firstRect.x,
1267
+ y: labelY,
1268
+ size: 10,
1269
+ font: labelFont,
1270
+ color: rgb(0, 0, 0)
1271
+ });
1272
+ drawnOnce.add(groupLabelKey);
1273
+ }
1274
+ }
1275
+ }
1276
+ }
1277
+ if (field.options && field.options.length > 0) {
1278
+ for (let i = 0; i < widgets.length; i++) {
1279
+ const widget = widgets[i];
1280
+ if (!widget) continue;
1281
+ const rect = widget.getRectangle?.();
1282
+ if (!rect) continue;
1283
+ const pageRef = widget.P?.();
1284
+ const pageIndex = findPageIndexWithFallback(pages, pageRef);
1285
+ if (pageIndex >= 0) {
1286
+ const page = pages[pageIndex];
1287
+ if (!page) continue;
1288
+ const optionTexts = field.options;
1289
+ const optionText = optionTexts[i] || optionTexts[0];
1290
+ const optionKey = `${pageIndex}-${field.name}-opt-${i}`;
1291
+ if (optionText && !drawnOnce.has(optionKey)) {
1292
+ page.drawText(optionText, {
1293
+ x: rect.x + rect.width + 6,
1294
+ y: rect.y + (rect.height - 10) / 2,
1295
+ size: 10,
1296
+ font: labelFont,
1297
+ color: rgb(0, 0, 0)
1298
+ });
1299
+ drawnOnce.add(optionKey);
1300
+ }
1301
+ }
1302
+ }
1303
+ }
1304
+ } else {
1305
+ const widget = widgets[0];
1306
+ if (!widget) continue;
1307
+ const rect = widget.getRectangle?.();
1308
+ if (!rect) continue;
1309
+ const pageRef = widget.P?.();
1310
+ const pageIndex = findPageIndexWithFallback(pages, pageRef);
1311
+ if (pageIndex >= 0 && pageIndex < pages.length) {
1312
+ const page = pages[pageIndex];
1313
+ if (!page) continue;
1314
+ const key = `${pageIndex}-${field.name}`;
1315
+ if (!drawnOnce.has(key)) {
1316
+ if (!isAutoGeneratedLabel(field.label)) {
1317
+ if (field.type === "checkbox" /* CHECKBOX */) {
1318
+ page.drawText(field.label, {
1319
+ x: rect.x + rect.width + 5,
1320
+ y: rect.y + rect.height * 0.2,
1321
+ size: 10,
1322
+ font: labelFont,
1323
+ color: rgb(0, 0, 0)
1324
+ });
1325
+ } else {
1326
+ const labelY = rect.y + rect.height + 5;
1327
+ page.drawText(field.label, {
1328
+ x: rect.x,
1329
+ y: labelY,
1330
+ size: 9,
1331
+ font: labelFont,
1332
+ color: rgb(0.3, 0.3, 0.3)
1333
+ });
1334
+ }
1335
+ drawnOnce.add(key);
1336
+ }
1337
+ }
1338
+ }
1339
+ }
1340
+ }
1341
+ try {
1342
+ form.updateFieldAppearances();
1343
+ } catch (appearanceError) {
1344
+ }
1345
+ } catch (error) {
1346
+ logger.error("Error drawing field labels:", error);
1347
+ }
1348
+ }
1349
+ async function filterPdfForCurrentSigner(pdfBytes, allFields, multiSignerContext) {
1350
+ if (!multiSignerContext.isMultiSigner) {
1351
+ try {
1352
+ const { PDFDocument: PDFDocument2, rgb: rgb2 } = await loadPdfLib();
1353
+ const pdfDoc = await PDFDocument2.load(pdfBytes);
1354
+ await drawFieldLabelsOnPdf(pdfDoc, allFields, rgb2);
1355
+ const labeledBytes = await pdfDoc.save();
1356
+ return labeledBytes;
1357
+ } catch (error) {
1358
+ return pdfBytes;
1359
+ }
1360
+ }
1361
+ const otherSignersFields = allFields.filter((f) => {
1362
+ if (f.type === "text_label" /* TEXT_LABEL */) {
1363
+ return false;
1364
+ }
1365
+ return !isFieldVisibleToSigner(f, multiSignerContext);
1366
+ });
1367
+ if (otherSignersFields.length === 0) {
1368
+ try {
1369
+ const { PDFDocument: PDFDocument2, rgb: rgb2 } = await loadPdfLib();
1370
+ const pdfDoc = await PDFDocument2.load(pdfBytes);
1371
+ const fieldsToLabel = allFields.filter((f) => {
1372
+ if (f.type === "text_label" /* TEXT_LABEL */) {
1373
+ return true;
1374
+ }
1375
+ return isFieldVisibleToSigner(f, multiSignerContext);
1376
+ });
1377
+ await drawFieldLabelsOnPdf(pdfDoc, fieldsToLabel, rgb2);
1378
+ const labeledBytes = await pdfDoc.save();
1379
+ return labeledBytes;
1380
+ } catch (error) {
1381
+ return pdfBytes;
1382
+ }
1383
+ }
1384
+ const { PDFDocument, rgb } = await loadPdfLib();
1385
+ const freshPdfDoc = await PDFDocument.load(pdfBytes);
1386
+ const form = freshPdfDoc.getForm();
1387
+ let removedCount = 0;
1388
+ let notFoundCount = 0;
1389
+ for (const field of otherSignersFields) {
1390
+ try {
1391
+ const pdfField = form.getFieldMaybe(field.name);
1392
+ if (pdfField) {
1393
+ form.removeField(pdfField);
1394
+ removedCount++;
1395
+ } else {
1396
+ notFoundCount++;
1397
+ }
1398
+ } catch (error) {
1399
+ const errorMsg = error instanceof Error ? error.message : "Unknown error";
1400
+ logger.error("Error removing field:", errorMsg);
1401
+ }
1402
+ }
1403
+ try {
1404
+ const fieldsToLabel = allFields.filter((f) => {
1405
+ if (f.type === "text_label" /* TEXT_LABEL */) {
1406
+ return true;
1407
+ }
1408
+ return isFieldVisibleToSigner(f, multiSignerContext);
1409
+ });
1410
+ await drawFieldLabelsOnPdf(freshPdfDoc, fieldsToLabel, rgb);
1411
+ } catch (labelError) {
1412
+ }
1413
+ const modifiedPdfBytes = await freshPdfDoc.save();
1414
+ return modifiedPdfBytes;
1415
+ }
1416
+
1417
+ // src/hooks/usePdfViewer.ts
1418
+ function usePdfViewer(multiSignerContext) {
1419
+ const viewerRef = useRef(null);
1420
+ const [isLoading, setIsLoading] = useState(false);
1421
+ const [error, setError] = useState(null);
1422
+ const [isLoaded, setIsLoaded] = useState(false);
1423
+ const [pdfUrl, setPdfUrl] = useState(null);
1424
+ const [originalPdfBytes, setOriginalPdfBytes] = useState(null);
1425
+ const [_viewerPdfBytes, setViewerPdfBytes] = useState(null);
1426
+ const [extractedFields, setExtractedFields] = useState([]);
1427
+ const loadPdf = useCallback(async (url) => {
1428
+ setIsLoading(true);
1429
+ setError(null);
1430
+ setIsLoaded(false);
1431
+ setPdfUrl(url);
1432
+ try {
1433
+ initializePdfJs();
1434
+ const urlValidation = validatePdfUrl(url);
1435
+ if (!urlValidation.valid) {
1436
+ throw new PdfValidationError(urlValidation.error || "Invalid PDF URL");
1437
+ }
1438
+ const bytes = await urlToPdfBytes(url);
1439
+ setOriginalPdfBytes(bytes);
1440
+ setViewerPdfBytes(bytes);
1441
+ await viewerRef.current?.loadPdf(url);
1442
+ } catch (err) {
1443
+ const errorMessage = err instanceof Error ? err.message : "Failed to load PDF";
1444
+ logger.error("Error loading PDF:", err);
1445
+ setError(errorMessage);
1446
+ setOriginalPdfBytes(null);
1447
+ setViewerPdfBytes(null);
1448
+ }
1449
+ }, []);
1450
+ const handleLoad = useCallback(() => {
1451
+ setIsLoading(false);
1452
+ setIsLoaded(true);
1453
+ setError(null);
1454
+ }, []);
1455
+ const handleError = useCallback((errorMessage) => {
1456
+ setIsLoading(false);
1457
+ setIsLoaded(false);
1458
+ setError(errorMessage);
1459
+ }, []);
1460
+ const getFormFieldValues = useCallback(async () => {
1461
+ if (!viewerRef.current) {
1462
+ return {};
1463
+ }
1464
+ return await viewerRef.current.getFormFieldValues();
1465
+ }, []);
1466
+ const setFormFieldValues = useCallback(async (values) => {
1467
+ if (!viewerRef.current) {
1468
+ return;
1469
+ }
1470
+ return await viewerRef.current.setFormFieldValues(values);
1471
+ }, []);
1472
+ const getAllFieldNames = useCallback(async () => {
1473
+ if (!viewerRef.current) {
1474
+ return [];
1475
+ }
1476
+ return await viewerRef.current.getAllFieldNames();
1477
+ }, []);
1478
+ const saveDocument = useCallback(async () => {
1479
+ if (!viewerRef.current) {
1480
+ return null;
1481
+ }
1482
+ return await viewerRef.current.saveDocument();
1483
+ }, []);
1484
+ const extractFormFields = useCallback(
1485
+ async (currentSignerEmail) => {
1486
+ if (!originalPdfBytes) {
1487
+ throw new Error("No PDF loaded");
1488
+ }
1489
+ try {
1490
+ const fields = await extractVisibleFormFields(
1491
+ originalPdfBytes,
1492
+ currentSignerEmail
1493
+ );
1494
+ setExtractedFields(fields);
1495
+ if (multiSignerContext?.isMultiSigner) {
1496
+ const filteredBytes = await filterPdfForCurrentSigner(
1497
+ originalPdfBytes,
1498
+ fields,
1499
+ multiSignerContext
1500
+ );
1501
+ setViewerPdfBytes(filteredBytes);
1502
+ const filteredBlobUrl = createPdfBlobUrl(filteredBytes);
1503
+ await viewerRef.current?.loadPdf(filteredBlobUrl);
1504
+ } else {
1505
+ const labeledBytes = await filterPdfForCurrentSigner(
1506
+ originalPdfBytes,
1507
+ fields,
1508
+ multiSignerContext || {
1509
+ isMultiSigner: false,
1510
+ currentSigner: null,
1511
+ currentSignerEmail: "",
1512
+ isPrimarySigner: true,
1513
+ isFinalSigner: true
1514
+ }
1515
+ );
1516
+ setViewerPdfBytes(labeledBytes);
1517
+ const labeledBlobUrl = createPdfBlobUrl(labeledBytes);
1518
+ await viewerRef.current?.loadPdf(labeledBlobUrl);
1519
+ }
1520
+ if (viewerRef.current && fields.length > 0) {
1521
+ await new Promise((resolve) => setTimeout(resolve, 100));
1522
+ viewerRef.current.injectPlaceholders(fields);
1523
+ }
1524
+ return fields;
1525
+ } catch (err) {
1526
+ const errorMessage = err instanceof Error ? err.message : "Failed to extract form fields";
1527
+ throw new Error(errorMessage);
1528
+ }
1529
+ },
1530
+ [originalPdfBytes, multiSignerContext]
1531
+ );
1532
+ const fillPdf = useCallback(
1533
+ async (fieldValues, signatures, currentSignerEmail, metadata, auditTrail) => {
1534
+ if (!originalPdfBytes) {
1535
+ throw new PdfProcessingError("No PDF loaded");
1536
+ }
1537
+ try {
1538
+ const valuesValidation = validateFieldValues(fieldValues);
1539
+ if (!valuesValidation.valid) {
1540
+ logger.warn("Field values validation warnings:", valuesValidation.errors);
1541
+ }
1542
+ const sigsValidation = validateSignatures(signatures);
1543
+ if (!sigsValidation.valid) {
1544
+ logger.warn("Signatures validation warnings:", sigsValidation.errors);
1545
+ }
1546
+ const filledPdfBytes = await fillPdfWithSignatures(
1547
+ originalPdfBytes,
1548
+ // CRITICAL: Use original PDF with ALL fields
1549
+ signatures,
1550
+ fieldValues,
1551
+ currentSignerEmail,
1552
+ extractedFields,
1553
+ // Pass extracted fields for proper signature filtering
1554
+ metadata,
1555
+ auditTrail,
1556
+ multiSignerContext
1557
+ // Pass multi-signer context for partial flattening
1558
+ );
1559
+ return filledPdfBytes;
1560
+ } catch (err) {
1561
+ const errorMessage = err instanceof Error ? err.message : "Failed to fill PDF";
1562
+ logger.error("Error filling PDF:", err);
1563
+ throw new PdfProcessingError(errorMessage, err);
1564
+ }
1565
+ },
1566
+ [originalPdfBytes, extractedFields, multiSignerContext]
1567
+ );
1568
+ const validatePdf = useCallback(
1569
+ async (fieldValues, signatures, visibleFields) => {
1570
+ if (!originalPdfBytes) {
1571
+ throw new Error("No PDF loaded");
1572
+ }
1573
+ try {
1574
+ const fieldsToValidate = visibleFields || extractedFields;
1575
+ const errors = await validatePdfFormFields(
1576
+ originalPdfBytes,
1577
+ fieldValues,
1578
+ signatures,
1579
+ fieldsToValidate,
1580
+ // Pass visible/filtered fields for validation
1581
+ multiSignerContext
1582
+ // Pass multi-signer context for validation filtering
1583
+ );
1584
+ return {
1585
+ isValid: errors.length === 0,
1586
+ errors
1587
+ };
1588
+ } catch (err) {
1589
+ const errorMessage = err instanceof Error ? err.message : "Failed to validate PDF";
1590
+ throw new Error(errorMessage);
1591
+ }
1592
+ },
1593
+ [originalPdfBytes, extractedFields, multiSignerContext]
1594
+ );
1595
+ const getRequiredFieldsMap = useCallback(async () => {
1596
+ if (!originalPdfBytes) {
1597
+ return {};
1598
+ }
1599
+ try {
1600
+ const { PDFDocument } = await loadPdfLib();
1601
+ const pdfDoc = await PDFDocument.load(originalPdfBytes);
1602
+ const form = pdfDoc.getForm();
1603
+ const fields = form.getFields();
1604
+ const requiredMap = {};
1605
+ for (const field of fields) {
1606
+ const fieldName = field.getName();
1607
+ const isRequired = field.isRequired();
1608
+ requiredMap[fieldName] = isRequired;
1609
+ }
1610
+ return requiredMap;
1611
+ } catch (err) {
1612
+ logger.error("Failed to get required fields map:", err);
1613
+ return {};
1614
+ }
1615
+ }, [originalPdfBytes]);
1616
+ const getCurrentPdfBytes = useCallback(() => {
1617
+ return originalPdfBytes;
1618
+ }, [originalPdfBytes]);
1619
+ const reloadPdfWithBytes = useCallback(
1620
+ async (newPdfBytes) => {
1621
+ try {
1622
+ const blobUrl = createPdfBlobUrl(newPdfBytes);
1623
+ setOriginalPdfBytes(newPdfBytes);
1624
+ setViewerPdfBytes(newPdfBytes);
1625
+ setPdfUrl(blobUrl);
1626
+ await viewerRef.current?.loadPdf(blobUrl);
1627
+ } catch (err) {
1628
+ const errorMessage = err instanceof Error ? err.message : "Failed to reload PDF";
1629
+ setError(errorMessage);
1630
+ }
1631
+ },
1632
+ []
1633
+ );
1634
+ const reset = useCallback(() => {
1635
+ setIsLoading(false);
1636
+ setError(null);
1637
+ setIsLoaded(false);
1638
+ setPdfUrl(null);
1639
+ setOriginalPdfBytes(null);
1640
+ setViewerPdfBytes(null);
1641
+ setExtractedFields([]);
1642
+ }, []);
1643
+ return {
1644
+ // Ref and state
1645
+ viewerRef,
1646
+ isLoading,
1647
+ error,
1648
+ isLoaded,
1649
+ pdfUrl,
1650
+ pdfBytes: originalPdfBytes,
1651
+ // Expose as pdfBytes for backward compatibility
1652
+ extractedFields,
1653
+ // Basic operations
1654
+ loadPdf,
1655
+ handleLoad,
1656
+ handleError,
1657
+ getFormFieldValues,
1658
+ setFormFieldValues,
1659
+ getAllFieldNames,
1660
+ saveDocument,
1661
+ // Enhanced operations
1662
+ extractFormFields,
1663
+ fillPdf,
1664
+ validatePdf,
1665
+ getRequiredFieldsMap,
1666
+ getCurrentPdfBytes,
1667
+ reloadPdfWithBytes,
1668
+ reset
1669
+ };
1670
+ }
1671
+ function useFormFields(fields = []) {
1672
+ const [fieldValues, setFieldValues] = useState({});
1673
+ const [errors, setErrors] = useState([]);
1674
+ const [touched, setTouched] = useState({});
1675
+ const updateField = useCallback((fieldId, value) => {
1676
+ setFieldValues((prev) => ({
1677
+ ...prev,
1678
+ [fieldId]: value
1679
+ }));
1680
+ setTouched((prev) => ({
1681
+ ...prev,
1682
+ [fieldId]: true
1683
+ }));
1684
+ setErrors((prev) => prev.filter((err) => err.field !== fieldId));
1685
+ }, []);
1686
+ const updateMultipleFields = useCallback((values) => {
1687
+ setFieldValues((prev) => ({
1688
+ ...prev,
1689
+ ...values
1690
+ }));
1691
+ const touchedFields = Object.keys(values).reduce((acc, key) => {
1692
+ acc[key] = true;
1693
+ return acc;
1694
+ }, {});
1695
+ setTouched((prev) => ({
1696
+ ...prev,
1697
+ ...touchedFields
1698
+ }));
1699
+ const updatedFieldIds = Object.keys(values);
1700
+ setErrors((prev) => prev.filter((err) => !updatedFieldIds.includes(err.field)));
1701
+ }, []);
1702
+ const validateField = useCallback(
1703
+ (fieldId) => {
1704
+ const field = fields.find((f) => f.id === fieldId);
1705
+ if (!field) return true;
1706
+ const value = fieldValues[fieldId];
1707
+ const newErrors = errors.filter((e) => e.field !== fieldId);
1708
+ if (field.required) {
1709
+ if (!value || value.trim() === "") {
1710
+ newErrors.push({
1711
+ field: fieldId,
1712
+ message: `${field.label || field.name} is required`
1713
+ });
1714
+ }
1715
+ }
1716
+ if (value && field.maxLength && value.length > field.maxLength) {
1717
+ newErrors.push({
1718
+ field: fieldId,
1719
+ message: `${field.label || field.name} must be at most ${field.maxLength} characters`
1720
+ });
1721
+ }
1722
+ if (value && field.type === "date") {
1723
+ if (!isValidISODate(value)) {
1724
+ newErrors.push({
1725
+ field: fieldId,
1726
+ message: "Please enter a valid date"
1727
+ });
1728
+ }
1729
+ }
1730
+ setErrors(newErrors);
1731
+ return newErrors.length === errors.length - newErrors.filter((e) => e.field === fieldId).length;
1732
+ },
1733
+ [fields, fieldValues, errors]
1734
+ );
1735
+ const validateFields = useCallback(
1736
+ (signatures) => {
1737
+ const newErrors = [];
1738
+ for (const field of fields) {
1739
+ if (field.required) {
1740
+ const value2 = fieldValues[field.id];
1741
+ if ((field.type === "signature" || field.type === "initials") && signatures) {
1742
+ if (!signatures[field.id]) {
1743
+ newErrors.push({
1744
+ field: field.id,
1745
+ message: `${field.label || field.name} is required`
1746
+ });
1747
+ }
1748
+ } else if (!value2 || value2.trim() === "") {
1749
+ newErrors.push({
1750
+ field: field.id,
1751
+ message: `${field.label || field.name} is required`
1752
+ });
1753
+ }
1754
+ }
1755
+ const value = fieldValues[field.id];
1756
+ if (value && field.maxLength && value.length > field.maxLength) {
1757
+ newErrors.push({
1758
+ field: field.id,
1759
+ message: `${field.label || field.name} must be at most ${field.maxLength} characters`
1760
+ });
1761
+ }
1762
+ if (value && field.type === "date") {
1763
+ if (!isValidISODate(value)) {
1764
+ newErrors.push({
1765
+ field: field.id,
1766
+ message: "Please enter a valid date"
1767
+ });
1768
+ }
1769
+ }
1770
+ }
1771
+ setErrors(newErrors);
1772
+ return newErrors.length === 0;
1773
+ },
1774
+ [fields, fieldValues]
1775
+ );
1776
+ const resetFields = useCallback(() => {
1777
+ setFieldValues({});
1778
+ setErrors([]);
1779
+ setTouched({});
1780
+ }, []);
1781
+ const resetValidation = useCallback(() => {
1782
+ setErrors([]);
1783
+ }, []);
1784
+ const getFieldValue = useCallback(
1785
+ (fieldId) => {
1786
+ return fieldValues[fieldId] || "";
1787
+ },
1788
+ [fieldValues]
1789
+ );
1790
+ const getFieldError = useCallback(
1791
+ (fieldId) => {
1792
+ const error = errors.find((e) => e.field === fieldId);
1793
+ return error?.message;
1794
+ },
1795
+ [errors]
1796
+ );
1797
+ const isFieldTouched = useCallback(
1798
+ (fieldId) => {
1799
+ return touched[fieldId] || false;
1800
+ },
1801
+ [touched]
1802
+ );
1803
+ const setFieldValue = updateField;
1804
+ const setMultipleFieldValues = updateMultipleFields;
1805
+ const clearFields = resetFields;
1806
+ const hasErrors = errors.length > 0;
1807
+ return {
1808
+ fieldValues,
1809
+ errors,
1810
+ hasErrors,
1811
+ touched,
1812
+ updateField,
1813
+ updateMultipleFields,
1814
+ validateField,
1815
+ validateFields,
1816
+ resetFields,
1817
+ resetValidation,
1818
+ getFieldValue,
1819
+ getFieldError,
1820
+ isFieldTouched,
1821
+ // Aliases
1822
+ setFieldValue,
1823
+ setMultipleFieldValues,
1824
+ clearFields
1825
+ };
1826
+ }
1827
+ function useSignatureCapture() {
1828
+ const canvasRef = useRef(null);
1829
+ const [signatureDataUrl, setSignatureDataUrl] = useState(null);
1830
+ const [uploadedImage, setUploadedImage] = useState(null);
1831
+ const [uploadError, setUploadError] = useState(null);
1832
+ const clear = useCallback(() => {
1833
+ canvasRef.current?.clear();
1834
+ setSignatureDataUrl(null);
1835
+ }, []);
1836
+ const saveSignature = useCallback(() => {
1837
+ const dataUrl = canvasRef.current?.getSignatureDataUrl();
1838
+ if (dataUrl) {
1839
+ setSignatureDataUrl(dataUrl);
1840
+ return dataUrl;
1841
+ }
1842
+ return null;
1843
+ }, []);
1844
+ const isEmpty = useCallback(() => {
1845
+ return canvasRef.current?.isEmpty() ?? true;
1846
+ }, []);
1847
+ const handleFileUpload = useCallback((file) => {
1848
+ setUploadError(null);
1849
+ const allowedTypes = ["image/png", "image/jpeg", "image/jpg"];
1850
+ if (!allowedTypes.includes(file.type.toLowerCase())) {
1851
+ setUploadError("Please upload a PNG or JPEG image file.");
1852
+ return;
1853
+ }
1854
+ const maxSizeInBytes = 5 * 1024 * 1024;
1855
+ if (file.size > maxSizeInBytes) {
1856
+ setUploadError("File size must be less than 5MB. Please choose a smaller image.");
1857
+ return;
1858
+ }
1859
+ const reader = new FileReader();
1860
+ reader.onload = (event) => {
1861
+ if (typeof event.target?.result === "string") {
1862
+ setUploadedImage(event.target.result);
1863
+ setSignatureDataUrl(event.target.result);
1864
+ setUploadError(null);
1865
+ }
1866
+ };
1867
+ reader.onerror = () => {
1868
+ setUploadError("Failed to read the image file. Please try again.");
1869
+ };
1870
+ reader.readAsDataURL(file);
1871
+ }, []);
1872
+ const clearUpload = useCallback(() => {
1873
+ setUploadedImage(null);
1874
+ setUploadError(null);
1875
+ setSignatureDataUrl(null);
1876
+ }, []);
1877
+ const reset = useCallback(() => {
1878
+ clear();
1879
+ clearUpload();
1880
+ }, [clear, clearUpload]);
1881
+ return {
1882
+ canvasRef,
1883
+ signatureDataUrl,
1884
+ uploadedImage,
1885
+ uploadError,
1886
+ clear,
1887
+ saveSignature,
1888
+ isEmpty,
1889
+ handleFileUpload,
1890
+ clearUpload,
1891
+ reset
1892
+ };
1893
+ }
1894
+ function useSignatures() {
1895
+ const [signatures, setSignatures] = useState({});
1896
+ const setSignature = useCallback((fieldId, dataUrl) => {
1897
+ setSignatures((prev) => ({
1898
+ ...prev,
1899
+ [fieldId]: dataUrl
1900
+ }));
1901
+ }, []);
1902
+ const clearSignature = useCallback((fieldId) => {
1903
+ setSignatures((prev) => {
1904
+ const newSignatures = { ...prev };
1905
+ delete newSignatures[fieldId];
1906
+ return newSignatures;
1907
+ });
1908
+ }, []);
1909
+ const clearAllSignatures = useCallback(() => {
1910
+ setSignatures({});
1911
+ }, []);
1912
+ const hasSignature = useCallback(
1913
+ (fieldId) => {
1914
+ return !!signatures[fieldId];
1915
+ },
1916
+ [signatures]
1917
+ );
1918
+ const getSignature = useCallback(
1919
+ (fieldId) => {
1920
+ return signatures[fieldId] || null;
1921
+ },
1922
+ [signatures]
1923
+ );
1924
+ const validateSignatures2 = useCallback(
1925
+ (fields, currentSignatures) => {
1926
+ const signaturesToCheck = currentSignatures ?? signatures;
1927
+ const errors = [];
1928
+ for (const field of fields) {
1929
+ if (field.required && (field.type === "signature" || field.type === "initials")) {
1930
+ if (!signaturesToCheck[field.id]) {
1931
+ errors.push(`${field.label || field.name} is required`);
1932
+ }
1933
+ }
1934
+ }
1935
+ return {
1936
+ isValid: errors.length === 0,
1937
+ errors
1938
+ };
1939
+ },
1940
+ [signatures]
1941
+ );
1942
+ const signatureCount = Object.keys(signatures).length;
1943
+ const hasAnySignatures = signatureCount > 0;
1944
+ return {
1945
+ signatures,
1946
+ setSignature,
1947
+ clearSignature,
1948
+ clearAllSignatures,
1949
+ hasSignature,
1950
+ getSignature,
1951
+ validateSignatures: validateSignatures2,
1952
+ signatureCount,
1953
+ hasAnySignatures
1954
+ };
1955
+ }
1956
+ function useFieldFiltering(allFields, multiSignerContext) {
1957
+ const filteredFields = useMemo(() => {
1958
+ let fields = allFields.filter((field) => {
1959
+ if (field.id === "signature_field_main" || field.id === "initials_field_main") {
1960
+ return true;
1961
+ }
1962
+ if (field.type === "signature" || field.type === "initials") {
1963
+ return false;
1964
+ }
1965
+ return true;
1966
+ });
1967
+ return filterFieldsBySigner(fields, multiSignerContext);
1968
+ }, [allFields, multiSignerContext]);
1969
+ const requiredFields = useMemo(() => {
1970
+ return filteredFields.filter((field) => field.required);
1971
+ }, [filteredFields]);
1972
+ const optionalFields = useMemo(() => {
1973
+ return filteredFields.filter((field) => !field.required);
1974
+ }, [filteredFields]);
1975
+ const isFieldVisible = (field) => {
1976
+ return isFieldVisibleToSigner(field, multiSignerContext);
1977
+ };
1978
+ return {
1979
+ // Filtered fields
1980
+ filteredFields,
1981
+ requiredFields,
1982
+ optionalFields,
1983
+ // Utilities
1984
+ isFieldVisible,
1985
+ // Counts
1986
+ totalFields: allFields.length,
1987
+ filteredCount: filteredFields.length,
1988
+ requiredCount: requiredFields.length,
1989
+ optionalCount: optionalFields.length
1990
+ };
1991
+ }
1992
+ function generateAttachmentId() {
1993
+ return `att_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
1994
+ }
1995
+ async function createPreview(file) {
1996
+ if (!isImageType(file.type)) {
1997
+ return void 0;
1998
+ }
1999
+ return new Promise((resolve, reject) => {
2000
+ const reader = new FileReader();
2001
+ reader.onload = () => resolve(reader.result);
2002
+ reader.onerror = reject;
2003
+ reader.readAsDataURL(file);
2004
+ });
2005
+ }
2006
+ function useAttachments(options = {}) {
2007
+ const [attachments, setAttachments] = useState([]);
2008
+ const [isUploading, setIsUploading] = useState(false);
2009
+ const [validationErrors, setValidationErrors] = useState([]);
2010
+ const constraints = {
2011
+ ...DEFAULT_ATTACHMENT_CONSTRAINTS,
2012
+ ...options.constraints
2013
+ };
2014
+ const addFiles = useCallback(
2015
+ async (files) => {
2016
+ setIsUploading(true);
2017
+ setValidationErrors([]);
2018
+ const fileArray = Array.from(files);
2019
+ const errors = [];
2020
+ const newAttachments = [];
2021
+ for (const file of fileArray) {
2022
+ const validation = validateFile(file, constraints);
2023
+ if (!validation.valid) {
2024
+ errors.push(...validation.errors);
2025
+ continue;
2026
+ }
2027
+ const currentTotalSize = [...attachments, ...newAttachments].reduce((sum, att) => sum + att.size, 0);
2028
+ if (currentTotalSize + file.size > constraints.maxTotalSize) {
2029
+ const maxTotalMB = (constraints.maxTotalSize / 1024 / 1024).toFixed(1);
2030
+ errors.push(`Adding "${file.name}" would exceed total size limit of ${maxTotalMB}MB`);
2031
+ continue;
2032
+ }
2033
+ if ([...attachments, ...newAttachments].length >= constraints.maxFiles) {
2034
+ errors.push(`Maximum of ${constraints.maxFiles} files allowed`);
2035
+ continue;
2036
+ }
2037
+ try {
2038
+ const preview = await createPreview(file);
2039
+ const attachment = {
2040
+ id: generateAttachmentId(),
2041
+ file,
2042
+ name: file.name,
2043
+ size: file.size,
2044
+ type: file.type,
2045
+ uploadedAt: /* @__PURE__ */ new Date(),
2046
+ preview
2047
+ };
2048
+ newAttachments.push(attachment);
2049
+ } catch (error) {
2050
+ logger.error("Error creating attachment:", error);
2051
+ errors.push(`Failed to process file "${file.name}"`);
2052
+ }
2053
+ }
2054
+ if (newAttachments.length > 0) {
2055
+ setAttachments((prev) => [...prev, ...newAttachments]);
2056
+ }
2057
+ if (errors.length > 0) {
2058
+ setValidationErrors(errors);
2059
+ options.onError?.(errors);
2060
+ }
2061
+ setIsUploading(false);
2062
+ return {
2063
+ added: newAttachments.length,
2064
+ errors
2065
+ };
2066
+ },
2067
+ [attachments, constraints, options]
2068
+ );
2069
+ const removeAttachment = useCallback((attachmentId) => {
2070
+ setAttachments((prev) => prev.filter((att) => att.id !== attachmentId));
2071
+ setValidationErrors([]);
2072
+ }, []);
2073
+ const clearAttachments = useCallback(() => {
2074
+ setAttachments([]);
2075
+ setValidationErrors([]);
2076
+ }, []);
2077
+ const getTotalSize = useCallback(() => {
2078
+ return attachments.reduce((sum, att) => sum + att.size, 0);
2079
+ }, [attachments]);
2080
+ const formatSize = useCallback((bytes) => {
2081
+ return formatFileSize(bytes);
2082
+ }, []);
2083
+ const validateAll = useCallback(() => {
2084
+ const errors = [];
2085
+ const totalSize = getTotalSize();
2086
+ if (totalSize > constraints.maxTotalSize) {
2087
+ const maxTotalMB = (constraints.maxTotalSize / 1024 / 1024).toFixed(1);
2088
+ errors.push(`Total file size (${formatSize(totalSize)}) exceeds ${maxTotalMB}MB limit`);
2089
+ }
2090
+ if (attachments.length > constraints.maxFiles) {
2091
+ errors.push(`Maximum of ${constraints.maxFiles} files allowed (currently ${attachments.length})`);
2092
+ }
2093
+ return {
2094
+ isValid: errors.length === 0,
2095
+ errors
2096
+ };
2097
+ }, [attachments, constraints, getTotalSize, formatSize]);
2098
+ return {
2099
+ // State
2100
+ attachments,
2101
+ isUploading,
2102
+ validationErrors,
2103
+ constraints,
2104
+ // Actions
2105
+ addFiles,
2106
+ removeAttachment,
2107
+ clearAttachments,
2108
+ // Utilities
2109
+ getTotalSize,
2110
+ formatSize,
2111
+ validateAll
2112
+ };
2113
+ }
2114
+ function useMultiSignerContext(currentSigner, isMultipleSignature, totalSigners) {
2115
+ return useMemo(() => {
2116
+ if (!isMultipleSignature || !currentSigner) {
2117
+ return {
2118
+ isMultiSigner: false,
2119
+ currentSigner: null,
2120
+ currentSignerEmail: "",
2121
+ isPrimarySigner: false,
2122
+ isFinalSigner: false
2123
+ };
2124
+ }
2125
+ const isPrimarySigner = currentSigner.signOrder === 1;
2126
+ const isFinalSigner = totalSigners ? currentSigner.signOrder === totalSigners : true;
2127
+ return {
2128
+ isMultiSigner: true,
2129
+ currentSigner,
2130
+ currentSignerEmail: currentSigner.email,
2131
+ isPrimarySigner,
2132
+ isFinalSigner
2133
+ };
2134
+ }, [currentSigner, isMultipleSignature, totalSigners]);
2135
+ }
2136
+
2137
+ export { useAttachments, useFieldFiltering, useFormFields, useMultiSignerContext, usePdfViewer, useSignatureCapture, useSignatures };
2138
+ //# sourceMappingURL=index.mjs.map
2139
+ //# sourceMappingURL=index.mjs.map