@josephomills/esign 0.7.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
- import { S as SubjectSelection, E as EsignChannel, R as ResendPolicy, P as PersistencePort, a as SubjectsPort, N as NotifierPort, C as Clock, b as SkippedSubject, c as StoragePort, H as HooksPort, d as SignatureField, e as SigningDocumentVersionDTO, f as SigningDocumentDTO, g as SigningRequestDTO, h as EsignConfig, V as VersionPolicy, i as SubjectSigningStatus, j as CoverageReport, k as CampaignStatsRow, D as DocumentStatsRow, l as StatsRange, m as ScopeStatsRow, O as OutstandingRequest, n as SubjectTypeDescriptor, o as Placement } from '../ports-CkBaAepo.js';
2
- export { A as ACTIVE_STATUSES, p as AffectedSubject, q as AuthPort, r as DeclineInput, s as DocumentDeletionContext, t as ESIGN_CHANNELS, u as ESIGN_STATUSES, v as EsignStatus, G as GroupBreakdown, w as NewAuditEventRow, x as NewRequestRow, y as NotifierAttachment, z as Recipient, B as RequestSummary, F as SigningAuditEventDTO, I as SigningCampaignDTO, J as StatusCountMap, K as SubjectFilter, L as SubjectFilterField, M as SubjectSigningState, Q as SubjectSummary, T as SubmitSignatureInput, U as TERMINAL_STATUSES, W as assertTransition, X as canTransition, Y as declineSchema, Z as esignChannelSchema, _ as isActive, $ as isTerminal, a0 as placementSchema, a1 as placementsSchema, a2 as signatureFieldSchema, a3 as subjectSelectionSchema, a4 as submitSignatureSchema } from '../ports-CkBaAepo.js';
1
+ import { S as SubjectSelection, E as EsignChannel, R as ResendPolicy, P as PersistencePort, a as SubjectsPort, N as NotifierPort, C as Clock, b as SkippedSubject, c as StoragePort, H as HooksPort, d as SignatureField, e as SigningDocumentVersionDTO, f as SigningDocumentDTO, g as SigningRequestDTO, h as EsignConfig, V as VersionPolicy, i as SubjectSigningStatus, j as CoverageReport, k as CampaignStatsRow, D as DocumentStatsRow, l as StatsRange, m as ScopeStatsRow, O as OutstandingRequest, n as SubjectTypeDescriptor, o as DocumentField } from '../ports--YsOnh8H.js';
2
+ export { A as ACTIVE_STATUSES, p as AffectedSubject, q as AuthPort, r as DATE_FORMATS, s as DATE_FORMAT_LABELS, t as DEFAULT_DATE_FORMAT, u as DateFormat, v as DeclineInput, w as DocumentDeletionContext, x as ESIGN_CHANNELS, y as ESIGN_STATUSES, z as EsignStatus, F as FieldType, G as GroupBreakdown, B as NewAuditEventRow, I as NewRequestRow, J as NotifierAttachment, K as Placement, L as Recipient, M as RequestSummary, Q as SigningAuditEventDTO, T as SigningCampaignDTO, U as StatusCountMap, W as SubjectFilter, X as SubjectFilterField, Y as SubjectSigningState, Z as SubjectSummary, _ as SubmitSignatureInput, $ as TERMINAL_STATUSES, a0 as assertTransition, a1 as canTransition, a2 as declineSchema, a3 as documentFieldSchema, a4 as esignChannelSchema, a5 as formatPickedDate, a6 as isActive, a7 as isTerminal, a8 as placementSchema, a9 as placementsSchema, aa as signatureFieldSchema, ab as subjectSelectionSchema, ac as submitSignatureSchema } from '../ports--YsOnh8H.js';
3
3
  import { PDFDocument } from 'pdf-lib';
4
4
  import { ZodType } from 'zod';
5
5
 
@@ -176,6 +176,8 @@ interface SubmitInput {
176
176
  token: string;
177
177
  signaturePng: Uint8Array;
178
178
  signerName: string;
179
+ /** Picked date per date-field id (ISO `yyyy-mm-dd`). */
180
+ dateValues?: Record<string, string>;
179
181
  ip: string | null;
180
182
  userAgent: string | null;
181
183
  }
@@ -344,8 +346,10 @@ interface SealInput {
344
346
  sourcePdf: Uint8Array;
345
347
  /** PNG bytes of the drawn or typed signature. */
346
348
  signaturePng: Uint8Array;
347
- /** One or more boxes; the signature is stamped at each. */
348
- placements: Placement[];
349
+ /** The version's fields. Signature fields get the signature; date fields the picked date. */
350
+ fields: DocumentField[];
351
+ /** Picked date per date-field id (ISO `yyyy-mm-dd`). */
352
+ dateValues: Record<string, string>;
349
353
  signerName: string;
350
354
  /** ISO instant — also pins the PDF metadata dates for reproducibility. */
351
355
  signedAt: string;
@@ -390,4 +394,4 @@ declare class EsignError extends Error {
390
394
  }
391
395
  declare function toErrorResponse(err: unknown): Response;
392
396
 
393
- export { type AddVersionInput, type AuditEventInput, type AuditLink, type CampaignContentBuilder, CampaignStatsRow, type CertInfo, type ChannelContent, Clock, type CoverageDeps, CoverageReport, type CreateCampaignArgs, type CreateCampaignDeps, type CreateCampaignResult, type CreateDocumentInput, type DeclineRequestInput, type DeleteDocumentDeps, type DeleteDocumentInput, type DeleteDocumentResult, type DeleteMode, DocumentStatsRow, EsignChannel, EsignConfig, EsignError, type EsignErrorCode, type EsignRuntime, HooksPort, NotifierPort, OutstandingRequest, PersistencePort, Placement, ResendPolicy, type ResolvedEsignConfig, ScopeStatsRow, type SealInput, type SealResult, type SignDeps, type SignServeDeps, SignatureField, SigningDocumentDTO, SigningDocumentVersionDTO, SigningRequestDTO, type SigningToken, type SigningView, SkippedSubject, StatsRange, StoragePort, SubjectSelection, SubjectSigningStatus, type SubjectSigningStatusArgs, SubjectTypeDescriptor, SubjectsPort, type SubmitInput, type SubmitResult, VersionPolicy, type VersionSource, addVersion, appendEvent, buildChain, canonicalize, clientIp, configureEsign, createCampaign, createDocument, createSignActionsRoute, createSignServeRoute, declineRequest, deleteDocument, documentCoverage, getSigningView, linkHash, newSigningToken, ok, parseBody, recordEvent, recordView, registerSubjectTypes, renderAuditCertificatePage, sealPdf, sha256Hex, submitSignature, toErrorResponse, uid, verifyChain };
397
+ export { type AddVersionInput, type AuditEventInput, type AuditLink, type CampaignContentBuilder, CampaignStatsRow, type CertInfo, type ChannelContent, Clock, type CoverageDeps, CoverageReport, type CreateCampaignArgs, type CreateCampaignDeps, type CreateCampaignResult, type CreateDocumentInput, type DeclineRequestInput, type DeleteDocumentDeps, type DeleteDocumentInput, type DeleteDocumentResult, type DeleteMode, DocumentField, DocumentStatsRow, EsignChannel, EsignConfig, EsignError, type EsignErrorCode, type EsignRuntime, HooksPort, NotifierPort, OutstandingRequest, PersistencePort, ResendPolicy, type ResolvedEsignConfig, ScopeStatsRow, type SealInput, type SealResult, type SignDeps, type SignServeDeps, SignatureField, SigningDocumentDTO, SigningDocumentVersionDTO, SigningRequestDTO, type SigningToken, type SigningView, SkippedSubject, StatsRange, StoragePort, SubjectSelection, SubjectSigningStatus, type SubjectSigningStatusArgs, SubjectTypeDescriptor, SubjectsPort, type SubmitInput, type SubmitResult, VersionPolicy, type VersionSource, addVersion, appendEvent, buildChain, canonicalize, clientIp, configureEsign, createCampaign, createDocument, createSignActionsRoute, createSignServeRoute, declineRequest, deleteDocument, documentCoverage, getSigningView, linkHash, newSigningToken, ok, parseBody, recordEvent, recordView, registerSubjectTypes, renderAuditCertificatePage, sealPdf, sha256Hex, submitSignature, toErrorResponse, uid, verifyChain };
@@ -111,6 +111,58 @@ function toErrorResponse(err) {
111
111
  { status: 500 }
112
112
  );
113
113
  }
114
+
115
+ // src/shared/fields.ts
116
+ var DATE_FORMATS = [
117
+ "DD/MM/YYYY",
118
+ "MM/DD/YYYY",
119
+ "YYYY-MM-DD",
120
+ "D MMM YYYY",
121
+ "Do MMM YYYY",
122
+ "Do MMMM YYYY",
123
+ "MMMM D, YYYY"
124
+ ];
125
+ var DEFAULT_DATE_FORMAT = "Do MMMM YYYY";
126
+ var MONTHS_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
127
+ var MONTHS_LONG = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
128
+ function ordinal(n) {
129
+ const s = ["th", "st", "nd", "rd"];
130
+ const v = n % 100;
131
+ return `${n}${s[(v - 20) % 10] ?? s[v] ?? s[0]}`;
132
+ }
133
+ function formatPickedDate(iso, format) {
134
+ const [y, m, d] = iso.split("-").map(Number);
135
+ if (!y || !m || !d) return iso;
136
+ const dd = String(d).padStart(2, "0");
137
+ const mm = String(m).padStart(2, "0");
138
+ switch (format) {
139
+ case "DD/MM/YYYY":
140
+ return `${dd}/${mm}/${y}`;
141
+ case "MM/DD/YYYY":
142
+ return `${mm}/${dd}/${y}`;
143
+ case "YYYY-MM-DD":
144
+ return `${y}-${mm}-${dd}`;
145
+ case "D MMM YYYY":
146
+ return `${d} ${MONTHS_SHORT[m - 1]} ${y}`;
147
+ case "Do MMM YYYY":
148
+ return `${ordinal(d)} ${MONTHS_SHORT[m - 1]} ${y}`;
149
+ case "Do MMMM YYYY":
150
+ return `${ordinal(d)} ${MONTHS_LONG[m - 1]} ${y}`;
151
+ case "MMMM D, YYYY":
152
+ return `${MONTHS_LONG[m - 1]} ${d}, ${y}`;
153
+ }
154
+ }
155
+ var DATE_FORMAT_LABELS = {
156
+ "DD/MM/YYYY": "02/10/2026",
157
+ "MM/DD/YYYY": "10/02/2026",
158
+ "YYYY-MM-DD": "2026-10-02",
159
+ "D MMM YYYY": "2 Oct 2026",
160
+ "Do MMM YYYY": "2nd Oct 2026",
161
+ "Do MMMM YYYY": "2nd October 2026",
162
+ "MMMM D, YYYY": "October 2, 2026"
163
+ };
164
+
165
+ // src/server/types.ts
114
166
  var ESIGN_CHANNELS = [
115
167
  "EMAIL",
116
168
  "TELEGRAM",
@@ -126,11 +178,18 @@ var placementSchema = z.object({
126
178
  w: z.number().min(0.01).max(1),
127
179
  h: z.number().min(0.01).max(1)
128
180
  }).strict();
129
- var signatureFieldSchema = placementSchema.extend({
181
+ var isoDate = z.string().regex(/^\d{4}-\d{2}-\d{2}$/);
182
+ var documentFieldSchema = placementSchema.extend({
130
183
  id: z.string().min(1).max(64),
131
- label: z.string().trim().max(80)
184
+ label: z.string().trim().max(80),
185
+ type: z.enum(["signature", "date"]).default("signature"),
186
+ dateFormat: z.enum(DATE_FORMATS).optional(),
187
+ minDate: isoDate.nullable().optional()
188
+ }).strict();
189
+ var signatureFieldSchema = documentFieldSchema;
190
+ var placementsSchema = z.array(documentFieldSchema).min(1).max(20).refine((fields) => fields.some((f) => f.type === "signature"), {
191
+ message: "A document needs at least one signature field."
132
192
  });
133
- var placementsSchema = z.array(signatureFieldSchema).min(1).max(20);
134
193
  function addressFor(recipient, channel) {
135
194
  switch (channel) {
136
195
  case "EMAIL":
@@ -160,7 +219,8 @@ var submitSignatureSchema = z.object({
160
219
  signaturePng: z.string().min(1).max(15e5),
161
220
  // ~1MB cap on the data URL
162
221
  signerName: z.string().trim().min(1).max(200),
163
- consent: z.literal(true)
222
+ consent: z.literal(true),
223
+ dateValues: z.record(z.string().min(1).max(64), isoDate).optional()
164
224
  }).strict();
165
225
  var declineSchema = z.object({ reason: z.string().trim().max(500).optional() }).strict();
166
226
  var ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
@@ -650,15 +710,35 @@ async function sealPdf(input) {
650
710
  const png = await pdf.embedPng(input.signaturePng);
651
711
  const font = await pdf.embedFont(StandardFonts.Helvetica);
652
712
  const caption = `Signed by ${input.signerName} \xB7 ${input.signedAt}${input.signerIp ? ` \xB7 IP ${input.signerIp}` : ""}`;
653
- input.placements.forEach((placement, i) => {
654
- const pageIndex = Math.min(Math.max(placement.page - 1, 0), pages.length - 1);
713
+ let captionDrawn = false;
714
+ input.fields.forEach((field) => {
715
+ const pageIndex = Math.min(Math.max(field.page - 1, 0), pages.length - 1);
655
716
  const page = pages[pageIndex];
656
717
  const pageW = page.getWidth();
657
718
  const pageH = page.getHeight();
658
- const boxX = placement.x * pageW;
659
- const boxW = placement.w * pageW;
660
- const boxH = placement.h * pageH;
661
- const boxBottomY = pageH * (1 - placement.y - placement.h);
719
+ const boxX = field.x * pageW;
720
+ const boxW = field.w * pageW;
721
+ const boxH = field.h * pageH;
722
+ const boxBottomY = pageH * (1 - field.y - field.h);
723
+ if (field.type === "date") {
724
+ const iso = input.dateValues[field.id];
725
+ if (!iso) return;
726
+ const text = formatPickedDate(iso, field.dateFormat ?? DEFAULT_DATE_FORMAT);
727
+ let size = Math.min(boxH * 0.62, 15);
728
+ let textW = font.widthOfTextAtSize(text, size);
729
+ if (textW > boxW && textW > 0) {
730
+ size *= boxW / textW;
731
+ textW = font.widthOfTextAtSize(text, size);
732
+ }
733
+ page.drawText(text, {
734
+ x: boxX + Math.max(0, (boxW - textW) / 2),
735
+ y: boxBottomY + (boxH - size) / 2 + size * 0.22,
736
+ size,
737
+ font,
738
+ color: rgb(0.1, 0.1, 0.12)
739
+ });
740
+ return;
741
+ }
662
742
  const scale = Math.min(boxW / png.width, boxH / png.height);
663
743
  const drawW = png.width * scale;
664
744
  const drawH = png.height * scale;
@@ -668,7 +748,8 @@ async function sealPdf(input) {
668
748
  width: drawW,
669
749
  height: drawH
670
750
  });
671
- if (i === 0) {
751
+ if (!captionDrawn) {
752
+ captionDrawn = true;
672
753
  page.drawText(caption, {
673
754
  x: boxX,
674
755
  y: Math.max(boxBottomY - 11, 4),
@@ -784,6 +865,18 @@ async function submitSignature(deps, input) {
784
865
  const src = await storage.get(version.sourceObjectKey);
785
866
  if (!src) throw new EsignError("INTERNAL", "Source document missing.");
786
867
  const document = await persistence.getDocument(request.documentId);
868
+ const dateValues = input.dateValues ?? {};
869
+ const createdFloor = document?.createdAt ? document.createdAt.slice(0, 10) : null;
870
+ for (const field of version.placements) {
871
+ if (field.type !== "date") continue;
872
+ const value = dateValues[field.id];
873
+ const name = field.label || "date";
874
+ if (!value) throw new EsignError("INVALID_INPUT", `Please fill in the "${name}" date.`);
875
+ const floor = field.minDate ?? createdFloor;
876
+ if (floor && value < floor) {
877
+ throw new EsignError("INVALID_INPUT", `The "${name}" date can't be before ${floor}.`);
878
+ }
879
+ }
787
880
  const existing = (await persistence.listAuditEvents(request.id)).map(toLink);
788
881
  const nowIso = now.toISOString();
789
882
  const consented = await recordEvent(persistence, request.id, existing.at(-1) ?? null, {
@@ -798,7 +891,8 @@ async function submitSignature(deps, input) {
798
891
  const seal = await sealPdf({
799
892
  sourcePdf: src.bytes,
800
893
  signaturePng: input.signaturePng,
801
- placements: version.placements,
894
+ fields: version.placements,
895
+ dateValues,
802
896
  signerName: input.signerName,
803
897
  signedAt: nowIso,
804
898
  signerIp: input.ip,
@@ -919,6 +1013,7 @@ function createSignActionsRoute(deps) {
919
1013
  token,
920
1014
  signaturePng: pngFromDataUrl(body.signaturePng),
921
1015
  signerName: body.signerName,
1016
+ dateValues: body.dateValues,
922
1017
  ip: clientIp(req),
923
1018
  userAgent: req.headers.get("user-agent")
924
1019
  });
@@ -1011,6 +1106,6 @@ function registerSubjectTypes(types) {
1011
1106
  };
1012
1107
  }
1013
1108
 
1014
- export { ACTIVE_STATUSES, ESIGN_CHANNELS, ESIGN_STATUSES, EsignError, TERMINAL_STATUSES, addVersion, appendEvent, assertTransition, buildChain, canTransition, canonicalize, clientIp, configureEsign, createCampaign, createDocument, createSignActionsRoute, createSignServeRoute, declineRequest, declineSchema, deleteDocument, documentCoverage, esignChannelSchema, getSigningView, isActive, isTerminal, linkHash, newSigningToken, ok, parseBody, placementSchema, placementsSchema, recordEvent, recordView, registerSubjectTypes, renderAuditCertificatePage, sealPdf, sha256Hex, signatureFieldSchema, subjectSelectionSchema, submitSignature, submitSignatureSchema, toErrorResponse, uid, verifyChain };
1109
+ export { ACTIVE_STATUSES, DATE_FORMATS, DATE_FORMAT_LABELS, DEFAULT_DATE_FORMAT, ESIGN_CHANNELS, ESIGN_STATUSES, EsignError, TERMINAL_STATUSES, addVersion, appendEvent, assertTransition, buildChain, canTransition, canonicalize, clientIp, configureEsign, createCampaign, createDocument, createSignActionsRoute, createSignServeRoute, declineRequest, declineSchema, deleteDocument, documentCoverage, documentFieldSchema, esignChannelSchema, formatPickedDate, getSigningView, isActive, isTerminal, linkHash, newSigningToken, ok, parseBody, placementSchema, placementsSchema, recordEvent, recordView, registerSubjectTypes, renderAuditCertificatePage, sealPdf, sha256Hex, signatureFieldSchema, subjectSelectionSchema, submitSignature, submitSignatureSchema, toErrorResponse, uid, verifyChain };
1015
1110
  //# sourceMappingURL=index.js.map
1016
1111
  //# sourceMappingURL=index.js.map