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