@xenterprises/fastify-xsignwell 1.1.1 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts ADDED
@@ -0,0 +1,164 @@
1
+ import { FastifyPluginAsync } from "fastify";
2
+
3
+ interface XSignwellOptions {
4
+ /** SignWell API key (required) */
5
+ apiKey: string;
6
+ /** API base URL (default: "https://www.signwell.com/api/v1") */
7
+ baseUrl?: string;
8
+ /** Enable test mode (default: false) */
9
+ testMode?: boolean;
10
+ /** Enable/disable the plugin (default: true) */
11
+ active?: boolean;
12
+ }
13
+
14
+ interface Recipient {
15
+ id?: string;
16
+ email: string;
17
+ name?: string;
18
+ sendEmail?: boolean;
19
+ [key: string]: unknown;
20
+ }
21
+
22
+ interface FileAttachment {
23
+ name: string;
24
+ base64?: string;
25
+ file_base64?: string;
26
+ url?: string;
27
+ file_url?: string;
28
+ }
29
+
30
+ interface CreateDocumentParams {
31
+ name: string;
32
+ recipients: Recipient[];
33
+ files?: FileAttachment[];
34
+ draft?: boolean;
35
+ testMode?: boolean;
36
+ subject?: string;
37
+ message?: string;
38
+ metadata?: Record<string, unknown>;
39
+ [key: string]: unknown;
40
+ }
41
+
42
+ interface CreateFromTemplateParams {
43
+ recipients: Recipient[];
44
+ fields?: Record<string, unknown>;
45
+ draft?: boolean;
46
+ testMode?: boolean;
47
+ subject?: string;
48
+ message?: string;
49
+ metadata?: Record<string, unknown>;
50
+ [key: string]: unknown;
51
+ }
52
+
53
+ interface PaginationParams {
54
+ page?: number;
55
+ limit?: number;
56
+ }
57
+
58
+ interface DocumentsService {
59
+ create(params: CreateDocumentParams): Promise<Record<string, unknown>>;
60
+ createFromTemplate(templateId: string, params: CreateFromTemplateParams): Promise<Record<string, unknown>>;
61
+ get(documentId: string): Promise<Record<string, unknown>>;
62
+ list(params?: PaginationParams): Promise<Record<string, unknown>>;
63
+ send(documentId: string, params?: Record<string, unknown>): Promise<Record<string, unknown>>;
64
+ remind(documentId: string): Promise<Record<string, unknown>>;
65
+ remove(documentId: string): Promise<Record<string, unknown>>;
66
+ delete(documentId: string): Promise<Record<string, unknown>>;
67
+ getCompletedPdf(documentId: string): Promise<Record<string, unknown>>;
68
+ getEmbeddedSigningUrl(documentId: string, recipientId: string): Promise<{ url: string; recipient: Record<string, unknown> }>;
69
+ getAuditTrail(documentId: string): Promise<Record<string, unknown>>;
70
+ }
71
+
72
+ interface CreateTemplateParams {
73
+ name: string;
74
+ files?: FileAttachment[];
75
+ recipients?: Array<{ id?: string; name?: string; placeholder_name?: string; [key: string]: unknown }>;
76
+ fields?: unknown[];
77
+ subject?: string;
78
+ message?: string;
79
+ [key: string]: unknown;
80
+ }
81
+
82
+ interface TemplatesService {
83
+ get(templateId: string): Promise<Record<string, unknown>>;
84
+ create(params: CreateTemplateParams): Promise<Record<string, unknown>>;
85
+ update(templateId: string, params: Record<string, unknown>): Promise<Record<string, unknown>>;
86
+ remove(templateId: string): Promise<Record<string, unknown>>;
87
+ delete(templateId: string): Promise<Record<string, unknown>>;
88
+ list(params?: PaginationParams): Promise<Record<string, unknown>>;
89
+ getFields(templateId: string): Promise<unknown[]>;
90
+ getRecipients(templateId: string): Promise<unknown[]>;
91
+ }
92
+
93
+ interface WebhookEvents {
94
+ DOCUMENT_COMPLETED: "document.completed";
95
+ DOCUMENT_SENT: "document.sent";
96
+ DOCUMENT_VIEWED: "document.viewed";
97
+ DOCUMENT_SIGNED: "document.signed";
98
+ DOCUMENT_DECLINED: "document.declined";
99
+ DOCUMENT_EXPIRED: "document.expired";
100
+ DOCUMENT_VOIDED: "document.voided";
101
+ RECIPIENT_COMPLETED: "recipient.completed";
102
+ RECIPIENT_VIEWED: "recipient.viewed";
103
+ RECIPIENT_SIGNED: "recipient.signed";
104
+ RECIPIENT_DECLINED: "recipient.declined";
105
+ }
106
+
107
+ interface ParsedEvent {
108
+ event: string;
109
+ documentId: string;
110
+ document: Record<string, unknown>;
111
+ recipient: Record<string, unknown> | undefined;
112
+ timestamp: string;
113
+ raw: Record<string, unknown>;
114
+ }
115
+
116
+ interface WebhooksService {
117
+ list(): Promise<Record<string, unknown>>;
118
+ create(params: { callbackUrl: string; event?: string; [key: string]: unknown }): Promise<Record<string, unknown>>;
119
+ remove(webhookId: string): Promise<Record<string, unknown>>;
120
+ delete(webhookId: string): Promise<Record<string, unknown>>;
121
+ events: WebhookEvents;
122
+ verifySignature(payload: string, signature: string, secret: string): boolean;
123
+ parseEvent(payload: Record<string, unknown>): ParsedEvent;
124
+ }
125
+
126
+ interface CreateBulkSendParams {
127
+ templateId: string;
128
+ recipients: Record<string, unknown>[];
129
+ testMode?: boolean;
130
+ subject?: string;
131
+ message?: string;
132
+ [key: string]: unknown;
133
+ }
134
+
135
+ interface BulkSendService {
136
+ get(bulkSendId: string): Promise<Record<string, unknown>>;
137
+ list(params?: PaginationParams): Promise<Record<string, unknown>>;
138
+ create(params: CreateBulkSendParams): Promise<Record<string, unknown>>;
139
+ getCsvTemplate(templateId: string): Promise<Record<string, unknown>>;
140
+ validateCsv(templateId: string, csvContent: string): Promise<Record<string, unknown>>;
141
+ getDocuments(bulkSendId: string, params?: PaginationParams): Promise<Record<string, unknown>>;
142
+ }
143
+
144
+ interface XSignwellDecorator {
145
+ config: {
146
+ apiKey: string;
147
+ baseUrl: string;
148
+ testMode: boolean;
149
+ };
150
+ documents: DocumentsService;
151
+ templates: TemplatesService;
152
+ webhooks: WebhooksService;
153
+ bulkSend: BulkSendService;
154
+ me(): Promise<Record<string, unknown>>;
155
+ }
156
+
157
+ declare module "fastify" {
158
+ interface FastifyInstance {
159
+ xsignwell: XSignwellDecorator;
160
+ }
161
+ }
162
+
163
+ declare const xSignwell: FastifyPluginAsync<XSignwellOptions>;
164
+ export default xSignwell;
package/package.json CHANGED
@@ -1,35 +1,38 @@
1
1
  {
2
2
  "name": "@xenterprises/fastify-xsignwell",
3
- "version": "1.1.1",
4
- "description": "Fastify plugin for SignWell document signing API integration",
5
- "type": "module",
6
- "main": "src/xSignwell.js",
7
- "engines": {
8
- "node": ">=20.0.0",
9
- "npm": ">=10.0.0"
10
- },
11
- "scripts": {
12
- "test": "node --test test/xSignwell.test.js",
13
- "lint": "eslint src/ test/"
14
- },
15
- "keywords": [
16
- "fastify",
17
- "signwell",
18
- "esignature",
19
- "document-signing",
20
- "pdf",
21
- "api"
22
- ],
23
- "author": "X Enterprises",
24
- "license": "MIT",
25
- "dependencies": {
26
- "fastify-plugin": "^5.0.1"
27
- },
28
- "peerDependencies": {
29
- "fastify": "^5.0.0"
30
- },
31
- "devDependencies": {
32
- "eslint": "^9.0.0",
33
- "fastify": "^5.0.0"
34
- }
3
+ "version": "1.2.1",
4
+ "description": "Fastify plugin for SignWell document signing API integration",
5
+ "type": "module",
6
+ "main": "src/xSignwell.js",
7
+ "engines": {
8
+ "node": ">=20.0.0",
9
+ "npm": ">=10.0.0"
10
+ },
11
+ "scripts": {
12
+ "test": "node --test test/xSignwell.test.js",
13
+ "lint": "eslint src/ test/"
14
+ },
15
+ "keywords": [
16
+ "fastify",
17
+ "signwell",
18
+ "esignature",
19
+ "document-signing",
20
+ "pdf",
21
+ "api"
22
+ ],
23
+ "author": "X Enterprises",
24
+ "license": "SEE LICENSE IN LICENSE",
25
+ "dependencies": {
26
+ "fastify-plugin": "^5.0.1"
27
+ },
28
+ "peerDependencies": {
29
+ "fastify": "^5.0.0"
30
+ },
31
+ "devDependencies": {
32
+ "eslint": "^9.0.0",
33
+ "fastify": "^5.0.0"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ }
35
38
  }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Shared API request helper for SignWell services.
3
+ *
4
+ * @param {import('fastify').FastifyInstance} fastify - Fastify instance (for logging)
5
+ * @param {Object} config - Plugin configuration
6
+ * @param {string} endpoint - API endpoint path
7
+ * @param {Object} [options] - Fetch options
8
+ * @returns {Promise<Object>} Parsed API response
9
+ */
10
+ export async function apiRequest(fastify, config, endpoint, options = {}) {
11
+ const url = `${config.baseUrl}${endpoint}`;
12
+ const headers = {
13
+ "X-Api-Key": config.apiKey,
14
+ "Content-Type": "application/json",
15
+ ...options.headers,
16
+ };
17
+
18
+ let response;
19
+ try {
20
+ response = await fetch(url, { ...options, headers });
21
+ } catch (err) {
22
+ throw new Error(`[xSignwell] Network error calling ${endpoint}: ${err.message}`);
23
+ }
24
+
25
+ if (!response.ok) {
26
+ const errorText = await response.text();
27
+ let errorData;
28
+ try {
29
+ errorData = JSON.parse(errorText);
30
+ } catch {
31
+ errorData = { message: errorText };
32
+ }
33
+ fastify.log.error(
34
+ { endpoint, status: response.status, error: errorData },
35
+ "[xSignwell] API error"
36
+ );
37
+ const err = new Error(
38
+ `[xSignwell] ${errorData.message || `API error: ${response.status}`}`
39
+ );
40
+ err.statusCode = response.status;
41
+ err.signwellError = errorData;
42
+ throw err;
43
+ }
44
+
45
+ const text = await response.text();
46
+ if (!text) return { success: true };
47
+
48
+ return JSON.parse(text);
49
+ }
@@ -6,100 +6,67 @@
6
6
  * @see https://developers.signwell.com/reference/get_api-v1-bulk-sends-id
7
7
  */
8
8
 
9
+ import { apiRequest } from "../apiRequest.js";
10
+
9
11
  /**
10
- * Setup bulk send service
11
- *
12
- * @param {import('fastify').FastifyInstance} fastify - Fastify instance
13
- * @param {Object} config - Plugin configuration
12
+ * @param {import('fastify').FastifyInstance} fastify
13
+ * @param {Object} config
14
14
  */
15
15
  export async function setupBulkSend(fastify, config) {
16
- const { apiKey, baseUrl, testMode } = config;
17
-
18
- /**
19
- * Make an API request to SignWell
20
- * @param {string} endpoint - API endpoint
21
- * @param {Object} options - Fetch options
22
- * @returns {Promise<Object>} API response
23
- */
24
- async function apiRequest(endpoint, options = {}) {
25
- const url = `${baseUrl}${endpoint}`;
26
- const headers = {
27
- "X-Api-Key": apiKey,
28
- "Content-Type": "application/json",
29
- ...options.headers,
30
- };
31
-
32
- const response = await fetch(url, {
33
- ...options,
34
- headers,
35
- });
36
-
37
- if (!response.ok) {
38
- const errorText = await response.text();
39
- let errorData;
40
- try {
41
- errorData = JSON.parse(errorText);
42
- } catch {
43
- errorData = { message: errorText };
44
- }
45
- fastify.log.error({ endpoint, status: response.status, error: errorData }, "SignWell API error");
46
- throw new Error(errorData.message || `SignWell API error: ${response.status}`);
47
- }
48
-
49
- const text = await response.text();
50
- if (!text) return { success: true };
16
+ const { testMode } = config;
51
17
 
52
- return JSON.parse(text);
18
+ function req(endpoint, options) {
19
+ return apiRequest(fastify, config, endpoint, options);
53
20
  }
54
21
 
55
22
  /**
56
23
  * Get a bulk send by ID
57
- * @param {string} bulkSendId - Bulk send ID
58
- * @returns {Promise<Object>} Bulk send details
24
+ * @param {string} bulkSendId
25
+ * @returns {Promise<Object>}
59
26
  */
60
27
  async function get(bulkSendId) {
61
- return apiRequest(`/bulk_sends/${bulkSendId}`, {
62
- method: "GET",
63
- });
28
+ if (!bulkSendId || typeof bulkSendId !== "string") {
29
+ throw new Error("[xSignwell] bulkSend.get: bulkSendId (string) is required");
30
+ }
31
+ return req(`/bulk_sends/${bulkSendId}`, { method: "GET" });
64
32
  }
65
33
 
66
34
  /**
67
35
  * List all bulk sends
68
- * @param {Object} [params] - Query parameters
69
- * @param {number} [params.page] - Page number
70
- * @param {number} [params.limit] - Items per page
71
- * @returns {Promise<Object>} List of bulk sends
36
+ * @param {Object} [params]
37
+ * @param {number} [params.page]
38
+ * @param {number} [params.limit]
39
+ * @returns {Promise<Object>}
72
40
  */
73
41
  async function list(params = {}) {
74
- const queryParams = new URLSearchParams();
75
- if (params.page) queryParams.set("page", params.page);
76
- if (params.limit) queryParams.set("limit", params.limit);
77
-
78
- const queryString = queryParams.toString();
79
- const endpoint = `/bulk_sends${queryString ? `?${queryString}` : ""}`;
80
-
81
- return apiRequest(endpoint, {
82
- method: "GET",
83
- });
42
+ const qp = new URLSearchParams();
43
+ if (params.page) qp.set("page", params.page);
44
+ if (params.limit) qp.set("limit", params.limit);
45
+ const qs = qp.toString();
46
+ return req(`/bulk_sends${qs ? `?${qs}` : ""}`, { method: "GET" });
84
47
  }
85
48
 
86
49
  /**
87
50
  * Create a bulk send
88
- * @param {Object} params - Bulk send parameters
89
- * @param {string} params.templateId - Template ID to use
90
- * @param {Array<Object>} params.recipients - List of recipient data
51
+ * @param {Object} params
52
+ * @param {string} params.templateId - Template ID
53
+ * @param {Array<Object>} params.recipients - Recipient data rows
91
54
  * @param {string} [params.subject] - Email subject
92
55
  * @param {string} [params.message] - Email message
93
- * @returns {Promise<Object>} Created bulk send
56
+ * @returns {Promise<Object>}
94
57
  */
95
58
  async function create(params) {
96
- const {
97
- templateId,
98
- recipients,
99
- subject,
100
- message,
101
- ...rest
102
- } = params;
59
+ if (!params || typeof params !== "object") {
60
+ throw new Error("[xSignwell] bulkSend.create: params object is required");
61
+ }
62
+ if (!params.templateId || typeof params.templateId !== "string") {
63
+ throw new Error("[xSignwell] bulkSend.create: templateId (string) is required");
64
+ }
65
+ if (!Array.isArray(params.recipients) || params.recipients.length === 0) {
66
+ throw new Error("[xSignwell] bulkSend.create: recipients (non-empty array) is required");
67
+ }
68
+
69
+ const { templateId, recipients, subject, message, ...rest } = params;
103
70
 
104
71
  const body = {
105
72
  document_template_id: templateId,
@@ -111,31 +78,40 @@ export async function setupBulkSend(fastify, config) {
111
78
  if (subject) body.subject = subject;
112
79
  if (message) body.message = message;
113
80
 
114
- return apiRequest("/bulk_sends", {
81
+ return req("/bulk_sends", {
115
82
  method: "POST",
116
83
  body: JSON.stringify(body),
117
84
  });
118
85
  }
119
86
 
120
87
  /**
121
- * Get CSV template for bulk send
122
- * @param {string} templateId - Template ID
123
- * @returns {Promise<Object>} CSV template info
88
+ * Get CSV template for a bulk send
89
+ * @param {string} templateId
90
+ * @returns {Promise<Object>}
124
91
  */
125
92
  async function getCsvTemplate(templateId) {
126
- return apiRequest(`/bulk_sends/csv_template?document_template_id=${templateId}`, {
93
+ if (!templateId || typeof templateId !== "string") {
94
+ throw new Error("[xSignwell] bulkSend.getCsvTemplate: templateId (string) is required");
95
+ }
96
+ return req(`/bulk_sends/csv_template?document_template_id=${templateId}`, {
127
97
  method: "GET",
128
98
  });
129
99
  }
130
100
 
131
101
  /**
132
102
  * Validate CSV for bulk send
133
- * @param {string} templateId - Template ID
134
- * @param {string} csvContent - CSV content (base64 or raw)
135
- * @returns {Promise<Object>} Validation result
103
+ * @param {string} templateId
104
+ * @param {string} csvContent
105
+ * @returns {Promise<Object>}
136
106
  */
137
107
  async function validateCsv(templateId, csvContent) {
138
- return apiRequest("/bulk_sends/validate_csv", {
108
+ if (!templateId || typeof templateId !== "string") {
109
+ throw new Error("[xSignwell] bulkSend.validateCsv: templateId (string) is required");
110
+ }
111
+ if (!csvContent || typeof csvContent !== "string") {
112
+ throw new Error("[xSignwell] bulkSend.validateCsv: csvContent (string) is required");
113
+ }
114
+ return req("/bulk_sends/validate_csv", {
139
115
  method: "POST",
140
116
  body: JSON.stringify({
141
117
  document_template_id: templateId,
@@ -146,26 +122,23 @@ export async function setupBulkSend(fastify, config) {
146
122
 
147
123
  /**
148
124
  * Get documents from a bulk send
149
- * @param {string} bulkSendId - Bulk send ID
150
- * @param {Object} [params] - Query parameters
151
- * @param {number} [params.page] - Page number
152
- * @param {number} [params.limit] - Items per page
153
- * @returns {Promise<Object>} List of documents
125
+ * @param {string} bulkSendId
126
+ * @param {Object} [params]
127
+ * @returns {Promise<Object>}
154
128
  */
155
129
  async function getDocuments(bulkSendId, params = {}) {
156
- const queryParams = new URLSearchParams();
157
- if (params.page) queryParams.set("page", params.page);
158
- if (params.limit) queryParams.set("limit", params.limit);
159
-
160
- const queryString = queryParams.toString();
161
- const endpoint = `/bulk_sends/${bulkSendId}/documents${queryString ? `?${queryString}` : ""}`;
162
-
163
- return apiRequest(endpoint, {
130
+ if (!bulkSendId || typeof bulkSendId !== "string") {
131
+ throw new Error("[xSignwell] bulkSend.getDocuments: bulkSendId (string) is required");
132
+ }
133
+ const qp = new URLSearchParams();
134
+ if (params.page) qp.set("page", params.page);
135
+ if (params.limit) qp.set("limit", params.limit);
136
+ const qs = qp.toString();
137
+ return req(`/bulk_sends/${bulkSendId}/documents${qs ? `?${qs}` : ""}`, {
164
138
  method: "GET",
165
139
  });
166
140
  }
167
141
 
168
- // Add bulk send service to xsignwell namespace
169
142
  fastify.xsignwell.bulkSend = {
170
143
  get,
171
144
  list,
@@ -175,5 +148,5 @@ export async function setupBulkSend(fastify, config) {
175
148
  getDocuments,
176
149
  };
177
150
 
178
- console.info(" Bulk Send Service initialized");
151
+ fastify.log.info("[xSignwell] Bulk Send service initialized");
179
152
  }