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