@assinafy/sdk 1.4.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.
- package/LICENSE +19 -0
- package/README.md +466 -0
- package/dist/index.d.mts +1123 -0
- package/dist/index.d.ts +1123 -0
- package/dist/index.js +1564 -0
- package/dist/index.mjs +1511 -0
- package/package.json +69 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1564 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
ApiError: () => ApiError,
|
|
34
|
+
AssignmentResource: () => AssignmentResource,
|
|
35
|
+
AssinafyClient: () => AssinafyClient,
|
|
36
|
+
AssinafyError: () => AssinafyError,
|
|
37
|
+
AuthenticationResource: () => AuthenticationResource,
|
|
38
|
+
DocumentResource: () => DocumentResource,
|
|
39
|
+
FieldsResource: () => FieldsResource,
|
|
40
|
+
NetworkError: () => NetworkError,
|
|
41
|
+
SignerDocumentsResource: () => SignerDocumentsResource,
|
|
42
|
+
SignerResource: () => SignerResource,
|
|
43
|
+
TagResource: () => TagResource,
|
|
44
|
+
TemplateResource: () => TemplateResource,
|
|
45
|
+
ValidationError: () => ValidationError,
|
|
46
|
+
WebhookResource: () => WebhookResource,
|
|
47
|
+
WebhookVerifier: () => WebhookVerifier,
|
|
48
|
+
WorkspaceResource: () => WorkspaceResource,
|
|
49
|
+
buildAssignmentPayload: () => buildAssignmentPayload
|
|
50
|
+
});
|
|
51
|
+
module.exports = __toCommonJS(index_exports);
|
|
52
|
+
|
|
53
|
+
// src/client.ts
|
|
54
|
+
var import_axios2 = __toESM(require("axios"));
|
|
55
|
+
|
|
56
|
+
// src/errors.ts
|
|
57
|
+
var AssinafyError = class extends Error {
|
|
58
|
+
constructor(message, context = {}, options) {
|
|
59
|
+
super(message);
|
|
60
|
+
this.name = "AssinafyError";
|
|
61
|
+
this.context = context;
|
|
62
|
+
if (options?.cause !== void 0) {
|
|
63
|
+
this.cause = options.cause;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
var ApiError = class _ApiError extends AssinafyError {
|
|
68
|
+
constructor(message, statusCode, responseData = null, options) {
|
|
69
|
+
super(message, { statusCode, responseData }, options);
|
|
70
|
+
this.name = "ApiError";
|
|
71
|
+
this.statusCode = statusCode;
|
|
72
|
+
this.responseData = responseData;
|
|
73
|
+
}
|
|
74
|
+
static fromResponse(statusCode, responseData) {
|
|
75
|
+
const data = responseData ?? {};
|
|
76
|
+
const rawMessage = data["message"];
|
|
77
|
+
const rawError = data["error"];
|
|
78
|
+
const message = typeof rawMessage === "string" && rawMessage.length > 0 ? rawMessage : typeof rawError === "string" ? rawError : "API request failed";
|
|
79
|
+
return new _ApiError(message, statusCode, responseData);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
var ValidationError = class extends AssinafyError {
|
|
83
|
+
constructor(message = "Validation failed", errors = {}) {
|
|
84
|
+
super(message, { errors });
|
|
85
|
+
this.name = "ValidationError";
|
|
86
|
+
this.errors = errors;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
var NetworkError = class extends AssinafyError {
|
|
90
|
+
constructor(message, options) {
|
|
91
|
+
super(message, {}, options);
|
|
92
|
+
this.name = "NetworkError";
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// src/utils.ts
|
|
97
|
+
var import_axios = __toESM(require("axios"));
|
|
98
|
+
function handleAssinafyResponse(response) {
|
|
99
|
+
const resp = response;
|
|
100
|
+
if (resp && typeof resp === "object" && resp.status !== void 0 && "data" in resp) {
|
|
101
|
+
if (resp.status >= 200 && resp.status < 300) {
|
|
102
|
+
return resp.data;
|
|
103
|
+
}
|
|
104
|
+
throw ApiError.fromResponse(resp.status, resp);
|
|
105
|
+
}
|
|
106
|
+
return response;
|
|
107
|
+
}
|
|
108
|
+
function toSdkError(error, fallbackMessage) {
|
|
109
|
+
if (error instanceof AssinafyError) {
|
|
110
|
+
return error;
|
|
111
|
+
}
|
|
112
|
+
if (import_axios.default.isAxiosError(error)) {
|
|
113
|
+
const status = error.response?.status;
|
|
114
|
+
if (status) {
|
|
115
|
+
return ApiError.fromResponse(status, error.response?.data ?? null);
|
|
116
|
+
}
|
|
117
|
+
return new NetworkError(`${fallbackMessage}: ${error.message}`, { cause: error });
|
|
118
|
+
}
|
|
119
|
+
if (error instanceof Error) {
|
|
120
|
+
return new AssinafyError(`${fallbackMessage}: ${error.message}`, {}, { cause: error });
|
|
121
|
+
}
|
|
122
|
+
return new AssinafyError(fallbackMessage, { cause: error });
|
|
123
|
+
}
|
|
124
|
+
function createNoopLogger() {
|
|
125
|
+
return {
|
|
126
|
+
debug: () => void 0,
|
|
127
|
+
info: () => void 0,
|
|
128
|
+
warn: () => void 0,
|
|
129
|
+
error: () => void 0
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function cleanParams(params) {
|
|
133
|
+
const out = {};
|
|
134
|
+
for (const [key, value] of Object.entries(params)) {
|
|
135
|
+
if (value !== void 0 && value !== null) {
|
|
136
|
+
out[key] = value;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/resources/documents.ts
|
|
143
|
+
var import_node_fs = require("fs");
|
|
144
|
+
var import_node_path = __toESM(require("path"));
|
|
145
|
+
|
|
146
|
+
// src/resources/base.ts
|
|
147
|
+
var BaseResource = class {
|
|
148
|
+
constructor(http, defaultAccountId, logger = createNoopLogger()) {
|
|
149
|
+
this.http = http;
|
|
150
|
+
this.defaultAccountId = defaultAccountId;
|
|
151
|
+
this.logger = logger;
|
|
152
|
+
}
|
|
153
|
+
/** Resolve the effective account id, throwing if none is available. */
|
|
154
|
+
accountId(explicit) {
|
|
155
|
+
const id = explicit ?? this.defaultAccountId;
|
|
156
|
+
if (!id) {
|
|
157
|
+
throw new ValidationError(
|
|
158
|
+
"Account ID is required. Provide it as a parameter or set a default in the client."
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
return id;
|
|
162
|
+
}
|
|
163
|
+
/** Guard required path arguments (documentId, signerId, …). */
|
|
164
|
+
requireId(value, name) {
|
|
165
|
+
if (!value) {
|
|
166
|
+
throw new ValidationError(`${name} is required`);
|
|
167
|
+
}
|
|
168
|
+
return value;
|
|
169
|
+
}
|
|
170
|
+
/** Execute an HTTP call and return the unwrapped envelope body. */
|
|
171
|
+
async call(label, request) {
|
|
172
|
+
try {
|
|
173
|
+
const response = await request();
|
|
174
|
+
return handleAssinafyResponse(response.data);
|
|
175
|
+
} catch (err) {
|
|
176
|
+
throw toSdkError(err, label);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/** Like {@link call} but returns `null` when the API responds with 404. */
|
|
180
|
+
async callOptional(label, request) {
|
|
181
|
+
try {
|
|
182
|
+
return await this.call(label, request);
|
|
183
|
+
} catch (err) {
|
|
184
|
+
if (err instanceof ApiError && err.statusCode === 404) return null;
|
|
185
|
+
throw err;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/** Execute an HTTP call that returns no body (DELETE / 204). */
|
|
189
|
+
async callVoid(label, request) {
|
|
190
|
+
try {
|
|
191
|
+
const response = await request();
|
|
192
|
+
if (response.status < 200 || response.status >= 300) {
|
|
193
|
+
throw new ValidationError(`${label}: HTTP ${response.status}`);
|
|
194
|
+
}
|
|
195
|
+
} catch (err) {
|
|
196
|
+
throw toSdkError(err, label);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/** Execute an HTTP call that returns binary data (artifact downloads). */
|
|
200
|
+
async callBinary(label, request) {
|
|
201
|
+
try {
|
|
202
|
+
const response = await request();
|
|
203
|
+
return Buffer.from(response.data);
|
|
204
|
+
} catch (err) {
|
|
205
|
+
throw toSdkError(err, label);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/** Execute a paginated list call and attach meta from `X-Pagination-*` headers. */
|
|
209
|
+
async callList(label, request) {
|
|
210
|
+
try {
|
|
211
|
+
const response = await request();
|
|
212
|
+
const unwrapped = handleAssinafyResponse(response.data);
|
|
213
|
+
const data = Array.isArray(unwrapped) ? unwrapped : Array.isArray(unwrapped?.data) ? unwrapped.data : [];
|
|
214
|
+
const meta = parsePaginationMeta(response.headers);
|
|
215
|
+
return meta === void 0 ? { data } : { data, meta };
|
|
216
|
+
} catch (err) {
|
|
217
|
+
throw toSdkError(err, label);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
function parsePaginationMeta(headers) {
|
|
222
|
+
if (!headers) return void 0;
|
|
223
|
+
const read = (key) => {
|
|
224
|
+
const h = headers;
|
|
225
|
+
const raw = h[key] ?? h[key.toLowerCase()];
|
|
226
|
+
if (raw === void 0 || raw === null) return void 0;
|
|
227
|
+
return Array.isArray(raw) ? String(raw[0]) : String(raw);
|
|
228
|
+
};
|
|
229
|
+
const current = toInt(read("x-pagination-current-page"));
|
|
230
|
+
const perPage = toInt(read("x-pagination-per-page"));
|
|
231
|
+
const total = toInt(read("x-pagination-total-count"));
|
|
232
|
+
const lastPage = toInt(read("x-pagination-page-count"));
|
|
233
|
+
if (current === void 0 && perPage === void 0 && total === void 0 && lastPage === void 0) {
|
|
234
|
+
return void 0;
|
|
235
|
+
}
|
|
236
|
+
const meta = {};
|
|
237
|
+
if (current !== void 0) meta.current_page = current;
|
|
238
|
+
if (perPage !== void 0) meta.per_page = perPage;
|
|
239
|
+
if (total !== void 0) meta.total = total;
|
|
240
|
+
if (lastPage !== void 0) meta.last_page = lastPage;
|
|
241
|
+
return meta;
|
|
242
|
+
}
|
|
243
|
+
function toInt(value) {
|
|
244
|
+
if (value === void 0) return void 0;
|
|
245
|
+
const n = Number.parseInt(value, 10);
|
|
246
|
+
return Number.isFinite(n) ? n : void 0;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// src/resources/documents.ts
|
|
250
|
+
var MAX_UPLOAD_BYTES = 25 * 1024 * 1024;
|
|
251
|
+
var READY_STATUSES = /* @__PURE__ */ new Set([
|
|
252
|
+
"metadata_ready",
|
|
253
|
+
"pending_signature",
|
|
254
|
+
"certificated"
|
|
255
|
+
]);
|
|
256
|
+
var FAILED_STATUSES = /* @__PURE__ */ new Set([
|
|
257
|
+
"failed",
|
|
258
|
+
"rejected_by_signer",
|
|
259
|
+
"rejected_by_user",
|
|
260
|
+
"expired"
|
|
261
|
+
]);
|
|
262
|
+
var DocumentResource = class extends BaseResource {
|
|
263
|
+
/**
|
|
264
|
+
* Upload a PDF to the workspace.
|
|
265
|
+
*
|
|
266
|
+
* @example
|
|
267
|
+
* ```ts
|
|
268
|
+
* await client.documents.upload({ filePath: './contract.pdf' });
|
|
269
|
+
* await client.documents.upload({ buffer, fileName: 'contract.pdf' }, { metadata });
|
|
270
|
+
* ```
|
|
271
|
+
*/
|
|
272
|
+
async upload(source, options = {}) {
|
|
273
|
+
const { buffer, fileName } = await loadSource(source);
|
|
274
|
+
validateUpload(buffer, fileName);
|
|
275
|
+
const accountId = this.accountId(options.accountId);
|
|
276
|
+
const form = buildUploadForm(buffer, fileName, options.metadata);
|
|
277
|
+
this.logger.info("Uploading document", { fileName, size: buffer.byteLength });
|
|
278
|
+
const document = await this.call(
|
|
279
|
+
"Document upload failed",
|
|
280
|
+
() => this.http.post(`/accounts/${accountId}/documents`, form, {
|
|
281
|
+
headers: { "Content-Type": "multipart/form-data" }
|
|
282
|
+
})
|
|
283
|
+
);
|
|
284
|
+
if (!document?.id) {
|
|
285
|
+
throw new ValidationError("Upload succeeded but no document ID was returned", {
|
|
286
|
+
response: document
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
this.logger.info("Document uploaded", { documentId: document.id });
|
|
290
|
+
return document;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* List workspace documents. Pagination info (if any) is attached in `meta`.
|
|
294
|
+
* Supports `status`, `method`, `tags`, `search`, `sort`, `page`, `per_page`.
|
|
295
|
+
*/
|
|
296
|
+
async list(params = {}, accountId) {
|
|
297
|
+
const id = this.accountId(accountId);
|
|
298
|
+
return this.callList(
|
|
299
|
+
"Failed to list documents",
|
|
300
|
+
() => this.http.get(`/accounts/${id}/documents`, { params: cleanParams(params) })
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
/** Get document details. */
|
|
304
|
+
async details(documentId) {
|
|
305
|
+
const id = this.requireId(documentId, "Document ID");
|
|
306
|
+
return this.call("Failed to fetch document details", () => this.http.get(`/documents/${id}`));
|
|
307
|
+
}
|
|
308
|
+
/** Alias for {@link details}. */
|
|
309
|
+
async get(documentId) {
|
|
310
|
+
return this.details(documentId);
|
|
311
|
+
}
|
|
312
|
+
/** Poll document status until ready (or a terminal status / timeout). */
|
|
313
|
+
async waitUntilReady(documentId, options = {}) {
|
|
314
|
+
const id = this.requireId(documentId, "Document ID");
|
|
315
|
+
const maxWaitMs = options.maxWaitMs ?? 3e4;
|
|
316
|
+
const pollIntervalMs = options.pollIntervalMs ?? 2e3;
|
|
317
|
+
const start = Date.now();
|
|
318
|
+
let attempts = 0;
|
|
319
|
+
this.logger.info("Waiting for document to be ready", { documentId: id, maxWaitMs });
|
|
320
|
+
while (Date.now() - start < maxWaitMs) {
|
|
321
|
+
attempts++;
|
|
322
|
+
try {
|
|
323
|
+
const details = await this.details(id);
|
|
324
|
+
const status = details.status ?? "unknown";
|
|
325
|
+
this.logger.debug("Document status check", { attempts, status });
|
|
326
|
+
if (READY_STATUSES.has(status)) return details;
|
|
327
|
+
if (FAILED_STATUSES.has(status)) {
|
|
328
|
+
throw new ValidationError(`Document processing failed with status: ${status}`, {
|
|
329
|
+
status
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
} catch (err) {
|
|
333
|
+
if (err instanceof ValidationError) throw err;
|
|
334
|
+
this.logger.warn("Error checking document status", {
|
|
335
|
+
error: err instanceof Error ? err.message : String(err)
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
await sleep(pollIntervalMs);
|
|
339
|
+
}
|
|
340
|
+
throw new ValidationError("Timeout waiting for document to be ready", {
|
|
341
|
+
documentId: id,
|
|
342
|
+
attempts
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
/** Download a document artifact. Defaults to the certificated (signed) PDF. */
|
|
346
|
+
async download(documentId, artifactName = "certificated") {
|
|
347
|
+
const id = this.requireId(documentId, "Document ID");
|
|
348
|
+
return this.callBinary(
|
|
349
|
+
"Failed to download document",
|
|
350
|
+
() => this.http.get(`/documents/${id}/download/${artifactName}`, {
|
|
351
|
+
responseType: "arraybuffer"
|
|
352
|
+
})
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
/** Download the document thumbnail. */
|
|
356
|
+
async thumbnail(documentId) {
|
|
357
|
+
const id = this.requireId(documentId, "Document ID");
|
|
358
|
+
return this.callBinary(
|
|
359
|
+
"Failed to download document thumbnail",
|
|
360
|
+
() => this.http.get(`/documents/${id}/thumbnail`, { responseType: "arraybuffer" })
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
/** Download a single page as a JPEG. */
|
|
364
|
+
async downloadPage(documentId, pageId) {
|
|
365
|
+
const docId = this.requireId(documentId, "Document ID");
|
|
366
|
+
const pid = this.requireId(pageId, "Page ID");
|
|
367
|
+
return this.callBinary(
|
|
368
|
+
"Failed to download page",
|
|
369
|
+
() => this.http.get(`/documents/${docId}/pages/${pid}/download`, {
|
|
370
|
+
responseType: "arraybuffer"
|
|
371
|
+
})
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
/** Fetch the document activity log. */
|
|
375
|
+
async activities(documentId) {
|
|
376
|
+
const id = this.requireId(documentId, "Document ID");
|
|
377
|
+
const result = await this.call(
|
|
378
|
+
"Failed to fetch document activities",
|
|
379
|
+
() => this.http.get(`/documents/${id}/activities`)
|
|
380
|
+
);
|
|
381
|
+
return result ?? [];
|
|
382
|
+
}
|
|
383
|
+
/** Delete a document. */
|
|
384
|
+
async delete(documentId) {
|
|
385
|
+
const id = this.requireId(documentId, "Document ID");
|
|
386
|
+
return this.callVoid("Failed to delete document", () => this.http.delete(`/documents/${id}`));
|
|
387
|
+
}
|
|
388
|
+
/** List the tags attached to a document. */
|
|
389
|
+
async listTags(documentId, accountId) {
|
|
390
|
+
const accId = this.accountId(accountId);
|
|
391
|
+
const docId = this.requireId(documentId, "Document ID");
|
|
392
|
+
return this.call(
|
|
393
|
+
"Failed to list document tags",
|
|
394
|
+
() => this.http.get(`/accounts/${accId}/documents/${docId}/tags`)
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Replace the document's tag set with `tags` (an array of tag names).
|
|
399
|
+
* Unknown names are auto-created; an empty array detaches all tags.
|
|
400
|
+
*/
|
|
401
|
+
async replaceTags(documentId, tags, accountId) {
|
|
402
|
+
const accId = this.accountId(accountId);
|
|
403
|
+
const docId = this.requireId(documentId, "Document ID");
|
|
404
|
+
if (!Array.isArray(tags)) throw new ValidationError("tags must be an array of tag names");
|
|
405
|
+
return this.call(
|
|
406
|
+
"Failed to replace document tags",
|
|
407
|
+
() => this.http.put(`/accounts/${accId}/documents/${docId}/tags`, { tags })
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
/** Attach additional tags (by name) without removing existing ones. Idempotent. */
|
|
411
|
+
async addTags(documentId, tags, accountId) {
|
|
412
|
+
const accId = this.accountId(accountId);
|
|
413
|
+
const docId = this.requireId(documentId, "Document ID");
|
|
414
|
+
if (!Array.isArray(tags) || tags.length === 0) {
|
|
415
|
+
throw new ValidationError("tags must be a non-empty array of tag names");
|
|
416
|
+
}
|
|
417
|
+
return this.call(
|
|
418
|
+
"Failed to add document tags",
|
|
419
|
+
() => this.http.post(`/accounts/${accId}/documents/${docId}/tags`, { tags })
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
/** Detach a single tag from a document (the tag itself is not deleted). */
|
|
423
|
+
async detachTag(documentId, tagId, accountId) {
|
|
424
|
+
const accId = this.accountId(accountId);
|
|
425
|
+
const docId = this.requireId(documentId, "Document ID");
|
|
426
|
+
const tid = this.requireId(tagId, "Tag ID");
|
|
427
|
+
return this.callVoid(
|
|
428
|
+
"Failed to detach document tag",
|
|
429
|
+
() => this.http.delete(`/accounts/${accId}/documents/${docId}/tags/${tid}`)
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Create a document from a template.
|
|
434
|
+
*
|
|
435
|
+
* @example
|
|
436
|
+
* ```ts
|
|
437
|
+
* await client.documents.createFromTemplate('tmpl_id', [
|
|
438
|
+
* { role_id: 'role_id', id: 'signer_id', verification_method: 'Email', notification_methods: ['Email'] },
|
|
439
|
+
* ], { name: 'My Contract' });
|
|
440
|
+
* ```
|
|
441
|
+
*/
|
|
442
|
+
async createFromTemplate(templateId, signers, options = {}, accountId) {
|
|
443
|
+
const tmplId = this.requireId(templateId, "Template ID");
|
|
444
|
+
const accId = this.accountId(accountId);
|
|
445
|
+
const body = { signers, ...options };
|
|
446
|
+
this.logger.info("Creating document from template", { templateId: tmplId, accountId: accId });
|
|
447
|
+
return this.call(
|
|
448
|
+
"Failed to create document from template",
|
|
449
|
+
() => this.http.post(`/accounts/${accId}/templates/${tmplId}/documents`, body)
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
/** Estimate the credit cost of creating a document from a template. */
|
|
453
|
+
async estimateCostFromTemplate(templateId, signers, accountId) {
|
|
454
|
+
const tmplId = this.requireId(templateId, "Template ID");
|
|
455
|
+
const accId = this.accountId(accountId);
|
|
456
|
+
return this.call(
|
|
457
|
+
"Failed to estimate cost from template",
|
|
458
|
+
() => this.http.post(`/accounts/${accId}/templates/${tmplId}/documents/estimate-cost`, { signers })
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
/** Verify a document by its signature hash. */
|
|
462
|
+
async verify(hash) {
|
|
463
|
+
const h = this.requireId(hash, "Signature hash");
|
|
464
|
+
return this.call("Failed to verify document", () => this.http.get(`/documents/${h}/verify`));
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* `GET /documents/statuses` — list every possible document status with
|
|
468
|
+
* its description and whether documents in that status can be deleted.
|
|
469
|
+
*/
|
|
470
|
+
async statuses() {
|
|
471
|
+
return this.call(
|
|
472
|
+
"Failed to list document statuses",
|
|
473
|
+
() => this.http.get("/documents/statuses")
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* `GET /public/documents/{document_id}` — public, unauthenticated lookup of
|
|
478
|
+
* basic document info (used by the signing portal before the signer
|
|
479
|
+
* authenticates via the access code).
|
|
480
|
+
*/
|
|
481
|
+
async getPublic(documentId) {
|
|
482
|
+
const id = this.requireId(documentId, "Document ID");
|
|
483
|
+
return this.call(
|
|
484
|
+
"Failed to fetch public document info",
|
|
485
|
+
() => this.http.get(`/public/documents/${id}`)
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* `PUT /public/documents/{document_id}/send-token` — send the 6-digit
|
|
490
|
+
* verification token to the signer's email / WhatsApp.
|
|
491
|
+
*/
|
|
492
|
+
async sendToken(documentId, recipient, channel = "email") {
|
|
493
|
+
const id = this.requireId(documentId, "Document ID");
|
|
494
|
+
if (!recipient) throw new ValidationError("recipient is required");
|
|
495
|
+
return this.call(
|
|
496
|
+
"Failed to send signing token",
|
|
497
|
+
() => this.http.put(`/public/documents/${id}/send-token`, { recipient, channel })
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
/** Quick check: has every signer completed their assignment? */
|
|
501
|
+
async isFullySigned(documentId) {
|
|
502
|
+
const details = await this.details(documentId);
|
|
503
|
+
if (details.status === "certificated") return true;
|
|
504
|
+
const summary = details.assignment?.summary;
|
|
505
|
+
if (summary && typeof summary.signer_count === "number") {
|
|
506
|
+
return summary.signer_count > 0 && summary.signer_count === summary.completed_count;
|
|
507
|
+
}
|
|
508
|
+
return false;
|
|
509
|
+
}
|
|
510
|
+
/** Summarise signing progress for UI display. */
|
|
511
|
+
async getSigningProgress(documentId) {
|
|
512
|
+
const details = await this.details(documentId);
|
|
513
|
+
const summary = details.assignment?.summary;
|
|
514
|
+
const total = summary?.signer_count ?? details.assignment?.signers?.length ?? 0;
|
|
515
|
+
const signed = summary?.completed_count ?? 0;
|
|
516
|
+
const pending = Math.max(total - signed, 0);
|
|
517
|
+
const percentage = total > 0 ? Math.round(signed / total * 1e4) / 100 : 0;
|
|
518
|
+
return { signed, total, pending, percentage };
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
async function loadSource(source) {
|
|
522
|
+
if ("buffer" in source) {
|
|
523
|
+
if (!source.fileName) {
|
|
524
|
+
throw new ValidationError("fileName is required when uploading a Buffer");
|
|
525
|
+
}
|
|
526
|
+
return { buffer: source.buffer, fileName: source.fileName };
|
|
527
|
+
}
|
|
528
|
+
if (!source.filePath) {
|
|
529
|
+
throw new ValidationError("filePath is required");
|
|
530
|
+
}
|
|
531
|
+
const buffer = await import_node_fs.promises.readFile(source.filePath);
|
|
532
|
+
return { buffer, fileName: source.fileName ?? import_node_path.default.basename(source.filePath) };
|
|
533
|
+
}
|
|
534
|
+
function validateUpload(buffer, fileName) {
|
|
535
|
+
if (!buffer || buffer.byteLength === 0) {
|
|
536
|
+
throw new ValidationError("File buffer is empty", { fileName });
|
|
537
|
+
}
|
|
538
|
+
if (!fileName.toLowerCase().endsWith(".pdf")) {
|
|
539
|
+
throw new ValidationError("Only PDF files are supported", { fileName });
|
|
540
|
+
}
|
|
541
|
+
if (buffer.byteLength > MAX_UPLOAD_BYTES) {
|
|
542
|
+
throw new ValidationError("File size exceeds maximum allowed (25MB)", {
|
|
543
|
+
fileSize: buffer.byteLength,
|
|
544
|
+
maxSize: MAX_UPLOAD_BYTES
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
function buildUploadForm(buffer, fileName, metadata) {
|
|
549
|
+
const form = new FormData();
|
|
550
|
+
const view = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
551
|
+
form.append("file", new Blob([view], { type: "application/pdf" }), fileName);
|
|
552
|
+
form.append("name", fileName);
|
|
553
|
+
if (metadata) {
|
|
554
|
+
form.append("metadata", JSON.stringify(metadata));
|
|
555
|
+
}
|
|
556
|
+
return form;
|
|
557
|
+
}
|
|
558
|
+
function sleep(ms) {
|
|
559
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// src/resources/signers.ts
|
|
563
|
+
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
564
|
+
var SignerResource = class extends BaseResource {
|
|
565
|
+
/**
|
|
566
|
+
* Create a signer in the workspace.
|
|
567
|
+
*
|
|
568
|
+
* `email` is optional — the API also accepts whatsapp-only signers — but at
|
|
569
|
+
* least one of `email` / `whatsapp_phone_number` (or the `phone` alias) is
|
|
570
|
+
* required. When an `email` is supplied the call is idempotent by email:
|
|
571
|
+
* an existing signer with that address is reused instead of duplicated.
|
|
572
|
+
*/
|
|
573
|
+
async create(payload, accountId) {
|
|
574
|
+
const id = this.accountId(accountId);
|
|
575
|
+
const phone = payload.whatsapp_phone_number ?? payload.phone;
|
|
576
|
+
if (!payload.email && !phone) {
|
|
577
|
+
throw new ValidationError(
|
|
578
|
+
"A signer requires at least an email or a whatsapp_phone_number"
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
if (payload.email) this.assertEmail(payload.email);
|
|
582
|
+
if (payload.email) {
|
|
583
|
+
const existing = await this.findByEmail(payload.email, id);
|
|
584
|
+
if (existing) {
|
|
585
|
+
this.logger.info("Using existing signer", { email: payload.email });
|
|
586
|
+
return existing;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
this.logger.info("Creating signer", { email: payload.email });
|
|
590
|
+
try {
|
|
591
|
+
return await this.call(
|
|
592
|
+
"Failed to create signer",
|
|
593
|
+
() => this.http.post(`/accounts/${id}/signers`, normaliseSignerPayload(payload))
|
|
594
|
+
);
|
|
595
|
+
} catch (err) {
|
|
596
|
+
if (err instanceof ApiError && err.statusCode === 409 && payload.email) {
|
|
597
|
+
const duplicate = await this.findByEmail(payload.email, id);
|
|
598
|
+
if (duplicate) {
|
|
599
|
+
this.logger.info("Signer already exists, using existing signer", {
|
|
600
|
+
email: payload.email
|
|
601
|
+
});
|
|
602
|
+
return duplicate;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
throw err;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
/** Get a signer by ID. */
|
|
609
|
+
async get(signerId, accountId) {
|
|
610
|
+
const id = this.accountId(accountId);
|
|
611
|
+
const sid = this.requireId(signerId, "Signer ID");
|
|
612
|
+
return this.call(
|
|
613
|
+
"Failed to fetch signer",
|
|
614
|
+
() => this.http.get(`/accounts/${id}/signers/${sid}`)
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
/** List signers for the workspace (supports `page`, `per_page`, `search`, `sort`). */
|
|
618
|
+
async list(params = {}, accountId) {
|
|
619
|
+
const id = this.accountId(accountId);
|
|
620
|
+
return this.callList(
|
|
621
|
+
"Failed to list signers",
|
|
622
|
+
() => this.http.get(`/accounts/${id}/signers`, { params: cleanParams(params) })
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
/** Update a signer. Fails if the signer has active assignments. */
|
|
626
|
+
async update(signerId, payload, accountId) {
|
|
627
|
+
const id = this.accountId(accountId);
|
|
628
|
+
const sid = this.requireId(signerId, "Signer ID");
|
|
629
|
+
return this.call(
|
|
630
|
+
"Failed to update signer",
|
|
631
|
+
() => this.http.put(`/accounts/${id}/signers/${sid}`, normaliseSignerPayload(payload))
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
/** Delete a signer. */
|
|
635
|
+
async delete(signerId, accountId) {
|
|
636
|
+
const id = this.accountId(accountId);
|
|
637
|
+
const sid = this.requireId(signerId, "Signer ID");
|
|
638
|
+
return this.callVoid(
|
|
639
|
+
"Failed to delete signer",
|
|
640
|
+
() => this.http.delete(`/accounts/${id}/signers/${sid}`)
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
/** Find a signer by email via the API's `search` parameter. Returns `null` if none match. */
|
|
644
|
+
async findByEmail(email, accountId) {
|
|
645
|
+
this.assertEmail(email);
|
|
646
|
+
try {
|
|
647
|
+
const { data } = await this.list({ search: email, per_page: 100 }, accountId);
|
|
648
|
+
const lower = email.toLowerCase();
|
|
649
|
+
return data.find((s) => (s.email ?? "").toLowerCase() === lower) ?? null;
|
|
650
|
+
} catch (err) {
|
|
651
|
+
if (err instanceof ApiError && err.statusCode === 404) {
|
|
652
|
+
return null;
|
|
653
|
+
}
|
|
654
|
+
throw err;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
assertEmail(email) {
|
|
658
|
+
if (!email || !EMAIL_RE.test(email)) {
|
|
659
|
+
throw new ValidationError("Invalid email address", { email });
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
};
|
|
663
|
+
function normaliseSignerPayload(payload) {
|
|
664
|
+
const normalised = {
|
|
665
|
+
full_name: payload.full_name,
|
|
666
|
+
email: payload.email,
|
|
667
|
+
whatsapp_phone_number: payload.whatsapp_phone_number ?? payload.phone
|
|
668
|
+
};
|
|
669
|
+
if (payload.cpf) {
|
|
670
|
+
normalised["cpf"] = payload.cpf.replace(/\D/g, "");
|
|
671
|
+
}
|
|
672
|
+
if ("metadata" in payload && payload.metadata !== void 0) {
|
|
673
|
+
normalised["metadata"] = payload.metadata;
|
|
674
|
+
}
|
|
675
|
+
return cleanParams(normalised);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// src/resources/workspaces.ts
|
|
679
|
+
var WorkspaceResource = class extends BaseResource {
|
|
680
|
+
/** Create a new workspace. */
|
|
681
|
+
async create(payload) {
|
|
682
|
+
return this.call("Failed to create workspace", () => this.http.post("/accounts", payload));
|
|
683
|
+
}
|
|
684
|
+
/** List workspaces the authenticated user can access. */
|
|
685
|
+
async list() {
|
|
686
|
+
return this.callList(
|
|
687
|
+
"Failed to list workspaces",
|
|
688
|
+
() => this.http.get("/accounts")
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
/** Fetch a single workspace. */
|
|
692
|
+
async get(accountId) {
|
|
693
|
+
const id = this.requireId(accountId, "Account ID");
|
|
694
|
+
return this.call("Failed to fetch workspace", () => this.http.get(`/accounts/${id}`));
|
|
695
|
+
}
|
|
696
|
+
/** Update a workspace. */
|
|
697
|
+
async update(accountId, payload) {
|
|
698
|
+
const id = this.requireId(accountId, "Account ID");
|
|
699
|
+
return this.call("Failed to update workspace", () => this.http.put(`/accounts/${id}`, payload));
|
|
700
|
+
}
|
|
701
|
+
/** Delete a workspace. */
|
|
702
|
+
async delete(accountId) {
|
|
703
|
+
const id = this.requireId(accountId, "Account ID");
|
|
704
|
+
return this.callVoid("Failed to delete workspace", () => this.http.delete(`/accounts/${id}`));
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
// src/resources/assignments.ts
|
|
709
|
+
function buildAssignmentPayload(payload, options = {}) {
|
|
710
|
+
const signers = extractSignerRefs(payload);
|
|
711
|
+
if (signers.length === 0) {
|
|
712
|
+
throw new ValidationError("At least one signer is required", {
|
|
713
|
+
signers: payload.signers ?? payload.signer_ids ?? payload.signerIds
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
return cleanParams({
|
|
717
|
+
method: payload.method ?? "virtual",
|
|
718
|
+
signers: signers.map((ref) => normaliseSignerRef(ref, options)),
|
|
719
|
+
message: payload.message,
|
|
720
|
+
expires_at: payload.expires_at,
|
|
721
|
+
copy_receivers: payload.copy_receivers,
|
|
722
|
+
entries: payload.entries
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
function extractSignerRefs(payload) {
|
|
726
|
+
if (Array.isArray(payload.signers) && payload.signers.length > 0) {
|
|
727
|
+
return payload.signers;
|
|
728
|
+
}
|
|
729
|
+
const legacy = payload.signer_ids ?? payload.signerIds;
|
|
730
|
+
return Array.isArray(legacy) ? legacy : [];
|
|
731
|
+
}
|
|
732
|
+
function normaliseSignerRef(ref, options) {
|
|
733
|
+
if (typeof ref === "string") {
|
|
734
|
+
if (!ref) throw new ValidationError("Signer ID cannot be empty");
|
|
735
|
+
return { id: ref };
|
|
736
|
+
}
|
|
737
|
+
if (ref && typeof ref === "object") {
|
|
738
|
+
const input = ref;
|
|
739
|
+
const id = input.id ?? input.signer_id;
|
|
740
|
+
const normalised = cleanParams({
|
|
741
|
+
id,
|
|
742
|
+
verification_method: input.verification_method,
|
|
743
|
+
notification_methods: input.notification_methods,
|
|
744
|
+
step: input.step
|
|
745
|
+
});
|
|
746
|
+
if (typeof id === "string" && id.length > 0) {
|
|
747
|
+
return normalised;
|
|
748
|
+
}
|
|
749
|
+
if (options.allowSignersWithoutId && Object.keys(normalised).length > 0) {
|
|
750
|
+
return normalised;
|
|
751
|
+
}
|
|
752
|
+
if (options.allowSignersWithoutId && Object.keys(normalised).length === 0) {
|
|
753
|
+
return {};
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
throw new ValidationError("Invalid signer reference", { ref });
|
|
757
|
+
}
|
|
758
|
+
var AssignmentResource = class extends BaseResource {
|
|
759
|
+
/** Create a signing assignment for a document. */
|
|
760
|
+
async create(documentId, payload) {
|
|
761
|
+
const docId = this.requireId(documentId, "Document ID");
|
|
762
|
+
const signers = extractSignerRefs(payload);
|
|
763
|
+
const body = buildAssignmentPayload(payload);
|
|
764
|
+
this.logger.info("Creating assignment", {
|
|
765
|
+
documentId: docId,
|
|
766
|
+
signers: signers.length
|
|
767
|
+
});
|
|
768
|
+
return this.call(
|
|
769
|
+
"Failed to create assignment",
|
|
770
|
+
() => this.http.post(`/documents/${docId}/assignments`, body)
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
/** Estimate the cost (in credits) of creating the assignment. */
|
|
774
|
+
async estimateCost(documentId, payload) {
|
|
775
|
+
const docId = this.requireId(documentId, "Document ID");
|
|
776
|
+
return this.call(
|
|
777
|
+
"Failed to estimate assignment cost",
|
|
778
|
+
() => this.http.post(
|
|
779
|
+
`/documents/${docId}/assignments/estimate-cost`,
|
|
780
|
+
buildAssignmentPayload(payload, { allowSignersWithoutId: true })
|
|
781
|
+
)
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Update the expiration date of an existing assignment.
|
|
786
|
+
* Pass `null` to remove the expiration entirely.
|
|
787
|
+
*/
|
|
788
|
+
async resetExpiration(documentId, assignmentId, expiresAt) {
|
|
789
|
+
const docId = this.requireId(documentId, "Document ID");
|
|
790
|
+
const asgId = this.requireId(assignmentId, "Assignment ID");
|
|
791
|
+
return this.call(
|
|
792
|
+
"Failed to update assignment expiration",
|
|
793
|
+
() => this.http.put(`/documents/${docId}/assignments/${asgId}/reset-expiration`, {
|
|
794
|
+
expires_at: expiresAt
|
|
795
|
+
})
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
/** Resend the signing notification to a single signer. */
|
|
799
|
+
async resendNotification(documentId, assignmentId, signerId) {
|
|
800
|
+
const docId = this.requireId(documentId, "Document ID");
|
|
801
|
+
const asgId = this.requireId(assignmentId, "Assignment ID");
|
|
802
|
+
const sid = this.requireId(signerId, "Signer ID");
|
|
803
|
+
return this.call(
|
|
804
|
+
"Failed to resend signer notification",
|
|
805
|
+
() => this.http.put(`/documents/${docId}/assignments/${asgId}/signers/${sid}/resend`)
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
/** Estimate the cost of resending a signer notification. */
|
|
809
|
+
async estimateResendCost(documentId, assignmentId, signerId) {
|
|
810
|
+
const docId = this.requireId(documentId, "Document ID");
|
|
811
|
+
const asgId = this.requireId(assignmentId, "Assignment ID");
|
|
812
|
+
const sid = this.requireId(signerId, "Signer ID");
|
|
813
|
+
return this.call(
|
|
814
|
+
"Failed to estimate resend cost",
|
|
815
|
+
() => this.http.post(
|
|
816
|
+
`/documents/${docId}/assignments/${asgId}/signers/${sid}/estimate-resend-cost`
|
|
817
|
+
)
|
|
818
|
+
);
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* `GET /documents/{documentId}/assignments/{assignmentId}/whatsapp-notifications`
|
|
822
|
+
* — list every WhatsApp notification rendered + sent for an assignment.
|
|
823
|
+
*/
|
|
824
|
+
async listWhatsAppNotifications(documentId, assignmentId) {
|
|
825
|
+
const docId = this.requireId(documentId, "Document ID");
|
|
826
|
+
const asgId = this.requireId(assignmentId, "Assignment ID");
|
|
827
|
+
return this.call(
|
|
828
|
+
"Failed to list WhatsApp notifications",
|
|
829
|
+
() => this.http.get(`/documents/${docId}/assignments/${asgId}/whatsapp-notifications`)
|
|
830
|
+
);
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Cancel a signature request. This endpoint is not listed in the public
|
|
834
|
+
* Swagger but is exposed by the platform.
|
|
835
|
+
*/
|
|
836
|
+
async cancel(documentId, reason, accountId) {
|
|
837
|
+
const docId = this.requireId(documentId, "Document ID");
|
|
838
|
+
const accId = this.accountId(accountId);
|
|
839
|
+
this.logger.info("Cancelling signature request", { documentId: docId, reason });
|
|
840
|
+
return this.call(
|
|
841
|
+
"Failed to cancel signature request",
|
|
842
|
+
() => this.http.post(
|
|
843
|
+
`/accounts/${accId}/signature-requests/${docId}/cancel`,
|
|
844
|
+
{ document_id: docId, reason }
|
|
845
|
+
)
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
// src/resources/webhooks.ts
|
|
851
|
+
var DEFAULT_EVENTS = [
|
|
852
|
+
"document_ready",
|
|
853
|
+
"document_prepared",
|
|
854
|
+
"signer_signed_document",
|
|
855
|
+
"signer_rejected_document",
|
|
856
|
+
"document_processing_failed"
|
|
857
|
+
];
|
|
858
|
+
var WebhookResource = class extends BaseResource {
|
|
859
|
+
/** Register (or replace) the webhook subscription for the workspace. */
|
|
860
|
+
async register(payload, accountId) {
|
|
861
|
+
if (!payload.url) throw new ValidationError("Webhook URL is required");
|
|
862
|
+
if (!payload.email) throw new ValidationError("Webhook email is required");
|
|
863
|
+
const id = this.accountId(accountId);
|
|
864
|
+
const body = {
|
|
865
|
+
url: payload.url,
|
|
866
|
+
email: payload.email,
|
|
867
|
+
events: payload.events && payload.events.length > 0 ? payload.events : DEFAULT_EVENTS,
|
|
868
|
+
is_active: payload.is_active ?? true
|
|
869
|
+
};
|
|
870
|
+
this.logger.info("Registering webhook", { url: payload.url });
|
|
871
|
+
return this.call(
|
|
872
|
+
"Failed to register webhook",
|
|
873
|
+
() => this.http.put(`/accounts/${id}/webhooks/subscriptions`, body)
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
/** Fetch the current webhook subscription. Returns `null` if none exists. */
|
|
877
|
+
async get(accountId) {
|
|
878
|
+
const id = this.accountId(accountId);
|
|
879
|
+
return this.callOptional(
|
|
880
|
+
"Failed to fetch webhook subscription",
|
|
881
|
+
() => this.http.get(`/accounts/${id}/webhooks/subscriptions`)
|
|
882
|
+
);
|
|
883
|
+
}
|
|
884
|
+
/** Delete the current webhook subscription. */
|
|
885
|
+
async delete(accountId) {
|
|
886
|
+
const id = this.accountId(accountId);
|
|
887
|
+
this.logger.info("Deleting webhook subscription");
|
|
888
|
+
return this.callVoid(
|
|
889
|
+
"Failed to delete webhook subscription",
|
|
890
|
+
() => this.http.delete(`/accounts/${id}/webhooks/subscriptions`)
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
/** Inactivate the current webhook subscription without deleting it. */
|
|
894
|
+
async inactivate(accountId) {
|
|
895
|
+
const id = this.accountId(accountId);
|
|
896
|
+
this.logger.info("Inactivating webhook subscription");
|
|
897
|
+
return this.call(
|
|
898
|
+
"Failed to inactivate webhook subscription",
|
|
899
|
+
() => this.http.put(`/accounts/${id}/webhooks/inactivate`)
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
/** List currently supported webhook event types. */
|
|
903
|
+
async listEventTypes() {
|
|
904
|
+
return this.call(
|
|
905
|
+
"Failed to list webhook event types",
|
|
906
|
+
() => this.http.get("/webhooks/event-types")
|
|
907
|
+
);
|
|
908
|
+
}
|
|
909
|
+
/** List webhook delivery history for the workspace. */
|
|
910
|
+
async listDispatches(params = {}, accountId) {
|
|
911
|
+
const id = this.accountId(accountId);
|
|
912
|
+
return this.callList(
|
|
913
|
+
"Failed to list webhook dispatches",
|
|
914
|
+
() => this.http.get(`/accounts/${id}/webhooks`, {
|
|
915
|
+
params: cleanParams(params)
|
|
916
|
+
})
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
/** Retry delivery of a specific webhook dispatch. */
|
|
920
|
+
async retryDispatch(dispatchId, accountId) {
|
|
921
|
+
const id = this.accountId(accountId);
|
|
922
|
+
const did = this.requireId(dispatchId, "Dispatch ID");
|
|
923
|
+
return this.call(
|
|
924
|
+
"Failed to retry webhook dispatch",
|
|
925
|
+
() => this.http.post(`/accounts/${id}/webhooks/${did}/retry`)
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
};
|
|
929
|
+
|
|
930
|
+
// src/resources/templates.ts
|
|
931
|
+
var TemplateResource = class extends BaseResource {
|
|
932
|
+
/** List templates for the workspace. */
|
|
933
|
+
async list(params = {}, accountId) {
|
|
934
|
+
const id = this.accountId(accountId);
|
|
935
|
+
return this.callList(
|
|
936
|
+
"Failed to list templates",
|
|
937
|
+
() => this.http.get(`/accounts/${id}/templates`, { params: cleanParams(params) })
|
|
938
|
+
);
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Get a template by ID.
|
|
942
|
+
*
|
|
943
|
+
* Note: the swagger only documents the list endpoint; this single-resource
|
|
944
|
+
* `GET /accounts/{id}/templates/{id}` is exposed by the platform and used
|
|
945
|
+
* by the official PHP SDK.
|
|
946
|
+
*/
|
|
947
|
+
async get(templateId, accountId) {
|
|
948
|
+
const id = this.accountId(accountId);
|
|
949
|
+
const tmplId = this.requireId(templateId, "Template ID");
|
|
950
|
+
return this.call(
|
|
951
|
+
"Failed to fetch template",
|
|
952
|
+
() => this.http.get(`/accounts/${id}/templates/${tmplId}`)
|
|
953
|
+
);
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* `GET /accounts/{id}/templates/{template_id}/pages/{page_id}/download` —
|
|
957
|
+
* download a template page as a JPEG (used by template editors to render
|
|
958
|
+
* thumbnails on the client).
|
|
959
|
+
*/
|
|
960
|
+
async downloadPage(templateId, pageId, accountId) {
|
|
961
|
+
const id = this.accountId(accountId);
|
|
962
|
+
const tmplId = this.requireId(templateId, "Template ID");
|
|
963
|
+
const pid = this.requireId(pageId, "Page ID");
|
|
964
|
+
return this.callBinary(
|
|
965
|
+
"Failed to download template page",
|
|
966
|
+
() => this.http.get(
|
|
967
|
+
`/accounts/${id}/templates/${tmplId}/pages/${pid}/download`,
|
|
968
|
+
{ responseType: "arraybuffer" }
|
|
969
|
+
)
|
|
970
|
+
);
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
|
|
974
|
+
// src/resources/tags.ts
|
|
975
|
+
var TagResource = class extends BaseResource {
|
|
976
|
+
/** List the workspace's tags, ordered alphabetically. Optional case-insensitive `search`. */
|
|
977
|
+
async list(params = {}, accountId) {
|
|
978
|
+
const id = this.accountId(accountId);
|
|
979
|
+
return this.call(
|
|
980
|
+
"Failed to list tags",
|
|
981
|
+
() => this.http.get(`/accounts/${id}/tags`, {
|
|
982
|
+
params: cleanParams(params)
|
|
983
|
+
})
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
/** Create a tag. Throws `ApiError` (409) if the name already exists (case-insensitive). */
|
|
987
|
+
async create(payload, accountId) {
|
|
988
|
+
if (!payload.name) throw new ValidationError("Tag name is required");
|
|
989
|
+
const id = this.accountId(accountId);
|
|
990
|
+
return this.call(
|
|
991
|
+
"Failed to create tag",
|
|
992
|
+
() => this.http.post(`/accounts/${id}/tags`, cleanParams({ ...payload }))
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* Update a tag's name and/or color. Omit a field to leave it unchanged;
|
|
997
|
+
* pass `color: null` to clear the color. Throws `ApiError` (409) if another
|
|
998
|
+
* tag already uses the new name.
|
|
999
|
+
*/
|
|
1000
|
+
async update(tagId, payload, accountId) {
|
|
1001
|
+
const id = this.accountId(accountId);
|
|
1002
|
+
const tid = this.requireId(tagId, "Tag ID");
|
|
1003
|
+
const body = {};
|
|
1004
|
+
if (payload.name !== void 0) body["name"] = payload.name;
|
|
1005
|
+
if ("color" in payload) body["color"] = payload.color;
|
|
1006
|
+
return this.call(
|
|
1007
|
+
"Failed to update tag",
|
|
1008
|
+
() => this.http.put(`/accounts/${id}/tags/${tid}`, body)
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Delete a tag. By default fails with `ApiError` (409) if the tag is still
|
|
1013
|
+
* attached to anything; pass `{ force: true }` to detach everywhere first.
|
|
1014
|
+
*/
|
|
1015
|
+
async delete(tagId, options = {}) {
|
|
1016
|
+
const id = this.accountId(options.accountId);
|
|
1017
|
+
const tid = this.requireId(tagId, "Tag ID");
|
|
1018
|
+
const params = options.force ? { force: "true" } : void 0;
|
|
1019
|
+
return this.callVoid(
|
|
1020
|
+
"Failed to delete tag",
|
|
1021
|
+
() => this.http.delete(`/accounts/${id}/tags/${tid}`, { params })
|
|
1022
|
+
);
|
|
1023
|
+
}
|
|
1024
|
+
};
|
|
1025
|
+
|
|
1026
|
+
// src/resources/authentication.ts
|
|
1027
|
+
var AuthenticationResource = class extends BaseResource {
|
|
1028
|
+
/** `POST /login` — exchange email + password for a JWT access token. */
|
|
1029
|
+
async login(email, password) {
|
|
1030
|
+
if (!email) throw new ValidationError("email is required");
|
|
1031
|
+
if (!password) throw new ValidationError("password is required");
|
|
1032
|
+
return this.call(
|
|
1033
|
+
"Login failed",
|
|
1034
|
+
() => this.http.post("/login", { email, password })
|
|
1035
|
+
);
|
|
1036
|
+
}
|
|
1037
|
+
/** `POST /authentication/social-login` — exchange a provider token for an Assinafy JWT. */
|
|
1038
|
+
async socialLogin(payload) {
|
|
1039
|
+
if (!payload.provider) throw new ValidationError("provider is required");
|
|
1040
|
+
if (!payload.token) throw new ValidationError("token is required");
|
|
1041
|
+
return this.call(
|
|
1042
|
+
"Social login failed",
|
|
1043
|
+
() => this.http.post("/authentication/social-login", payload)
|
|
1044
|
+
);
|
|
1045
|
+
}
|
|
1046
|
+
/** `POST /users/api-keys` — generate (and rotate) the current user's API key. */
|
|
1047
|
+
async createApiKey(password) {
|
|
1048
|
+
if (!password) throw new ValidationError("password is required");
|
|
1049
|
+
return this.call(
|
|
1050
|
+
"Failed to create API key",
|
|
1051
|
+
() => this.http.post("/users/api-keys", { password })
|
|
1052
|
+
);
|
|
1053
|
+
}
|
|
1054
|
+
/**
|
|
1055
|
+
* `GET /users/api-keys` — fetch a masked version of the current API key, or
|
|
1056
|
+
* `null` if no key has been generated yet.
|
|
1057
|
+
*/
|
|
1058
|
+
async getApiKey() {
|
|
1059
|
+
const result = await this.call(
|
|
1060
|
+
"Failed to fetch API key",
|
|
1061
|
+
() => this.http.get("/users/api-keys")
|
|
1062
|
+
);
|
|
1063
|
+
return result ?? null;
|
|
1064
|
+
}
|
|
1065
|
+
/** `DELETE /users/api-keys` — revoke the current API key. */
|
|
1066
|
+
async deleteApiKey() {
|
|
1067
|
+
return this.callVoid(
|
|
1068
|
+
"Failed to delete API key",
|
|
1069
|
+
() => this.http.delete("/users/api-keys")
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
/** `PUT /authentication/change-password` — change the authenticated user's password. */
|
|
1073
|
+
async changePassword(payload) {
|
|
1074
|
+
if (!payload.email) throw new ValidationError("email is required");
|
|
1075
|
+
if (!payload.password) throw new ValidationError("password is required");
|
|
1076
|
+
if (!payload.new_password) throw new ValidationError("new_password is required");
|
|
1077
|
+
return this.call(
|
|
1078
|
+
"Failed to change password",
|
|
1079
|
+
() => this.http.put("/authentication/change-password", payload)
|
|
1080
|
+
);
|
|
1081
|
+
}
|
|
1082
|
+
/** `PUT /authentication/request-password-reset` — email a reset link to the user. */
|
|
1083
|
+
async requestPasswordReset(email) {
|
|
1084
|
+
if (!email) throw new ValidationError("email is required");
|
|
1085
|
+
return this.call(
|
|
1086
|
+
"Failed to request password reset",
|
|
1087
|
+
() => this.http.put("/authentication/request-password-reset", { email })
|
|
1088
|
+
);
|
|
1089
|
+
}
|
|
1090
|
+
/** `PUT /authentication/reset-password` — complete a password reset using the emailed token. */
|
|
1091
|
+
async resetPassword(payload) {
|
|
1092
|
+
if (!payload.email) throw new ValidationError("email is required");
|
|
1093
|
+
if (!payload.new_password) throw new ValidationError("new_password is required");
|
|
1094
|
+
return this.call(
|
|
1095
|
+
"Failed to reset password",
|
|
1096
|
+
() => this.http.put("/authentication/reset-password", payload)
|
|
1097
|
+
);
|
|
1098
|
+
}
|
|
1099
|
+
};
|
|
1100
|
+
|
|
1101
|
+
// src/resources/fields.ts
|
|
1102
|
+
var FieldsResource = class extends BaseResource {
|
|
1103
|
+
/** Create a field definition. */
|
|
1104
|
+
async create(payload, accountId) {
|
|
1105
|
+
if (!payload.type) throw new ValidationError("field type is required");
|
|
1106
|
+
if (!payload.name) throw new ValidationError("field name is required");
|
|
1107
|
+
const id = this.accountId(accountId);
|
|
1108
|
+
return this.call(
|
|
1109
|
+
"Failed to create field definition",
|
|
1110
|
+
() => this.http.post(`/accounts/${id}/fields`, payload)
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
/**
|
|
1114
|
+
* List field definitions for the workspace.
|
|
1115
|
+
*
|
|
1116
|
+
* @param params.include_inactive return inactive fields too
|
|
1117
|
+
* @param params.include_standard also return `signature`, `initial`, `signatureDate`
|
|
1118
|
+
*/
|
|
1119
|
+
async list(params = {}, accountId) {
|
|
1120
|
+
const id = this.accountId(accountId);
|
|
1121
|
+
return this.call(
|
|
1122
|
+
"Failed to list field definitions",
|
|
1123
|
+
() => this.http.get(`/accounts/${id}/fields`, {
|
|
1124
|
+
params: cleanParams(params)
|
|
1125
|
+
})
|
|
1126
|
+
);
|
|
1127
|
+
}
|
|
1128
|
+
/** Get a single field definition by ID. */
|
|
1129
|
+
async get(fieldId, accountId) {
|
|
1130
|
+
const id = this.accountId(accountId);
|
|
1131
|
+
const fid = this.requireId(fieldId, "Field ID");
|
|
1132
|
+
return this.call(
|
|
1133
|
+
"Failed to fetch field definition",
|
|
1134
|
+
() => this.http.get(`/accounts/${id}/fields/${fid}`)
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1137
|
+
/** Update a field definition. */
|
|
1138
|
+
async update(fieldId, payload, accountId) {
|
|
1139
|
+
const id = this.accountId(accountId);
|
|
1140
|
+
const fid = this.requireId(fieldId, "Field ID");
|
|
1141
|
+
return this.call(
|
|
1142
|
+
"Failed to update field definition",
|
|
1143
|
+
() => this.http.put(`/accounts/${id}/fields/${fid}`, payload)
|
|
1144
|
+
);
|
|
1145
|
+
}
|
|
1146
|
+
/** Delete a field definition. Fails if the field has been used. */
|
|
1147
|
+
async delete(fieldId, accountId) {
|
|
1148
|
+
const id = this.accountId(accountId);
|
|
1149
|
+
const fid = this.requireId(fieldId, "Field ID");
|
|
1150
|
+
return this.callVoid(
|
|
1151
|
+
"Failed to delete field definition",
|
|
1152
|
+
() => this.http.delete(`/accounts/${id}/fields/${fid}`)
|
|
1153
|
+
);
|
|
1154
|
+
}
|
|
1155
|
+
/**
|
|
1156
|
+
* Validate a single value against a field definition.
|
|
1157
|
+
*
|
|
1158
|
+
* Pass `signerAccessCode` for signer-side validation (the typical use case);
|
|
1159
|
+
* omit it when the caller is authenticated via API key.
|
|
1160
|
+
*/
|
|
1161
|
+
async validate(fieldId, value, options = {}) {
|
|
1162
|
+
const id = this.accountId(options.accountId);
|
|
1163
|
+
const fid = this.requireId(fieldId, "Field ID");
|
|
1164
|
+
const params = options.signerAccessCode ? { "signer-access-code": options.signerAccessCode } : void 0;
|
|
1165
|
+
return this.call(
|
|
1166
|
+
"Failed to validate field value",
|
|
1167
|
+
() => this.http.post(`/accounts/${id}/fields/${fid}/validate`, { value }, { params })
|
|
1168
|
+
);
|
|
1169
|
+
}
|
|
1170
|
+
/** Validate multiple values at once. */
|
|
1171
|
+
async validateMultiple(entries, options = {}) {
|
|
1172
|
+
if (!Array.isArray(entries) || entries.length === 0) {
|
|
1173
|
+
throw new ValidationError("entries must be a non-empty array");
|
|
1174
|
+
}
|
|
1175
|
+
const id = this.accountId(options.accountId);
|
|
1176
|
+
const params = options.signerAccessCode ? { "signer-access-code": options.signerAccessCode } : void 0;
|
|
1177
|
+
return this.call(
|
|
1178
|
+
"Failed to validate field values",
|
|
1179
|
+
() => this.http.post(`/accounts/${id}/fields/validate-multiple`, entries, { params })
|
|
1180
|
+
);
|
|
1181
|
+
}
|
|
1182
|
+
/** List the platform's supported field types. */
|
|
1183
|
+
async listTypes() {
|
|
1184
|
+
return this.call("Failed to list field types", () => this.http.get("/field-types"));
|
|
1185
|
+
}
|
|
1186
|
+
};
|
|
1187
|
+
|
|
1188
|
+
// src/resources/signer-documents.ts
|
|
1189
|
+
var SignerDocumentsResource = class extends BaseResource {
|
|
1190
|
+
/** `GET /signers/{signer_id}/document?signer-access-code=…` */
|
|
1191
|
+
async getCurrent(signerId, signerAccessCode) {
|
|
1192
|
+
const sid = this.requireId(signerId, "Signer ID");
|
|
1193
|
+
const code = this.requireId(signerAccessCode, "signer-access-code");
|
|
1194
|
+
return this.call(
|
|
1195
|
+
"Failed to fetch current signer document",
|
|
1196
|
+
() => this.http.get(`/signers/${sid}/document`, {
|
|
1197
|
+
params: { "signer-access-code": code }
|
|
1198
|
+
})
|
|
1199
|
+
);
|
|
1200
|
+
}
|
|
1201
|
+
/** `GET /signers/{signer_id}/documents?signer-access-code=…` */
|
|
1202
|
+
async list(signerId, signerAccessCode, params = {}) {
|
|
1203
|
+
const sid = this.requireId(signerId, "Signer ID");
|
|
1204
|
+
const code = this.requireId(signerAccessCode, "signer-access-code");
|
|
1205
|
+
return this.callList(
|
|
1206
|
+
"Failed to list signer documents",
|
|
1207
|
+
() => this.http.get(`/signers/${sid}/documents`, {
|
|
1208
|
+
params: { "signer-access-code": code, ...cleanParams(params) }
|
|
1209
|
+
})
|
|
1210
|
+
);
|
|
1211
|
+
}
|
|
1212
|
+
/** `GET /signers/{signer_id}/documents/{document_id}/download/{artifact}?signer-access-code=…` */
|
|
1213
|
+
async download(signerId, documentId, artifactName, signerAccessCode) {
|
|
1214
|
+
const sid = this.requireId(signerId, "Signer ID");
|
|
1215
|
+
const did = this.requireId(documentId, "Document ID");
|
|
1216
|
+
const code = this.requireId(signerAccessCode, "signer-access-code");
|
|
1217
|
+
return this.callBinary(
|
|
1218
|
+
"Failed to download signer document",
|
|
1219
|
+
() => this.http.get(
|
|
1220
|
+
`/signers/${sid}/documents/${did}/download/${artifactName}`,
|
|
1221
|
+
{ responseType: "arraybuffer", params: { "signer-access-code": code } }
|
|
1222
|
+
)
|
|
1223
|
+
);
|
|
1224
|
+
}
|
|
1225
|
+
/** `PUT /signers/documents/sign-multiple?signer-access-code=…` */
|
|
1226
|
+
async signMultiple(documentIds, signerAccessCode) {
|
|
1227
|
+
if (!Array.isArray(documentIds) || documentIds.length === 0) {
|
|
1228
|
+
throw new ValidationError("documentIds must be a non-empty array");
|
|
1229
|
+
}
|
|
1230
|
+
const code = this.requireId(signerAccessCode, "signer-access-code");
|
|
1231
|
+
return this.call(
|
|
1232
|
+
"Failed to sign multiple documents",
|
|
1233
|
+
() => this.http.put(
|
|
1234
|
+
"/signers/documents/sign-multiple",
|
|
1235
|
+
{ document_ids: documentIds },
|
|
1236
|
+
{ params: { "signer-access-code": code } }
|
|
1237
|
+
)
|
|
1238
|
+
);
|
|
1239
|
+
}
|
|
1240
|
+
/** `PUT /signers/documents/decline-multiple?signer-access-code=…` */
|
|
1241
|
+
async declineMultiple(documentIds, declineReason, signerAccessCode) {
|
|
1242
|
+
if (!Array.isArray(documentIds) || documentIds.length === 0) {
|
|
1243
|
+
throw new ValidationError("documentIds must be a non-empty array");
|
|
1244
|
+
}
|
|
1245
|
+
if (!declineReason) throw new ValidationError("declineReason is required");
|
|
1246
|
+
const code = this.requireId(signerAccessCode, "signer-access-code");
|
|
1247
|
+
return this.call(
|
|
1248
|
+
"Failed to decline multiple documents",
|
|
1249
|
+
() => this.http.put(
|
|
1250
|
+
"/signers/documents/decline-multiple",
|
|
1251
|
+
{ document_ids: documentIds, decline_reason: declineReason },
|
|
1252
|
+
{ params: { "signer-access-code": code } }
|
|
1253
|
+
)
|
|
1254
|
+
);
|
|
1255
|
+
}
|
|
1256
|
+
/** `GET /signers/self?signer-access-code=…` — fetch the signer's own profile. */
|
|
1257
|
+
async self(signerAccessCode) {
|
|
1258
|
+
const code = this.requireId(signerAccessCode, "signer-access-code");
|
|
1259
|
+
return this.call(
|
|
1260
|
+
"Failed to fetch signer profile",
|
|
1261
|
+
() => this.http.get("/signers/self", { params: { "signer-access-code": code } })
|
|
1262
|
+
);
|
|
1263
|
+
}
|
|
1264
|
+
/** `PUT /signers/accept-terms` — accept the platform terms as the signer. */
|
|
1265
|
+
async acceptTerms(signerAccessCode) {
|
|
1266
|
+
const code = this.requireId(signerAccessCode, "signer-access-code");
|
|
1267
|
+
return this.call(
|
|
1268
|
+
"Failed to accept terms",
|
|
1269
|
+
() => this.http.put("/signers/accept-terms", { "signer-access-code": code })
|
|
1270
|
+
);
|
|
1271
|
+
}
|
|
1272
|
+
/** `POST /verify` — verify the email OTP for a signer. */
|
|
1273
|
+
async verifyEmail(payload) {
|
|
1274
|
+
const code = this.requireId(payload.signerAccessCode, "signer-access-code");
|
|
1275
|
+
const otp = this.requireId(payload.verificationCode, "verification-code");
|
|
1276
|
+
return this.call(
|
|
1277
|
+
"Failed to verify signer email",
|
|
1278
|
+
() => this.http.post("/verify", {
|
|
1279
|
+
"signer-access-code": code,
|
|
1280
|
+
"verification-code": otp
|
|
1281
|
+
})
|
|
1282
|
+
);
|
|
1283
|
+
}
|
|
1284
|
+
/** `PUT /documents/{documentId}/signers/confirm-data?signer-access-code=…` */
|
|
1285
|
+
async confirmData(documentId, signerAccessCode, payload) {
|
|
1286
|
+
const did = this.requireId(documentId, "Document ID");
|
|
1287
|
+
const code = this.requireId(signerAccessCode, "signer-access-code");
|
|
1288
|
+
return this.call(
|
|
1289
|
+
"Failed to confirm signer data",
|
|
1290
|
+
() => this.http.put(`/documents/${did}/signers/confirm-data`, cleanParams(payload), {
|
|
1291
|
+
params: { "signer-access-code": code }
|
|
1292
|
+
})
|
|
1293
|
+
);
|
|
1294
|
+
}
|
|
1295
|
+
/**
|
|
1296
|
+
* `POST /signature?signer-access-code=…&type=…` — upload the signer's
|
|
1297
|
+
* signature or initial image. `imageType` defaults to `signature`.
|
|
1298
|
+
*/
|
|
1299
|
+
async uploadSignature(signerAccessCode, image, options = {}) {
|
|
1300
|
+
const code = this.requireId(signerAccessCode, "signer-access-code");
|
|
1301
|
+
if (!Buffer.isBuffer(image) || image.byteLength === 0) {
|
|
1302
|
+
throw new ValidationError("image buffer is required");
|
|
1303
|
+
}
|
|
1304
|
+
return this.call(
|
|
1305
|
+
"Failed to upload signer signature",
|
|
1306
|
+
() => this.http.post("/signature", image, {
|
|
1307
|
+
params: {
|
|
1308
|
+
"signer-access-code": code,
|
|
1309
|
+
type: options.imageType ?? "signature"
|
|
1310
|
+
},
|
|
1311
|
+
headers: { "Content-Type": options.contentType ?? "image/png" }
|
|
1312
|
+
})
|
|
1313
|
+
);
|
|
1314
|
+
}
|
|
1315
|
+
/** `GET /signature/{type}?signer-access-code=…` — download the signer's signature/initial. */
|
|
1316
|
+
async downloadSignature(signerAccessCode, imageType = "signature") {
|
|
1317
|
+
const code = this.requireId(signerAccessCode, "signer-access-code");
|
|
1318
|
+
return this.callBinary(
|
|
1319
|
+
"Failed to download signer signature",
|
|
1320
|
+
() => this.http.get(`/signature/${imageType}`, {
|
|
1321
|
+
responseType: "arraybuffer",
|
|
1322
|
+
params: { "signer-access-code": code }
|
|
1323
|
+
})
|
|
1324
|
+
);
|
|
1325
|
+
}
|
|
1326
|
+
/** `GET /sign?signer-access-code=…` — fetch the assignment as the signer sees it. */
|
|
1327
|
+
async getAssignment(signerAccessCode, hasAcceptedTerms) {
|
|
1328
|
+
const code = this.requireId(signerAccessCode, "signer-access-code");
|
|
1329
|
+
return this.call(
|
|
1330
|
+
"Failed to fetch signer assignment",
|
|
1331
|
+
() => this.http.get("/sign", {
|
|
1332
|
+
params: cleanParams({
|
|
1333
|
+
"signer-access-code": code,
|
|
1334
|
+
has_accepted_terms: hasAcceptedTerms
|
|
1335
|
+
})
|
|
1336
|
+
})
|
|
1337
|
+
);
|
|
1338
|
+
}
|
|
1339
|
+
/** `POST /documents/{documentId}/assignments/{assignmentId}?signer-access-code=…` — sign. */
|
|
1340
|
+
async sign(documentId, assignmentId, signerAccessCode, entries) {
|
|
1341
|
+
const did = this.requireId(documentId, "Document ID");
|
|
1342
|
+
const aid = this.requireId(assignmentId, "Assignment ID");
|
|
1343
|
+
const code = this.requireId(signerAccessCode, "signer-access-code");
|
|
1344
|
+
if (!Array.isArray(entries) || entries.length === 0) {
|
|
1345
|
+
throw new ValidationError("entries must be a non-empty array");
|
|
1346
|
+
}
|
|
1347
|
+
return this.call(
|
|
1348
|
+
"Failed to sign document",
|
|
1349
|
+
() => this.http.post(`/documents/${did}/assignments/${aid}`, entries, {
|
|
1350
|
+
params: { "signer-access-code": code }
|
|
1351
|
+
})
|
|
1352
|
+
);
|
|
1353
|
+
}
|
|
1354
|
+
/**
|
|
1355
|
+
* `PUT /documents/{documentId}/assignments/{assignmentId}/reject?signer-access-code=…`
|
|
1356
|
+
* — signer-side decline. (Distinct from `assignments.cancel`, which is the
|
|
1357
|
+
* workspace-side cancellation flow.)
|
|
1358
|
+
*/
|
|
1359
|
+
async decline(documentId, assignmentId, signerAccessCode, declineReason) {
|
|
1360
|
+
const did = this.requireId(documentId, "Document ID");
|
|
1361
|
+
const aid = this.requireId(assignmentId, "Assignment ID");
|
|
1362
|
+
const code = this.requireId(signerAccessCode, "signer-access-code");
|
|
1363
|
+
if (!declineReason) throw new ValidationError("declineReason is required");
|
|
1364
|
+
return this.call(
|
|
1365
|
+
"Failed to decline assignment",
|
|
1366
|
+
() => this.http.put(
|
|
1367
|
+
`/documents/${did}/assignments/${aid}/reject`,
|
|
1368
|
+
{ decline_reason: declineReason },
|
|
1369
|
+
{ params: { "signer-access-code": code } }
|
|
1370
|
+
)
|
|
1371
|
+
);
|
|
1372
|
+
}
|
|
1373
|
+
};
|
|
1374
|
+
|
|
1375
|
+
// src/support/webhook-verifier.ts
|
|
1376
|
+
var import_node_crypto = require("crypto");
|
|
1377
|
+
var WebhookVerifier = class {
|
|
1378
|
+
constructor(webhookSecret) {
|
|
1379
|
+
this.webhookSecret = webhookSecret;
|
|
1380
|
+
}
|
|
1381
|
+
/** Returns `true` if `signature` is a valid HMAC-SHA256 of `payload`. */
|
|
1382
|
+
verify(payload, signature) {
|
|
1383
|
+
if (!this.webhookSecret || !signature) return false;
|
|
1384
|
+
const buf = typeof payload === "string" ? Buffer.from(payload, "utf8") : payload;
|
|
1385
|
+
const expected = (0, import_node_crypto.createHmac)("sha256", this.webhookSecret).update(buf).digest("hex");
|
|
1386
|
+
const provided = signature.trim();
|
|
1387
|
+
const a = Buffer.from(expected, "utf8");
|
|
1388
|
+
const b = Buffer.from(provided, "utf8");
|
|
1389
|
+
if (a.length !== b.length) return false;
|
|
1390
|
+
try {
|
|
1391
|
+
return (0, import_node_crypto.timingSafeEqual)(a, b);
|
|
1392
|
+
} catch {
|
|
1393
|
+
return false;
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
/** Parse the raw webhook body into a JSON event envelope. */
|
|
1397
|
+
extractEvent(payload) {
|
|
1398
|
+
try {
|
|
1399
|
+
const text = typeof payload === "string" ? payload : payload.toString("utf8");
|
|
1400
|
+
const parsed = JSON.parse(text);
|
|
1401
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
1402
|
+
} catch {
|
|
1403
|
+
return null;
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
/** Extract the event name (`event` or `type`) from an event envelope. */
|
|
1407
|
+
getEventType(event) {
|
|
1408
|
+
if (!event || typeof event !== "object") return null;
|
|
1409
|
+
const e = event;
|
|
1410
|
+
return e.event ?? e.type ?? null;
|
|
1411
|
+
}
|
|
1412
|
+
/** Extract the event data (`data` or `object`) from an event envelope. */
|
|
1413
|
+
getEventData(event) {
|
|
1414
|
+
if (!event || typeof event !== "object") return {};
|
|
1415
|
+
const e = event;
|
|
1416
|
+
return e.data ?? e.object ?? {};
|
|
1417
|
+
}
|
|
1418
|
+
};
|
|
1419
|
+
|
|
1420
|
+
// src/client.ts
|
|
1421
|
+
var DEFAULT_BASE_URL = "https://api.assinafy.com.br/v1";
|
|
1422
|
+
var AssinafyClient = class _AssinafyClient {
|
|
1423
|
+
constructor(options) {
|
|
1424
|
+
if (!options.apiKey && !options.token) {
|
|
1425
|
+
throw new ValidationError(
|
|
1426
|
+
"An API key (options.apiKey) or legacy access token (options.token) is required."
|
|
1427
|
+
);
|
|
1428
|
+
}
|
|
1429
|
+
this.defaultAccountId = options.accountId;
|
|
1430
|
+
this.logger = options.logger ?? createNoopLogger();
|
|
1431
|
+
this.webhookSecret = options.webhookSecret;
|
|
1432
|
+
const baseURL = normaliseBaseUrl(options.baseUrl ?? DEFAULT_BASE_URL);
|
|
1433
|
+
const headers = {
|
|
1434
|
+
"Content-Type": "application/json",
|
|
1435
|
+
Accept: "application/json",
|
|
1436
|
+
"User-Agent": "assinafy-typescript-sdk"
|
|
1437
|
+
};
|
|
1438
|
+
if (options.apiKey) {
|
|
1439
|
+
headers["X-Api-Key"] = options.apiKey;
|
|
1440
|
+
} else if (options.token) {
|
|
1441
|
+
headers["Authorization"] = `Bearer ${options.token}`;
|
|
1442
|
+
}
|
|
1443
|
+
this.axiosInstance = import_axios2.default.create({
|
|
1444
|
+
baseURL,
|
|
1445
|
+
timeout: options.timeout ?? 3e4,
|
|
1446
|
+
headers
|
|
1447
|
+
});
|
|
1448
|
+
this.documents = new DocumentResource(this.axiosInstance, this.defaultAccountId, this.logger);
|
|
1449
|
+
this.signers = new SignerResource(this.axiosInstance, this.defaultAccountId, this.logger);
|
|
1450
|
+
this.workspaces = new WorkspaceResource(this.axiosInstance, void 0, this.logger);
|
|
1451
|
+
this.assignments = new AssignmentResource(
|
|
1452
|
+
this.axiosInstance,
|
|
1453
|
+
this.defaultAccountId,
|
|
1454
|
+
this.logger
|
|
1455
|
+
);
|
|
1456
|
+
this.webhooks = new WebhookResource(this.axiosInstance, this.defaultAccountId, this.logger);
|
|
1457
|
+
this.templates = new TemplateResource(this.axiosInstance, this.defaultAccountId, this.logger);
|
|
1458
|
+
this.tags = new TagResource(this.axiosInstance, this.defaultAccountId, this.logger);
|
|
1459
|
+
this.auth = new AuthenticationResource(this.axiosInstance, void 0, this.logger);
|
|
1460
|
+
this.fields = new FieldsResource(this.axiosInstance, this.defaultAccountId, this.logger);
|
|
1461
|
+
this.signerDocuments = new SignerDocumentsResource(
|
|
1462
|
+
this.axiosInstance,
|
|
1463
|
+
this.defaultAccountId,
|
|
1464
|
+
this.logger
|
|
1465
|
+
);
|
|
1466
|
+
this.webhookVerifier = new WebhookVerifier(this.webhookSecret);
|
|
1467
|
+
}
|
|
1468
|
+
/** Convenience factory for the common apiKey + accountId setup. */
|
|
1469
|
+
static create(apiKey, accountId, options = {}) {
|
|
1470
|
+
return new _AssinafyClient({ apiKey, accountId, ...options });
|
|
1471
|
+
}
|
|
1472
|
+
/** Build a client from a plain object (supports snake_case and camelCase keys). */
|
|
1473
|
+
static fromConfig(config) {
|
|
1474
|
+
const opts = {};
|
|
1475
|
+
const apiKey = config.api_key ?? config.apiKey;
|
|
1476
|
+
const token = config.token ?? config.access_token ?? config.accessToken;
|
|
1477
|
+
const accountId = config.account_id ?? config.accountId;
|
|
1478
|
+
const baseUrl = config.base_url ?? config.baseUrl;
|
|
1479
|
+
const webhookSecret = config.webhook_secret ?? config.webhookSecret;
|
|
1480
|
+
if (apiKey !== void 0) opts.apiKey = apiKey;
|
|
1481
|
+
if (token !== void 0) opts.token = token;
|
|
1482
|
+
if (accountId !== void 0) opts.accountId = accountId;
|
|
1483
|
+
if (baseUrl !== void 0) opts.baseUrl = baseUrl;
|
|
1484
|
+
if (webhookSecret !== void 0) opts.webhookSecret = webhookSecret;
|
|
1485
|
+
if (config.timeout !== void 0) opts.timeout = config.timeout;
|
|
1486
|
+
if (config.logger !== void 0) opts.logger = config.logger;
|
|
1487
|
+
return new _AssinafyClient(opts);
|
|
1488
|
+
}
|
|
1489
|
+
/**
|
|
1490
|
+
* High-level helper that uploads a PDF, ensures it's processed, creates any
|
|
1491
|
+
* missing signers, and kicks off a virtual signature assignment.
|
|
1492
|
+
*/
|
|
1493
|
+
async uploadAndRequestSignatures(options) {
|
|
1494
|
+
if (!options.signers || options.signers.length === 0) {
|
|
1495
|
+
throw new ValidationError("At least one signer is required");
|
|
1496
|
+
}
|
|
1497
|
+
this.logger.info("Starting upload + signature workflow", {
|
|
1498
|
+
signerCount: options.signers.length
|
|
1499
|
+
});
|
|
1500
|
+
const uploadOpts = {};
|
|
1501
|
+
if (options.metadata !== void 0) uploadOpts.metadata = options.metadata;
|
|
1502
|
+
if (options.accountId !== void 0) uploadOpts.accountId = options.accountId;
|
|
1503
|
+
const document = await this.documents.upload(options.source, uploadOpts);
|
|
1504
|
+
if (options.waitForReady !== false) {
|
|
1505
|
+
await this.documents.waitUntilReady(document.id);
|
|
1506
|
+
}
|
|
1507
|
+
const signerIds = [];
|
|
1508
|
+
for (const signer of options.signers) {
|
|
1509
|
+
const payload = { full_name: signer.name };
|
|
1510
|
+
if (signer.email !== void 0) {
|
|
1511
|
+
payload.email = signer.email;
|
|
1512
|
+
}
|
|
1513
|
+
const phone = signer.whatsapp_phone_number ?? signer.phone;
|
|
1514
|
+
if (phone !== void 0) {
|
|
1515
|
+
payload.whatsapp_phone_number = phone;
|
|
1516
|
+
}
|
|
1517
|
+
if (signer.cpf !== void 0) {
|
|
1518
|
+
payload.cpf = signer.cpf;
|
|
1519
|
+
}
|
|
1520
|
+
if (signer.metadata !== void 0) {
|
|
1521
|
+
payload.metadata = signer.metadata;
|
|
1522
|
+
}
|
|
1523
|
+
const created = await this.signers.create(payload, options.accountId);
|
|
1524
|
+
signerIds.push(created.id);
|
|
1525
|
+
}
|
|
1526
|
+
const assignmentPayload = {
|
|
1527
|
+
method: "virtual",
|
|
1528
|
+
signers: signerIds
|
|
1529
|
+
};
|
|
1530
|
+
if (options.message !== void 0) assignmentPayload.message = options.message;
|
|
1531
|
+
if (options.expiresAt !== void 0) assignmentPayload.expires_at = options.expiresAt;
|
|
1532
|
+
if (options.copyReceivers !== void 0) assignmentPayload.copy_receivers = options.copyReceivers;
|
|
1533
|
+
const assignment = await this.assignments.create(document.id, assignmentPayload);
|
|
1534
|
+
this.logger.info("Upload + signature workflow completed", { documentId: document.id });
|
|
1535
|
+
return { document, assignment, signer_ids: signerIds };
|
|
1536
|
+
}
|
|
1537
|
+
/** Expose the underlying axios instance for advanced use cases (interceptors, custom endpoints). */
|
|
1538
|
+
getAxiosInstance() {
|
|
1539
|
+
return this.axiosInstance;
|
|
1540
|
+
}
|
|
1541
|
+
};
|
|
1542
|
+
function normaliseBaseUrl(raw) {
|
|
1543
|
+
return raw.endsWith("/") ? raw.slice(0, -1) : raw;
|
|
1544
|
+
}
|
|
1545
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1546
|
+
0 && (module.exports = {
|
|
1547
|
+
ApiError,
|
|
1548
|
+
AssignmentResource,
|
|
1549
|
+
AssinafyClient,
|
|
1550
|
+
AssinafyError,
|
|
1551
|
+
AuthenticationResource,
|
|
1552
|
+
DocumentResource,
|
|
1553
|
+
FieldsResource,
|
|
1554
|
+
NetworkError,
|
|
1555
|
+
SignerDocumentsResource,
|
|
1556
|
+
SignerResource,
|
|
1557
|
+
TagResource,
|
|
1558
|
+
TemplateResource,
|
|
1559
|
+
ValidationError,
|
|
1560
|
+
WebhookResource,
|
|
1561
|
+
WebhookVerifier,
|
|
1562
|
+
WorkspaceResource,
|
|
1563
|
+
buildAssignmentPayload
|
|
1564
|
+
});
|