@happyvertical/signatures 0.80.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.
@@ -0,0 +1,1443 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { createHmac, timingSafeEqual, createHash } from "node:crypto";
3
+ import { c as SignatureProviderError, b as SignatureInputError, S as SignatureConfigurationError, e as SignatureVerificationError, d as SignatureTenantMismatchError } from "../chunks/errors-Bnx7QrSA.js";
4
+ function getSignatureFetch(fetchLike) {
5
+ const resolved = fetchLike ?? globalThis.fetch;
6
+ if (typeof resolved !== "function") {
7
+ throw new SignatureProviderError(
8
+ "A fetch implementation is required in this runtime."
9
+ );
10
+ }
11
+ return resolved.bind(globalThis);
12
+ }
13
+ function requireNonEmptyString(value, context) {
14
+ if (typeof value !== "string" || !value.trim()) {
15
+ throw new SignatureInputError(`${context} must be a non-empty string.`);
16
+ }
17
+ return value.trim();
18
+ }
19
+ function requireRecord(value, context) {
20
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
21
+ throw new SignatureProviderError(`${context} must be an object.`);
22
+ }
23
+ return value;
24
+ }
25
+ function readString(value, key) {
26
+ const item = value?.[key];
27
+ return typeof item === "string" ? item : void 0;
28
+ }
29
+ function readNumber(value, key) {
30
+ const item = value?.[key];
31
+ return typeof item === "number" && Number.isFinite(item) ? item : void 0;
32
+ }
33
+ function readBoolean(value, key) {
34
+ const item = value?.[key];
35
+ return typeof item === "boolean" ? item : void 0;
36
+ }
37
+ function readRecord(value, key) {
38
+ const item = value?.[key];
39
+ return item && typeof item === "object" && !Array.isArray(item) ? item : void 0;
40
+ }
41
+ function readRecords(value, key) {
42
+ const item = value?.[key];
43
+ return Array.isArray(item) ? item.filter(
44
+ (candidate) => Boolean(candidate) && typeof candidate === "object" && !Array.isArray(candidate)
45
+ ) : [];
46
+ }
47
+ function normalizeDate(value, context) {
48
+ if (!(value instanceof Date) && typeof value !== "string") {
49
+ throw new SignatureInputError(`${context} must be a Date or ISO string.`);
50
+ }
51
+ const date = value instanceof Date ? new Date(value) : new Date(value);
52
+ if (Number.isNaN(date.getTime())) {
53
+ throw new SignatureInputError(`${context} must be a valid date.`);
54
+ }
55
+ return date;
56
+ }
57
+ function parseOptionalEpochSeconds(value) {
58
+ return typeof value === "number" && Number.isFinite(value) ? new Date(value * 1e3) : void 0;
59
+ }
60
+ const BOLDSIGN_PROVIDER_ID = "boldsign";
61
+ const BOLDSIGN_TENANT_METADATA_KEY = "hvTenantId";
62
+ const BOLDSIGN_IDEMPOTENCY_METADATA_KEY = "hvIdempotencyKey";
63
+ const DEFAULT_WEBHOOK_TOLERANCE_SECONDS = 300;
64
+ const MAX_BOLDSIGN_METADATA_ENTRIES = 50;
65
+ const MAX_BOLDSIGN_METADATA_KEY_LENGTH = 50;
66
+ const MAX_BOLDSIGN_METADATA_VALUE_LENGTH = 500;
67
+ const MAX_BOLDSIGN_DOCUMENT_BYTES = 25 * 1024 * 1024;
68
+ const MIN_EXPIRY_DAYS = 1;
69
+ const MAX_EXPIRY_DAYS = 180;
70
+ const SUPPORTED_BOLDSIGN_DOCUMENT_EVENTS = /* @__PURE__ */ new Set([
71
+ "sent",
72
+ "signed",
73
+ "completed",
74
+ "declined",
75
+ "revoked",
76
+ "expired",
77
+ "viewed",
78
+ "deliveryfailed",
79
+ "sendfailed"
80
+ ]);
81
+ const REGION_URLS = {
82
+ us: "https://api.boldsign.com/v1",
83
+ eu: "https://api-eu.boldsign.com/v1",
84
+ ca: "https://api-ca.boldsign.com/v1",
85
+ au: "https://api-au.boldsign.com/v1"
86
+ };
87
+ class BoldSignAdapter {
88
+ capabilities;
89
+ tenantId;
90
+ apiKey;
91
+ accessToken;
92
+ apiBaseUrl;
93
+ webhookSecrets;
94
+ webhookToleranceSeconds;
95
+ fetch;
96
+ now;
97
+ constructor(options) {
98
+ if (!options || typeof options !== "object" || Array.isArray(options)) {
99
+ throw new SignatureConfigurationError(
100
+ "BoldSignAdapter options must be an object."
101
+ );
102
+ }
103
+ this.tenantId = configurationString(
104
+ options.tenantId,
105
+ "BoldSignAdapter tenantId"
106
+ );
107
+ this.apiKey = optionalConfigurationString(
108
+ options.apiKey,
109
+ "BoldSignAdapter apiKey"
110
+ );
111
+ this.accessToken = optionalConfigurationString(
112
+ options.accessToken,
113
+ "BoldSignAdapter accessToken"
114
+ );
115
+ if (Boolean(this.apiKey) === Boolean(this.accessToken)) {
116
+ throw new SignatureConfigurationError(
117
+ "BoldSignAdapter requires exactly one of apiKey or accessToken."
118
+ );
119
+ }
120
+ const region = options.region ?? "ca";
121
+ if (!(region in REGION_URLS)) {
122
+ throw new SignatureConfigurationError(
123
+ `BoldSignAdapter region must be one of ${Object.keys(REGION_URLS).join(", ")}.`
124
+ );
125
+ }
126
+ this.apiBaseUrl = normalizeBaseUrl(
127
+ options.apiBaseUrl ?? REGION_URLS[region]
128
+ );
129
+ this.webhookSecrets = normalizeWebhookSecrets(options.webhookSecrets);
130
+ this.webhookToleranceSeconds = normalizeWebhookTolerance(
131
+ options.webhookToleranceSeconds
132
+ );
133
+ this.fetch = getSignatureFetch(options.fetch);
134
+ this.now = options.now ?? (() => /* @__PURE__ */ new Date());
135
+ if (typeof this.now !== "function") {
136
+ throw new SignatureConfigurationError(
137
+ "BoldSignAdapter now must be a function."
138
+ );
139
+ }
140
+ this.capabilities = {
141
+ id: BOLDSIGN_PROVIDER_ID,
142
+ displayName: "BoldSign",
143
+ region,
144
+ supportsWebhooks: true,
145
+ supportsCancellation: true,
146
+ supportsExpiryExtension: true,
147
+ supportsSignedDocument: true,
148
+ supportsAuditTrail: true,
149
+ providerEnforcedIdempotency: false,
150
+ authenticationMethods: [
151
+ "none",
152
+ "access_code",
153
+ "email_otp",
154
+ "sms_otp",
155
+ "identity_verification"
156
+ ]
157
+ };
158
+ }
159
+ async createRequest(input) {
160
+ this.assertTenant(input.tenantId);
161
+ const idempotencyKey = requireNonEmptyString(
162
+ input.idempotencyKey,
163
+ "BoldSign idempotencyKey"
164
+ );
165
+ const title = requireNonEmptyString(input.title, "BoldSign title");
166
+ const documents = await normalizeDocuments(input.documents, input.signal);
167
+ const signers = normalizeSignerInputs(input.signers);
168
+ const metadata = normalizeMetadata(input.metadata, {
169
+ [BOLDSIGN_TENANT_METADATA_KEY]: this.tenantId,
170
+ [BOLDSIGN_IDEMPOTENCY_METADATA_KEY]: idempotencyKey
171
+ });
172
+ const expiresInDays = normalizeExpiryDays(input.expiresInDays);
173
+ const response = await this.request("/document/send", {
174
+ method: "POST",
175
+ operation: "create",
176
+ signal: input.signal,
177
+ body: {
178
+ Title: title,
179
+ Message: optionalTrimmedString(input.message),
180
+ Files: documents.map((document) => ({
181
+ base64: `data:${document.mediaType};base64,${Buffer.from(document.data).toString("base64")}`,
182
+ fileName: document.name
183
+ })),
184
+ Signers: signers.map(toBoldSignSigner),
185
+ EnableSigningOrder: input.signingOrder ?? false,
186
+ ExpiryDateType: "Days",
187
+ ExpiryDays: expiresInDays,
188
+ ExpiryValue: expiresInDays,
189
+ MetaData: metadata
190
+ }
191
+ });
192
+ const id = requireProviderString(
193
+ readString(response, "documentId"),
194
+ "BoldSign send response documentId"
195
+ );
196
+ return {
197
+ provider: BOLDSIGN_PROVIDER_ID,
198
+ tenantId: this.tenantId,
199
+ id,
200
+ status: "prepared",
201
+ title,
202
+ signers: signers.map(inputSignerToResult),
203
+ expiresAt: new Date(
204
+ this.now().getTime() + expiresInDays * 24 * 60 * 60 * 1e3
205
+ ),
206
+ metadata,
207
+ raw: response
208
+ };
209
+ }
210
+ async getRequest(input) {
211
+ this.assertTenant(input.tenantId);
212
+ const requestId = requireNonEmptyString(
213
+ input.requestId,
214
+ "BoldSign requestId"
215
+ );
216
+ const response = await this.request(
217
+ `/document/properties?documentId=${encodeURIComponent(requestId)}`,
218
+ { signal: input.signal, operation: "read" }
219
+ );
220
+ this.assertProviderTenant(response);
221
+ return mapBoldSignRequest(response, this.tenantId);
222
+ }
223
+ async cancelRequest(input) {
224
+ const reason = requireNonEmptyString(
225
+ input.reason,
226
+ "BoldSign cancellation reason"
227
+ );
228
+ const current = await this.getRequest(input);
229
+ if (isTerminalStatus(current.status)) {
230
+ throw new SignatureInputError(
231
+ `BoldSign request ${current.id} cannot be cancelled from ${current.status}.`
232
+ );
233
+ }
234
+ await this.request(
235
+ `/document/revoke?documentId=${encodeURIComponent(current.id)}`,
236
+ {
237
+ method: "POST",
238
+ operation: "mutate",
239
+ expect: "empty",
240
+ signal: input.signal,
241
+ body: { Message: reason }
242
+ }
243
+ );
244
+ return { ...current, status: "cancelled" };
245
+ }
246
+ async extendExpiry(input) {
247
+ const current = await this.getRequest(input);
248
+ const expiresAt = normalizeDate(input.expiresAt, "BoldSign expiresAt");
249
+ if (isTerminalStatus(current.status)) {
250
+ throw new SignatureInputError(
251
+ `BoldSign request ${current.id} expiry cannot be extended from ${current.status}.`
252
+ );
253
+ }
254
+ if (expiresAt.getTime() <= this.now().getTime()) {
255
+ throw new SignatureInputError(
256
+ "BoldSign expiresAt must be in the future."
257
+ );
258
+ }
259
+ if (current.expiresAt && expiresAt.getTime() <= current.expiresAt.getTime()) {
260
+ throw new SignatureInputError(
261
+ "BoldSign expiresAt must extend the current expiry date."
262
+ );
263
+ }
264
+ if (current.createdAt && expiresAt.getTime() > current.createdAt.getTime() + MAX_EXPIRY_DAYS * 24 * 60 * 60 * 1e3) {
265
+ throw new SignatureInputError(
266
+ `BoldSign expiresAt cannot exceed ${MAX_EXPIRY_DAYS} days from document creation.`
267
+ );
268
+ }
269
+ await this.request(
270
+ `/document/extendExpiry?documentId=${encodeURIComponent(current.id)}`,
271
+ {
272
+ method: "PATCH",
273
+ operation: "mutate",
274
+ expect: "empty",
275
+ signal: input.signal,
276
+ body: {
277
+ // We create requests with BoldSign's `Days` expiry type, whose
278
+ // extendExpiry endpoint requires a yyyy-MM-dd value.
279
+ NewExpiryValue: expiresAt.toISOString().slice(0, 10),
280
+ WarnPrior: input.warnPrior
281
+ }
282
+ }
283
+ );
284
+ return { ...current, expiresAt };
285
+ }
286
+ async downloadArtifact(input) {
287
+ if (!["signed_document", "audit_trail"].includes(input.kind)) {
288
+ throw new SignatureInputError(
289
+ "BoldSign artifact kind must be signed_document or audit_trail."
290
+ );
291
+ }
292
+ const current = await this.getRequest(input);
293
+ if (current.status !== "completed") {
294
+ throw new SignatureInputError(
295
+ "BoldSign execution artifacts may only be downloaded after completion."
296
+ );
297
+ }
298
+ const endpoint = input.kind === "signed_document" ? "/document/download" : "/document/downloadAuditLog";
299
+ const response = await this.request(
300
+ `${endpoint}?documentId=${encodeURIComponent(current.id)}`,
301
+ {
302
+ signal: input.signal,
303
+ operation: "read",
304
+ expect: "stream"
305
+ }
306
+ );
307
+ const suffix = input.kind === "signed_document" ? "signed" : "audit";
308
+ const hashed = createSha256Stream(response);
309
+ return {
310
+ provider: BOLDSIGN_PROVIDER_ID,
311
+ tenantId: this.tenantId,
312
+ requestId: current.id,
313
+ kind: input.kind,
314
+ filename: `${safeFilename(current.id)}-${suffix}.pdf`,
315
+ mediaType: "application/pdf",
316
+ stream: hashed.stream,
317
+ sha256: hashed.sha256,
318
+ retrievedAt: new Date(this.now())
319
+ };
320
+ }
321
+ parseWebhook(input) {
322
+ if (this.webhookSecrets.length === 0) {
323
+ throw new SignatureConfigurationError(
324
+ "BoldSignAdapter parseWebhook requires webhookSecrets."
325
+ );
326
+ }
327
+ verifyBoldSignWebhookSignature({
328
+ ...input,
329
+ secrets: this.webhookSecrets,
330
+ toleranceSeconds: this.webhookToleranceSeconds,
331
+ now: this.now()
332
+ });
333
+ let parsed;
334
+ try {
335
+ parsed = JSON.parse(input.payload);
336
+ } catch (error) {
337
+ throw new SignatureVerificationError(
338
+ "BoldSign webhook payload is not valid JSON.",
339
+ { cause: error }
340
+ );
341
+ }
342
+ const body = verificationRecord(parsed, "BoldSign webhook payload");
343
+ const event = verificationRecord(
344
+ body.event,
345
+ "BoldSign webhook event metadata"
346
+ );
347
+ const data = verificationRecord(body.data, "BoldSign webhook data");
348
+ this.assertProviderTenant(data);
349
+ const id = requireVerificationString(
350
+ readString(event, "id"),
351
+ "BoldSign webhook event id"
352
+ );
353
+ const type = requireVerificationString(
354
+ readString(event, "eventType"),
355
+ "BoldSign webhook event type"
356
+ );
357
+ const normalizedType = type.toLowerCase();
358
+ if (!SUPPORTED_BOLDSIGN_DOCUMENT_EVENTS.has(normalizedType)) {
359
+ throw new SignatureVerificationError(
360
+ `Unsupported BoldSign webhook event type: ${type}`
361
+ );
362
+ }
363
+ const requestId = requireVerificationString(
364
+ readString(data, "documentId"),
365
+ "BoldSign webhook documentId"
366
+ );
367
+ const created = readNumber(event, "created");
368
+ if (created === void 0 || !Number.isSafeInteger(created) || created < 0 || Number.isNaN(new Date(created * 1e3).getTime())) {
369
+ throw new SignatureVerificationError(
370
+ "BoldSign webhook event created must be an epoch timestamp."
371
+ );
372
+ }
373
+ return {
374
+ id,
375
+ provider: BOLDSIGN_PROVIDER_ID,
376
+ tenantId: this.tenantId,
377
+ requestId,
378
+ type,
379
+ status: mapBoldSignWebhookStatus(type, readString(data, "status")),
380
+ createdAt: new Date(created * 1e3),
381
+ environment: optionalTrimmedString(readString(event, "environment")),
382
+ signers: mapBoldSignSigners(data),
383
+ replay: {
384
+ deduplicationKey: `${BOLDSIGN_PROVIDER_ID}:${this.tenantId}:${id}`,
385
+ orderingKey: `${BOLDSIGN_PROVIDER_ID}:${this.tenantId}:${requestId}`
386
+ },
387
+ raw: body
388
+ };
389
+ }
390
+ assertTenant(tenantId) {
391
+ const normalized = requireNonEmptyString(tenantId, "BoldSign tenantId");
392
+ if (normalized !== this.tenantId) {
393
+ throw new SignatureTenantMismatchError(
394
+ "Signature request tenant does not match the configured BoldSign tenant."
395
+ );
396
+ }
397
+ }
398
+ assertProviderTenant(value) {
399
+ const metadata = readBoldSignMetadata(value);
400
+ const providerTenantId = metadata[BOLDSIGN_TENANT_METADATA_KEY];
401
+ if (providerTenantId !== this.tenantId) {
402
+ throw new SignatureTenantMismatchError(
403
+ "BoldSign resource is missing the configured tenant binding or belongs to another tenant."
404
+ );
405
+ }
406
+ }
407
+ async request(path, options = {}) {
408
+ const headers = new Headers({ Accept: "application/json" });
409
+ if (this.apiKey) {
410
+ headers.set("X-API-KEY", this.apiKey);
411
+ } else if (this.accessToken) {
412
+ headers.set("Authorization", `Bearer ${this.accessToken}`);
413
+ }
414
+ if (options.body !== void 0) {
415
+ headers.set("Content-Type", "application/json");
416
+ }
417
+ let response;
418
+ try {
419
+ response = await this.fetch(`${this.apiBaseUrl}${path}`, {
420
+ method: options.method ?? "GET",
421
+ headers,
422
+ body: options.body === void 0 ? void 0 : JSON.stringify(options.body),
423
+ signal: options.signal
424
+ });
425
+ } catch (error) {
426
+ if (error instanceof SignatureProviderError) {
427
+ throw error;
428
+ }
429
+ throw new SignatureProviderError("BoldSign API request failed.", {
430
+ cause: error,
431
+ retryable: true,
432
+ requestMayHaveSucceeded: options.operation === "create"
433
+ });
434
+ }
435
+ if (!response.ok) {
436
+ throw await boldSignResponseError(
437
+ response,
438
+ options.operation === "create"
439
+ );
440
+ }
441
+ if (options.expect === "empty" || response.status === 204) {
442
+ return void 0;
443
+ }
444
+ if (options.expect === "stream") {
445
+ if (!response.body) {
446
+ throw new SignatureProviderError(
447
+ "BoldSign API returned an empty artifact stream."
448
+ );
449
+ }
450
+ return response.body;
451
+ }
452
+ const text = await response.text();
453
+ if (!text) {
454
+ throw new SignatureProviderError(
455
+ "BoldSign API returned an empty JSON response."
456
+ );
457
+ }
458
+ try {
459
+ return requireRecord(JSON.parse(text), "BoldSign API response");
460
+ } catch (error) {
461
+ if (error instanceof SignatureProviderError) {
462
+ throw error;
463
+ }
464
+ throw new SignatureProviderError("BoldSign API returned invalid JSON.", {
465
+ cause: error
466
+ });
467
+ }
468
+ }
469
+ }
470
+ function verifyBoldSignWebhookSignature(input) {
471
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
472
+ throw new SignatureVerificationError(
473
+ "BoldSign webhook verification input must be an object."
474
+ );
475
+ }
476
+ if (typeof input.payload !== "string") {
477
+ throw new SignatureVerificationError(
478
+ "BoldSign webhook payload must be a string."
479
+ );
480
+ }
481
+ const header = verificationString(
482
+ input.signature,
483
+ "BoldSign signature header"
484
+ );
485
+ const secrets = normalizeVerificationSecrets(input.secrets);
486
+ const toleranceSeconds = normalizeWebhookTolerance(input.toleranceSeconds);
487
+ const now = input.now ?? /* @__PURE__ */ new Date();
488
+ if (!(now instanceof Date) || Number.isNaN(now.getTime())) {
489
+ throw new SignatureVerificationError(
490
+ "BoldSign webhook verification now must be a valid Date."
491
+ );
492
+ }
493
+ const timestamps = [];
494
+ const signatures = [];
495
+ for (const part of header.split(",")) {
496
+ const [rawKey, ...rawValue] = part.split("=");
497
+ const key = rawKey?.trim();
498
+ const value = rawValue.join("=").trim();
499
+ if (!key || !value) {
500
+ continue;
501
+ }
502
+ if (key === "t") {
503
+ timestamps.push(value);
504
+ } else if (key === "s0" || key === "s1") {
505
+ signatures.push(value);
506
+ }
507
+ }
508
+ if (timestamps.length !== 1 || signatures.length === 0) {
509
+ throw new SignatureVerificationError("Invalid BoldSign signature header.");
510
+ }
511
+ const timestampText = timestamps[0] ?? "";
512
+ if (!/^\d+$/.test(timestampText)) {
513
+ throw new SignatureVerificationError("Invalid BoldSign webhook timestamp.");
514
+ }
515
+ const timestamp = Number(timestampText);
516
+ if (!Number.isSafeInteger(timestamp)) {
517
+ throw new SignatureVerificationError("Invalid BoldSign webhook timestamp.");
518
+ }
519
+ const ageSeconds = Math.abs(Math.floor(now.getTime() / 1e3) - timestamp);
520
+ if (ageSeconds > toleranceSeconds) {
521
+ throw new SignatureVerificationError(
522
+ "BoldSign webhook timestamp is outside the allowed tolerance."
523
+ );
524
+ }
525
+ const signedPayload = `${timestampText}.${input.payload}`;
526
+ const matched = secrets.some((secret) => {
527
+ const expected = Buffer.from(
528
+ createHmac("sha256", secret).update(signedPayload).digest("hex"),
529
+ "utf8"
530
+ );
531
+ return signatures.some((signature) => {
532
+ if (!/^[a-f\d]{64}$/i.test(signature)) {
533
+ return false;
534
+ }
535
+ const received = Buffer.from(signature.toLowerCase(), "utf8");
536
+ return received.length === expected.length && timingSafeEqual(received, expected);
537
+ });
538
+ });
539
+ if (!matched) {
540
+ throw new SignatureVerificationError(
541
+ "BoldSign webhook signature did not match."
542
+ );
543
+ }
544
+ }
545
+ function configurationString(value, context) {
546
+ if (typeof value !== "string" || !value.trim()) {
547
+ throw new SignatureConfigurationError(
548
+ `${context} must be a non-empty string.`
549
+ );
550
+ }
551
+ return value.trim();
552
+ }
553
+ function optionalConfigurationString(value, context) {
554
+ return value === void 0 ? void 0 : configurationString(value, context);
555
+ }
556
+ function normalizeBaseUrl(value) {
557
+ const raw = configurationString(value, "BoldSignAdapter apiBaseUrl");
558
+ let url;
559
+ try {
560
+ url = new URL(raw);
561
+ } catch (error) {
562
+ throw new SignatureConfigurationError(
563
+ "BoldSignAdapter apiBaseUrl must be a valid URL.",
564
+ { cause: error }
565
+ );
566
+ }
567
+ if (url.protocol !== "https:") {
568
+ throw new SignatureConfigurationError(
569
+ "BoldSignAdapter apiBaseUrl must use HTTPS."
570
+ );
571
+ }
572
+ if (url.username || url.password || url.search || url.hash) {
573
+ throw new SignatureConfigurationError(
574
+ "BoldSignAdapter apiBaseUrl must not contain credentials, query parameters, or a fragment."
575
+ );
576
+ }
577
+ return url.toString().replace(/\/+$/, "");
578
+ }
579
+ function normalizeWebhookSecrets(value) {
580
+ if (value === void 0) {
581
+ return [];
582
+ }
583
+ try {
584
+ return normalizeVerificationSecrets(value);
585
+ } catch (error) {
586
+ throw new SignatureConfigurationError(
587
+ "BoldSignAdapter webhookSecrets must contain non-empty strings.",
588
+ { cause: error }
589
+ );
590
+ }
591
+ }
592
+ function normalizeVerificationSecrets(value) {
593
+ const candidates = typeof value === "string" ? [value] : value;
594
+ if (!Array.isArray(candidates) || candidates.length === 0) {
595
+ throw new SignatureVerificationError(
596
+ "BoldSign webhook secrets must contain at least one secret."
597
+ );
598
+ }
599
+ return candidates.map(
600
+ (secret) => verificationString(secret, "BoldSign webhook secret")
601
+ );
602
+ }
603
+ function normalizeWebhookTolerance(value) {
604
+ const tolerance = value ?? DEFAULT_WEBHOOK_TOLERANCE_SECONDS;
605
+ if (!Number.isFinite(tolerance) || tolerance <= 0) {
606
+ throw new SignatureConfigurationError(
607
+ "BoldSign webhook tolerance must be a positive finite number."
608
+ );
609
+ }
610
+ return tolerance;
611
+ }
612
+ async function normalizeDocuments(documents, signal) {
613
+ if (!Array.isArray(documents) || documents.length === 0) {
614
+ throw new SignatureInputError(
615
+ "BoldSign createRequest requires at least one document."
616
+ );
617
+ }
618
+ if (documents.length > 25) {
619
+ throw new SignatureInputError(
620
+ "BoldSign createRequest supports at most 25 documents."
621
+ );
622
+ }
623
+ const normalized = [];
624
+ let totalBytes = 0;
625
+ for (const [index, document] of documents.entries()) {
626
+ if (!document || typeof document !== "object") {
627
+ throw new SignatureInputError(
628
+ `BoldSign document ${index + 1} must be an object.`
629
+ );
630
+ }
631
+ const name = requireNonEmptyString(
632
+ document.name,
633
+ `BoldSign document ${index + 1} name`
634
+ );
635
+ const mediaType = requireNonEmptyString(
636
+ document.mediaType,
637
+ `BoldSign document ${index + 1} mediaType`
638
+ );
639
+ const data = await readByteSource(
640
+ document.data,
641
+ `BoldSign document ${index + 1} data`,
642
+ signal
643
+ );
644
+ totalBytes += data.length;
645
+ if (totalBytes > MAX_BOLDSIGN_DOCUMENT_BYTES) {
646
+ throw new SignatureInputError(
647
+ "BoldSign document files exceed the 25 MB aggregate limit."
648
+ );
649
+ }
650
+ normalized.push({ name, mediaType, data });
651
+ }
652
+ return normalized;
653
+ }
654
+ function normalizeSignerInputs(signers) {
655
+ if (!Array.isArray(signers) || signers.length === 0) {
656
+ throw new SignatureInputError(
657
+ "BoldSign createRequest requires at least one signer."
658
+ );
659
+ }
660
+ const normalized = signers.map((signer, index) => {
661
+ if (!signer || typeof signer !== "object") {
662
+ throw new SignatureInputError(
663
+ `BoldSign signer ${index + 1} must be an object.`
664
+ );
665
+ }
666
+ const name = requireNonEmptyString(
667
+ signer.name,
668
+ `BoldSign signer ${index + 1} name`
669
+ );
670
+ const email = requireNonEmptyString(
671
+ signer.email,
672
+ `BoldSign signer ${index + 1} email`
673
+ );
674
+ if (!/^\S+@\S+\.\S+$/.test(email)) {
675
+ throw new SignatureInputError(
676
+ `BoldSign signer ${index + 1} email must be valid.`
677
+ );
678
+ }
679
+ if (!Array.isArray(signer.fields) || signer.fields.length === 0) {
680
+ throw new SignatureInputError(
681
+ `BoldSign signer ${index + 1} requires at least one field.`
682
+ );
683
+ }
684
+ const fields = signer.fields.map(
685
+ (field, fieldIndex) => normalizeField(field, index, fieldIndex)
686
+ );
687
+ const order = normalizeOptionalPositiveInteger(
688
+ signer.order,
689
+ `BoldSign signer ${index + 1} order`
690
+ );
691
+ return {
692
+ ...signer,
693
+ name,
694
+ email,
695
+ role: optionalTrimmedString(signer.role),
696
+ privateMessage: optionalTrimmedString(signer.privateMessage),
697
+ order,
698
+ authentication: normalizeAuthentication(signer.authentication, index),
699
+ fields
700
+ };
701
+ });
702
+ const emails = /* @__PURE__ */ new Set();
703
+ for (const signer of normalized) {
704
+ const email = signer.email.toLowerCase();
705
+ if (emails.has(email)) {
706
+ throw new SignatureInputError(
707
+ "BoldSign createRequest signer emails must be unique."
708
+ );
709
+ }
710
+ emails.add(email);
711
+ }
712
+ return normalized;
713
+ }
714
+ function normalizeField(field, signerIndex, fieldIndex) {
715
+ const context = `BoldSign signer ${signerIndex + 1} field ${fieldIndex + 1}`;
716
+ if (!field || typeof field !== "object") {
717
+ throw new SignatureInputError(`${context} must be an object.`);
718
+ }
719
+ const id = requireNonEmptyString(field.id, `${context} id`);
720
+ if (!/^[A-Za-z_]\w*$/.test(id)) {
721
+ throw new SignatureInputError(
722
+ `${context} id must start with a letter or underscore and contain only letters, digits, and underscores.`
723
+ );
724
+ }
725
+ if (!["signature", "initial", "date_signed", "text"].includes(field.type)) {
726
+ throw new SignatureInputError(`${context} has an unsupported type.`);
727
+ }
728
+ const page = normalizePositiveInteger(field.page, `${context} page`);
729
+ if (!field.bounds || typeof field.bounds !== "object") {
730
+ throw new SignatureInputError(`${context} bounds must be an object.`);
731
+ }
732
+ const bounds = {
733
+ x: normalizeNonNegativeFinite(field.bounds.x, `${context} bounds.x`),
734
+ y: normalizeNonNegativeFinite(field.bounds.y, `${context} bounds.y`),
735
+ width: normalizePositiveFinite(
736
+ field.bounds.width,
737
+ `${context} bounds.width`
738
+ ),
739
+ height: normalizePositiveFinite(
740
+ field.bounds.height,
741
+ `${context} bounds.height`
742
+ )
743
+ };
744
+ return {
745
+ id,
746
+ type: field.type,
747
+ page,
748
+ bounds,
749
+ required: field.required ?? true,
750
+ value: optionalTrimmedString(field.value)
751
+ };
752
+ }
753
+ function normalizeAuthentication(value, signerIndex) {
754
+ const authentication = value ?? { method: "none" };
755
+ const context = `BoldSign signer ${signerIndex + 1} authentication`;
756
+ if (!authentication || typeof authentication !== "object") {
757
+ throw new SignatureInputError(`${context} must be an object.`);
758
+ }
759
+ if (![
760
+ "none",
761
+ "access_code",
762
+ "email_otp",
763
+ "sms_otp",
764
+ "identity_verification"
765
+ ].includes(authentication.method)) {
766
+ throw new SignatureInputError(`${context} method is unsupported.`);
767
+ }
768
+ if (authentication.method === "access_code") {
769
+ return {
770
+ method: "access_code",
771
+ accessCode: requireNonEmptyString(
772
+ authentication.accessCode,
773
+ `${context} accessCode`
774
+ )
775
+ };
776
+ }
777
+ if (authentication.method === "sms_otp") {
778
+ const phone = authentication.phone;
779
+ if (!phone || typeof phone !== "object") {
780
+ throw new SignatureInputError(
781
+ `${context} phone is required for sms_otp.`
782
+ );
783
+ }
784
+ const countryCode = requireNonEmptyString(
785
+ phone.countryCode,
786
+ `${context} phone.countryCode`
787
+ );
788
+ const number = requireNonEmptyString(
789
+ phone.number,
790
+ `${context} phone.number`
791
+ );
792
+ if (!/^\+\d{1,3}$/.test(countryCode) || !/^\d{4,15}$/.test(number) || countryCode.length - 1 + number.length > 15) {
793
+ throw new SignatureInputError(
794
+ `${context} phone must contain an E.164 country code and national number.`
795
+ );
796
+ }
797
+ return { method: "sms_otp", phone: { countryCode, number } };
798
+ }
799
+ if (authentication.method === "identity_verification") {
800
+ const settings = authentication.identityVerification ?? {};
801
+ const frequency = normalizeOptionalEnum(
802
+ settings.frequency,
803
+ ["every_access", "until_signed", "once_per_document"],
804
+ `${context} frequency`
805
+ );
806
+ const nameMatch = normalizeOptionalEnum(
807
+ settings.nameMatch,
808
+ ["strict", "moderate", "lenient"],
809
+ `${context} nameMatch`
810
+ );
811
+ const maximumRetryCount = normalizeOptionalIntegerRange(
812
+ settings.maximumRetryCount,
813
+ 1,
814
+ 10,
815
+ `${context} maximumRetryCount`
816
+ );
817
+ const allowedDocumentTypes = normalizeOptionalEnumArray(
818
+ settings.allowedDocumentTypes,
819
+ ["passport", "identity_card", "driver_license"],
820
+ `${context} allowedDocumentTypes`
821
+ );
822
+ if (settings.allowedCountries !== void 0 && !Array.isArray(settings.allowedCountries)) {
823
+ throw new SignatureInputError(
824
+ `${context} allowedCountries must be an array.`
825
+ );
826
+ }
827
+ const allowedCountries = settings.allowedCountries?.map((country) => {
828
+ const normalized = requireNonEmptyString(
829
+ country,
830
+ `${context} allowed country`
831
+ ).toUpperCase();
832
+ if (!/^[A-Z]{2}$/.test(normalized)) {
833
+ throw new SignatureInputError(
834
+ `${context} allowed countries must be ISO 3166-1 alpha-2 codes.`
835
+ );
836
+ }
837
+ return normalized;
838
+ });
839
+ return {
840
+ method: "identity_verification",
841
+ identityVerification: {
842
+ ...settings,
843
+ frequency,
844
+ nameMatch,
845
+ maximumRetryCount,
846
+ requireLiveCapture: normalizeOptionalBoolean(
847
+ settings.requireLiveCapture,
848
+ `${context} requireLiveCapture`
849
+ ),
850
+ requireMatchingSelfie: normalizeOptionalBoolean(
851
+ settings.requireMatchingSelfie,
852
+ `${context} requireMatchingSelfie`
853
+ ),
854
+ allowedDocumentTypes,
855
+ allowedCountries
856
+ }
857
+ };
858
+ }
859
+ return { method: authentication.method };
860
+ }
861
+ function normalizeMetadata(metadata, reserved) {
862
+ if (metadata !== void 0 && (!metadata || typeof metadata !== "object" || Array.isArray(metadata))) {
863
+ throw new SignatureInputError("BoldSign metadata must be an object.");
864
+ }
865
+ const result = {};
866
+ const reservedKeys = new Set(Object.keys(reserved));
867
+ for (const [key, value] of Object.entries(metadata ?? {})) {
868
+ const normalizedKey = requireNonEmptyString(key, "BoldSign metadata key");
869
+ if (reservedKeys.has(normalizedKey)) {
870
+ throw new SignatureInputError(
871
+ `BoldSign metadata key ${normalizedKey} is reserved.`
872
+ );
873
+ }
874
+ if (normalizedKey.length > MAX_BOLDSIGN_METADATA_KEY_LENGTH) {
875
+ throw new SignatureInputError(
876
+ `BoldSign metadata key ${normalizedKey} exceeds ${MAX_BOLDSIGN_METADATA_KEY_LENGTH} characters.`
877
+ );
878
+ }
879
+ if (typeof value !== "string") {
880
+ throw new SignatureInputError(
881
+ `BoldSign metadata value for ${normalizedKey} must be a string.`
882
+ );
883
+ }
884
+ if (value.length > MAX_BOLDSIGN_METADATA_VALUE_LENGTH) {
885
+ throw new SignatureInputError(
886
+ `BoldSign metadata value for ${normalizedKey} exceeds ${MAX_BOLDSIGN_METADATA_VALUE_LENGTH} characters.`
887
+ );
888
+ }
889
+ result[normalizedKey] = value;
890
+ }
891
+ for (const [key, value] of Object.entries(reserved)) {
892
+ if (value.length > MAX_BOLDSIGN_METADATA_VALUE_LENGTH) {
893
+ throw new SignatureInputError(
894
+ `BoldSign ${key} exceeds ${MAX_BOLDSIGN_METADATA_VALUE_LENGTH} characters.`
895
+ );
896
+ }
897
+ result[key] = value;
898
+ }
899
+ if (Object.keys(result).length > MAX_BOLDSIGN_METADATA_ENTRIES) {
900
+ throw new SignatureInputError(
901
+ `BoldSign metadata supports at most ${MAX_BOLDSIGN_METADATA_ENTRIES} entries including tenant and idempotency bindings.`
902
+ );
903
+ }
904
+ return result;
905
+ }
906
+ function normalizeExpiryDays(value) {
907
+ const days = value ?? 60;
908
+ if (!Number.isSafeInteger(days) || days < MIN_EXPIRY_DAYS || days > MAX_EXPIRY_DAYS) {
909
+ throw new SignatureInputError(
910
+ `BoldSign expiresInDays must be an integer from ${MIN_EXPIRY_DAYS} to ${MAX_EXPIRY_DAYS}.`
911
+ );
912
+ }
913
+ return days;
914
+ }
915
+ function toBoldSignSigner(signer) {
916
+ const authentication = signer.authentication ?? { method: "none" };
917
+ const result = {
918
+ Name: signer.name,
919
+ EmailAddress: signer.email,
920
+ SignerType: "Signer",
921
+ SignerRole: signer.role,
922
+ Order: signer.order,
923
+ PrivateMessage: signer.privateMessage,
924
+ Locale: "EN",
925
+ FormFields: signer.fields.map((field) => ({
926
+ Id: field.id,
927
+ Name: field.id,
928
+ FieldType: toBoldSignFieldType(field.type),
929
+ PageNumber: field.page,
930
+ Bounds: {
931
+ X: field.bounds.x,
932
+ Y: field.bounds.y,
933
+ Width: field.bounds.width,
934
+ Height: field.bounds.height
935
+ },
936
+ IsRequired: field.required ?? true,
937
+ Value: field.value
938
+ })),
939
+ ...toBoldSignAuthentication(authentication)
940
+ };
941
+ return withoutUndefined(result);
942
+ }
943
+ function toBoldSignFieldType(type) {
944
+ switch (type) {
945
+ case "signature":
946
+ return "Signature";
947
+ case "initial":
948
+ return "Initial";
949
+ case "date_signed":
950
+ return "DateSigned";
951
+ case "text":
952
+ return "TextBox";
953
+ }
954
+ }
955
+ function toBoldSignAuthentication(authentication) {
956
+ switch (authentication.method) {
957
+ case "access_code":
958
+ return {
959
+ AuthenticationType: "AccessCode",
960
+ AuthenticationCode: authentication.accessCode
961
+ };
962
+ case "email_otp":
963
+ return { AuthenticationType: "EmailOTP", EnableEmailOTP: true };
964
+ case "sms_otp":
965
+ return {
966
+ AuthenticationType: "SMSOTP",
967
+ PhoneNumber: authentication.phone ? {
968
+ CountryCode: authentication.phone.countryCode,
969
+ Number: authentication.phone.number
970
+ } : void 0
971
+ };
972
+ case "identity_verification":
973
+ return {
974
+ AuthenticationType: "IdVerification",
975
+ IdentityVerificationSettings: toBoldSignIdentityVerification(
976
+ authentication.identityVerification
977
+ )
978
+ };
979
+ case "none":
980
+ return { AuthenticationType: "None" };
981
+ }
982
+ }
983
+ function toBoldSignIdentityVerification(settings) {
984
+ return withoutUndefined({
985
+ Type: settings?.frequency === void 0 ? void 0 : {
986
+ every_access: "EveryAccess",
987
+ until_signed: "UntilSignCompleted",
988
+ once_per_document: "OncePerDocument"
989
+ }[settings.frequency],
990
+ MaximumRetryCount: settings?.maximumRetryCount,
991
+ RequireLiveCapture: settings?.requireLiveCapture,
992
+ RequireMatchingSelfie: settings?.requireMatchingSelfie,
993
+ NameMatcher: settings?.nameMatch === void 0 ? void 0 : {
994
+ strict: "Strict",
995
+ moderate: "Moderate",
996
+ lenient: "Lenient"
997
+ }[settings.nameMatch],
998
+ AllowedDocumentTypes: settings?.allowedDocumentTypes?.map(
999
+ (type) => ({
1000
+ passport: "Passport",
1001
+ identity_card: "IDCard",
1002
+ driver_license: "DriverLicense"
1003
+ })[type]
1004
+ ),
1005
+ AllowedCountries: settings?.allowedCountries
1006
+ });
1007
+ }
1008
+ function inputSignerToResult(signer) {
1009
+ return {
1010
+ name: signer.name,
1011
+ email: signer.email,
1012
+ role: signer.role,
1013
+ order: signer.order,
1014
+ status: "pending",
1015
+ authenticationMethod: signer.authentication?.method ?? "none"
1016
+ };
1017
+ }
1018
+ function mapBoldSignRequest(value, tenantId) {
1019
+ const id = requireProviderString(
1020
+ readString(value, "documentId"),
1021
+ "BoldSign document properties documentId"
1022
+ );
1023
+ const createdAt = parseOptionalEpochSeconds(value.createdDate);
1024
+ const expiresAt = parseBoldSignExpiry(value, createdAt);
1025
+ return {
1026
+ provider: BOLDSIGN_PROVIDER_ID,
1027
+ tenantId,
1028
+ id,
1029
+ status: mapBoldSignStatus(readString(value, "status")),
1030
+ title: optionalTrimmedString(readString(value, "messageTitle")),
1031
+ signers: mapBoldSignSigners(value),
1032
+ createdAt,
1033
+ expiresAt,
1034
+ metadata: readBoldSignMetadata(value),
1035
+ raw: value
1036
+ };
1037
+ }
1038
+ function mapBoldSignSigners(value) {
1039
+ return readRecords(value, "signerDetails").map((signer) => ({
1040
+ id: optionalTrimmedString(readString(signer, "id")),
1041
+ name: requireProviderString(
1042
+ readString(signer, "signerName"),
1043
+ "BoldSign signer name"
1044
+ ),
1045
+ email: requireProviderString(
1046
+ readString(signer, "signerEmail"),
1047
+ "BoldSign signer email"
1048
+ ),
1049
+ role: optionalTrimmedString(readString(signer, "signerRole")),
1050
+ order: readNumber(signer, "order"),
1051
+ status: mapBoldSignSignerStatus(
1052
+ readString(signer, "status"),
1053
+ readBoolean(signer, "isDeliveryFailed"),
1054
+ readBoolean(signer, "isViewed"),
1055
+ readBoolean(signer, "isAuthenticationFailed") === true || readString(readRecord(signer, "idVerification"), "status")?.trim().toLowerCase() === "failed"
1056
+ ),
1057
+ authenticationMethod: mapBoldSignAuthentication(
1058
+ readString(signer, "authenticationType")
1059
+ ),
1060
+ viewed: readBoolean(signer, "isViewed"),
1061
+ deliveryFailed: readBoolean(signer, "isDeliveryFailed")
1062
+ }));
1063
+ }
1064
+ function mapBoldSignStatus(value) {
1065
+ switch (value?.trim().toLowerCase()) {
1066
+ case "draft":
1067
+ return "prepared";
1068
+ case "inprogress":
1069
+ case "in_progress":
1070
+ case "sent":
1071
+ case "needsattention":
1072
+ case "needs_attention":
1073
+ case "needs attention":
1074
+ return "sent";
1075
+ case "viewed":
1076
+ return "viewed";
1077
+ case "partiallysigned":
1078
+ case "partially_signed":
1079
+ return "partially_signed";
1080
+ case "completed":
1081
+ return "completed";
1082
+ case "declined":
1083
+ return "declined";
1084
+ case "revoked":
1085
+ case "cancelled":
1086
+ case "canceled":
1087
+ return "cancelled";
1088
+ case "expired":
1089
+ return "expired";
1090
+ case "failed":
1091
+ case "sendfailed":
1092
+ return "failed";
1093
+ default:
1094
+ throw new SignatureProviderError(
1095
+ `Unsupported BoldSign document status: ${value ?? "<missing>"}`
1096
+ );
1097
+ }
1098
+ }
1099
+ function mapBoldSignWebhookStatus(eventType, documentStatus) {
1100
+ switch (eventType.trim().toLowerCase()) {
1101
+ case "sent":
1102
+ return "sent";
1103
+ case "viewed":
1104
+ return "viewed";
1105
+ case "signed":
1106
+ return documentStatus?.toLowerCase() === "completed" ? "completed" : "partially_signed";
1107
+ case "completed":
1108
+ return "completed";
1109
+ case "declined":
1110
+ return "declined";
1111
+ case "revoked":
1112
+ return "cancelled";
1113
+ case "expired":
1114
+ return "expired";
1115
+ case "sendfailed":
1116
+ return "failed";
1117
+ default:
1118
+ return mapBoldSignStatus(documentStatus);
1119
+ }
1120
+ }
1121
+ function mapBoldSignSignerStatus(value, deliveryFailed, viewed, authenticationFailed = false) {
1122
+ if (deliveryFailed || authenticationFailed) {
1123
+ return "failed";
1124
+ }
1125
+ switch (value?.trim().toLowerCase()) {
1126
+ case "notcompleted":
1127
+ case "not_completed":
1128
+ case "pending":
1129
+ return viewed ? "viewed" : "pending";
1130
+ case "completed":
1131
+ case "signed":
1132
+ return "signed";
1133
+ case "declined":
1134
+ return "declined";
1135
+ case "expired":
1136
+ return "expired";
1137
+ case "failed":
1138
+ case "authenticationfailed":
1139
+ return "failed";
1140
+ default:
1141
+ throw new SignatureProviderError(
1142
+ `Unsupported BoldSign signer status: ${value ?? "<missing>"}`
1143
+ );
1144
+ }
1145
+ }
1146
+ function mapBoldSignAuthentication(value) {
1147
+ switch (value?.trim().toLowerCase()) {
1148
+ case "none":
1149
+ return "none";
1150
+ case "accesscode":
1151
+ return "access_code";
1152
+ case "emailotp":
1153
+ return "email_otp";
1154
+ case "smsotp":
1155
+ return "sms_otp";
1156
+ case "idverification":
1157
+ return "identity_verification";
1158
+ default:
1159
+ return void 0;
1160
+ }
1161
+ }
1162
+ function readBoldSignMetadata(value) {
1163
+ const metadata = readRecord(value, "metaData") ?? readRecord(value, "metadata") ?? readRecord(value, "MetaData") ?? {};
1164
+ const result = {};
1165
+ for (const [key, item] of Object.entries(metadata)) {
1166
+ if (typeof item === "string") {
1167
+ result[key] = item;
1168
+ }
1169
+ }
1170
+ return result;
1171
+ }
1172
+ function parseBoldSignExpiry(value, createdAt) {
1173
+ const raw = value.expiryDate;
1174
+ if (typeof raw === "number" && Number.isFinite(raw)) {
1175
+ return new Date(raw * 1e3);
1176
+ }
1177
+ if (typeof raw === "string" && raw.trim()) {
1178
+ const date = new Date(raw);
1179
+ if (!Number.isNaN(date.getTime())) {
1180
+ return date;
1181
+ }
1182
+ }
1183
+ const expiryDays = readNumber(value, "expiryDays");
1184
+ return createdAt && expiryDays !== void 0 ? new Date(createdAt.getTime() + expiryDays * 24 * 60 * 60 * 1e3) : void 0;
1185
+ }
1186
+ async function boldSignResponseError(response, createOperation) {
1187
+ const text = await response.text();
1188
+ let body;
1189
+ if (text) {
1190
+ try {
1191
+ const parsed = JSON.parse(text);
1192
+ body = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
1193
+ } catch {
1194
+ body = void 0;
1195
+ }
1196
+ }
1197
+ const nestedError = body ? readRecord(body, "error") : void 0;
1198
+ const message = optionalTrimmedString(readString(body, "message")) ?? optionalTrimmedString(readString(nestedError, "message")) ?? `HTTP ${response.status}`;
1199
+ const retryable = response.status === 408 || response.status === 425 || response.status === 429 || response.status >= 500;
1200
+ return new SignatureProviderError(`BoldSign API: ${message}`, {
1201
+ status: response.status,
1202
+ retryable,
1203
+ retryAfterMs: parseRetryAfter(response.headers.get("Retry-After")),
1204
+ requestMayHaveSucceeded: createOperation && (response.status === 408 || response.status >= 500)
1205
+ });
1206
+ }
1207
+ function parseRetryAfter(value) {
1208
+ if (!value) {
1209
+ return void 0;
1210
+ }
1211
+ const seconds = Number(value);
1212
+ if (Number.isFinite(seconds) && seconds >= 0) {
1213
+ return Math.round(seconds * 1e3);
1214
+ }
1215
+ const date = new Date(value);
1216
+ return Number.isNaN(date.getTime()) ? void 0 : Math.max(0, date.getTime() - Date.now());
1217
+ }
1218
+ function normalizePositiveInteger(value, context) {
1219
+ if (!Number.isSafeInteger(value) || value <= 0) {
1220
+ throw new SignatureInputError(`${context} must be a positive integer.`);
1221
+ }
1222
+ return value;
1223
+ }
1224
+ function normalizeOptionalPositiveInteger(value, context) {
1225
+ return value === void 0 ? void 0 : normalizePositiveInteger(value, context);
1226
+ }
1227
+ function normalizeOptionalIntegerRange(value, min, max, context) {
1228
+ if (value === void 0) {
1229
+ return void 0;
1230
+ }
1231
+ if (!Number.isSafeInteger(value) || value < min || value > max) {
1232
+ throw new SignatureInputError(
1233
+ `${context} must be an integer from ${min} to ${max}.`
1234
+ );
1235
+ }
1236
+ return value;
1237
+ }
1238
+ function normalizeOptionalBoolean(value, context) {
1239
+ if (value !== void 0 && typeof value !== "boolean") {
1240
+ throw new SignatureInputError(`${context} must be a boolean.`);
1241
+ }
1242
+ return value;
1243
+ }
1244
+ function normalizeOptionalEnum(value, allowed, context) {
1245
+ if (value !== void 0 && !allowed.includes(value)) {
1246
+ throw new SignatureInputError(
1247
+ `${context} must be one of ${allowed.join(", ")}.`
1248
+ );
1249
+ }
1250
+ return value;
1251
+ }
1252
+ function normalizeOptionalEnumArray(value, allowed, context) {
1253
+ if (value === void 0) {
1254
+ return void 0;
1255
+ }
1256
+ if (!Array.isArray(value)) {
1257
+ throw new SignatureInputError(`${context} must be an array.`);
1258
+ }
1259
+ return value.map((item) => {
1260
+ const normalized = normalizeOptionalEnum(item, allowed, context);
1261
+ if (normalized === void 0) {
1262
+ throw new SignatureInputError(`${context} must not contain undefined.`);
1263
+ }
1264
+ return normalized;
1265
+ });
1266
+ }
1267
+ function normalizeNonNegativeFinite(value, context) {
1268
+ if (!Number.isFinite(value) || value < 0) {
1269
+ throw new SignatureInputError(
1270
+ `${context} must be a non-negative finite number.`
1271
+ );
1272
+ }
1273
+ return value;
1274
+ }
1275
+ function normalizePositiveFinite(value, context) {
1276
+ if (!Number.isFinite(value) || value <= 0) {
1277
+ throw new SignatureInputError(
1278
+ `${context} must be a positive finite number.`
1279
+ );
1280
+ }
1281
+ return value;
1282
+ }
1283
+ function optionalTrimmedString(value) {
1284
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
1285
+ }
1286
+ function requireProviderString(value, context) {
1287
+ if (!value?.trim()) {
1288
+ throw new SignatureProviderError(`${context} must be a non-empty string.`);
1289
+ }
1290
+ return value.trim();
1291
+ }
1292
+ function verificationString(value, context) {
1293
+ if (typeof value !== "string" || !value.trim()) {
1294
+ throw new SignatureVerificationError(
1295
+ `${context} must be a non-empty string.`
1296
+ );
1297
+ }
1298
+ return value.trim();
1299
+ }
1300
+ function requireVerificationString(value, context) {
1301
+ return verificationString(value, context);
1302
+ }
1303
+ function verificationRecord(value, context) {
1304
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1305
+ throw new SignatureVerificationError(`${context} must be an object.`);
1306
+ }
1307
+ return value;
1308
+ }
1309
+ function withoutUndefined(value) {
1310
+ return Object.fromEntries(
1311
+ Object.entries(value).filter(([, item]) => item !== void 0)
1312
+ );
1313
+ }
1314
+ function safeFilename(value) {
1315
+ return value.replace(/[^A-Za-z0-9_-]+/g, "_").slice(0, 120) || "document";
1316
+ }
1317
+ async function readByteSource(source, context, signal) {
1318
+ if (source instanceof Uint8Array) {
1319
+ if (source.length === 0) {
1320
+ throw new SignatureInputError(`${context} must not be empty.`);
1321
+ }
1322
+ if (source.length > MAX_BOLDSIGN_DOCUMENT_BYTES) {
1323
+ throw new SignatureInputError(
1324
+ `${context} exceeds BoldSign's 25 MB limit.`
1325
+ );
1326
+ }
1327
+ return source;
1328
+ }
1329
+ const chunks = [];
1330
+ let total = 0;
1331
+ const append = (chunk) => {
1332
+ signal?.throwIfAborted();
1333
+ if (!(chunk instanceof Uint8Array) || chunk.length === 0) {
1334
+ throw new SignatureInputError(
1335
+ `${context} stream must yield non-empty Uint8Array chunks.`
1336
+ );
1337
+ }
1338
+ total += chunk.length;
1339
+ if (total > MAX_BOLDSIGN_DOCUMENT_BYTES) {
1340
+ throw new SignatureInputError(
1341
+ `${context} exceeds BoldSign's 25 MB limit.`
1342
+ );
1343
+ }
1344
+ chunks.push(chunk);
1345
+ };
1346
+ signal?.throwIfAborted();
1347
+ if (isReadableStream(source)) {
1348
+ const reader = source.getReader();
1349
+ try {
1350
+ while (true) {
1351
+ const result2 = await reader.read();
1352
+ if (result2.done) {
1353
+ break;
1354
+ }
1355
+ append(result2.value);
1356
+ }
1357
+ } finally {
1358
+ reader.releaseLock();
1359
+ }
1360
+ } else if (isAsyncIterable(source)) {
1361
+ for await (const chunk of source) {
1362
+ append(chunk);
1363
+ }
1364
+ } else {
1365
+ throw new SignatureInputError(
1366
+ `${context} must be a Uint8Array, ReadableStream, or AsyncIterable.`
1367
+ );
1368
+ }
1369
+ if (total === 0) {
1370
+ throw new SignatureInputError(`${context} must not be empty.`);
1371
+ }
1372
+ const result = new Uint8Array(total);
1373
+ let offset = 0;
1374
+ for (const chunk of chunks) {
1375
+ result.set(chunk, offset);
1376
+ offset += chunk.length;
1377
+ }
1378
+ return result;
1379
+ }
1380
+ function isReadableStream(value) {
1381
+ return Boolean(value) && typeof value === "object" && typeof value.getReader === "function";
1382
+ }
1383
+ function isAsyncIterable(value) {
1384
+ return value !== null && value !== void 0 && typeof value === "object" && Symbol.asyncIterator in value;
1385
+ }
1386
+ function createSha256Stream(source) {
1387
+ const reader = source.getReader();
1388
+ const hash = createHash("sha256");
1389
+ let settled = false;
1390
+ let resolveHash;
1391
+ let rejectHash;
1392
+ const sha256 = new Promise((resolve, reject) => {
1393
+ resolveHash = resolve;
1394
+ rejectHash = reject;
1395
+ });
1396
+ const stream = new ReadableStream({
1397
+ async pull(controller) {
1398
+ try {
1399
+ const result = await reader.read();
1400
+ if (result.done) {
1401
+ settled = true;
1402
+ resolveHash(hash.digest("hex"));
1403
+ controller.close();
1404
+ return;
1405
+ }
1406
+ hash.update(result.value);
1407
+ controller.enqueue(result.value);
1408
+ } catch (error) {
1409
+ settled = true;
1410
+ rejectHash(error);
1411
+ controller.error(error);
1412
+ }
1413
+ },
1414
+ async cancel(reason) {
1415
+ try {
1416
+ await reader.cancel(reason);
1417
+ } finally {
1418
+ if (!settled) {
1419
+ settled = true;
1420
+ rejectHash(
1421
+ new SignatureProviderError(
1422
+ "BoldSign artifact stream was cancelled before hashing completed."
1423
+ )
1424
+ );
1425
+ }
1426
+ }
1427
+ }
1428
+ });
1429
+ return { stream, sha256 };
1430
+ }
1431
+ function isTerminalStatus(status) {
1432
+ return ["completed", "declined", "cancelled", "expired", "failed"].includes(
1433
+ status
1434
+ );
1435
+ }
1436
+ export {
1437
+ BOLDSIGN_IDEMPOTENCY_METADATA_KEY,
1438
+ BOLDSIGN_PROVIDER_ID,
1439
+ BOLDSIGN_TENANT_METADATA_KEY,
1440
+ BoldSignAdapter,
1441
+ verifyBoldSignWebhookSignature
1442
+ };
1443
+ //# sourceMappingURL=boldsign.js.map