apacuana-sdk-core 0.12.0 → 0.13.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.
Files changed (37) hide show
  1. package/README.md +131 -164
  2. package/coverage/clover.xml +238 -205
  3. package/coverage/coverage-final.json +4 -4
  4. package/coverage/lcov-report/index.html +24 -24
  5. package/coverage/lcov-report/src/api/certs.js.html +1 -1
  6. package/coverage/lcov-report/src/api/faceLiveness.js.html +1 -1
  7. package/coverage/lcov-report/src/api/index.html +13 -13
  8. package/coverage/lcov-report/src/api/revocations.js.html +1 -1
  9. package/coverage/lcov-report/src/api/signatures.js.html +17 -56
  10. package/coverage/lcov-report/src/api/users.js.html +1 -1
  11. package/coverage/lcov-report/src/config/index.html +1 -1
  12. package/coverage/lcov-report/src/config/index.js.html +1 -1
  13. package/coverage/lcov-report/src/errors/index.html +1 -1
  14. package/coverage/lcov-report/src/errors/index.js.html +1 -1
  15. package/coverage/lcov-report/src/index.html +1 -1
  16. package/coverage/lcov-report/src/index.js.html +3 -18
  17. package/coverage/lcov-report/src/success/index.html +1 -1
  18. package/coverage/lcov-report/src/success/index.js.html +1 -1
  19. package/coverage/lcov-report/src/utils/constant.js.html +1 -1
  20. package/coverage/lcov-report/src/utils/helpers.js.html +277 -16
  21. package/coverage/lcov-report/src/utils/httpClient.js.html +32 -17
  22. package/coverage/lcov-report/src/utils/index.html +22 -22
  23. package/coverage/lcov.info +466 -393
  24. package/dist/index.d.ts +0 -2
  25. package/dist/index.js +94 -135
  26. package/dist/index.js.map +1 -1
  27. package/dist/index.mjs +94 -135
  28. package/dist/index.mjs.map +1 -1
  29. package/dist/types/signatures.d.ts +19 -4
  30. package/dist/utils/helpers.d.ts +4 -2
  31. package/package.json +1 -1
  32. package/src/api/signatures.js +8 -21
  33. package/src/index.js +1 -6
  34. package/src/types/signatures.js +6 -1
  35. package/src/utils/helpers.js +95 -8
  36. package/src/utils/httpClient.js +15 -10
  37. package/tests/api/signatures.test.js +73 -16
@@ -41,6 +41,10 @@ export type GetDigestData = {
41
41
  * - Identificador único de la firma.
42
42
  */
43
43
  signatureId: string;
44
+ /**
45
+ * - Documento a firmar en formato de archivo. Este campo es opcional.
46
+ */
47
+ document?: File | undefined;
44
48
  };
45
49
  export type SignaturePlacement = {
46
50
  /**
@@ -64,10 +68,6 @@ export type SignerData = {
64
68
  * - Nombre del firmante.
65
69
  */
66
70
  name: string;
67
- /**
68
- * - Referencia o identificador del firmante.
69
- */
70
- reference: string;
71
71
  /**
72
72
  * - Tipo de documento de identidad del firmante.
73
73
  */
@@ -80,7 +80,18 @@ export type SignerData = {
80
80
  * - Array con las coordenadas donde se estampará la firma.
81
81
  */
82
82
  signature: Array<SignaturePlacement>;
83
+ /**
84
+ * - Referencia a un documento pre-cargado. Se debe proporcionar 'reference' o 'file', pero no ambos.
85
+ */
86
+ reference?: string | undefined;
87
+ /**
88
+ * - Archivo del documento a firmar. Se debe proporcionar 'reference' o 'file', pero no ambos.
89
+ */
90
+ file?: File | undefined;
83
91
  };
92
+ /**
93
+ * Define la estructura de datos para la respuesta de la creación de una firma.
94
+ */
84
95
  export type GetDigestResponseData = {
85
96
  /**
86
97
  * - El digest del documento que se va a firmar.
@@ -144,4 +155,8 @@ export type SignDocumentData = {
144
155
  * - Digest firmado.
145
156
  */
146
157
  signedDigest: string;
158
+ /**
159
+ * - Documento a firmar en formato de archivo. Este campo es opcional.
160
+ */
161
+ document?: File | undefined;
147
162
  };
@@ -8,6 +8,7 @@ declare namespace _default {
8
8
  export { validateGetDocsData };
9
9
  export { validateGetDigestData };
10
10
  export { validateOnBoardingSignDocumentData };
11
+ export { createPayloadGetDigest };
11
12
  }
12
13
  export default _default;
13
14
  declare function getCertificateStatus(userData: any, certificateInDevice: any): string;
@@ -16,8 +17,9 @@ declare function arrayBufferToBase64(buffer: any): string;
16
17
  declare function encryptedCsr(csr: any, encryptKey?: string): {
17
18
  csr: any;
18
19
  };
19
- declare function validateOnBoardingSignerData(signerData: any): void;
20
+ declare function validateOnBoardingSignerData(signerData: any): FormData;
20
21
  declare function validateCsr(encryptedCSR: any): void;
21
22
  declare function validateGetDocsData(data: any): void;
22
23
  declare function validateGetDigestData(signData: any): void;
23
- declare function validateOnBoardingSignDocumentData(signData: any): void;
24
+ declare function validateOnBoardingSignDocumentData(signData: any): FormData;
25
+ declare function createPayloadGetDigest(signData: any): FormData;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apacuana-sdk-core",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Core SDK para interacciones con las APIs de Apacuana.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -22,25 +22,13 @@ import { INTEGRATION_TYPE } from "../utils/constant";
22
22
  // =================================================================
23
23
 
24
24
  const signDocumentOnBoarding = async (signData) => {
25
- helpers.validateOnBoardingSignDocumentData(signData);
26
- const { signature, cert, signedDigest } = signData;
27
- try {
28
- const signBody = {
29
- positions: JSON.stringify(
30
- signature.positions.map((p) => ({
31
- x: p.x,
32
- y: p.y,
33
- page: p.page,
34
- status: 1,
35
- }))
36
- ),
37
- publickey: cert,
38
- signeddigest: signedDigest,
39
- };
25
+ const payload = helpers.validateOnBoardingSignDocumentData(signData);
40
26
 
27
+ const { signature } = signData;
28
+ try {
41
29
  const response = await httpRequest(
42
30
  `services/api/documents/sign/${signature.id}`,
43
- signBody,
31
+ payload,
44
32
  "PUT"
45
33
  );
46
34
  return new ApacuanaSuccess(response);
@@ -64,11 +52,10 @@ const signDocumentOnPremise = async () => {
64
52
 
65
53
  const getDigestToSignOnBoarding = async (signData) => {
66
54
  try {
55
+ const formData = helpers.createPayloadGetDigest(signData);
67
56
  const response = await httpRequest(
68
57
  `services/api/documents/getdigest/${signData.signatureId}`,
69
- {
70
- publickey: signData.cert,
71
- },
58
+ formData,
72
59
  "POST"
73
60
  );
74
61
  const digest = response.data?.digest || response.digest;
@@ -100,9 +87,9 @@ const getDigestToSignOnPremise = async () => {
100
87
  };
101
88
 
102
89
  const addSignerOnBoarding = async (signerData) => {
103
- helpers.validateOnBoardingSignerData(signerData);
90
+ const payload = helpers.validateOnBoardingSignerData(signerData);
104
91
  try {
105
- await httpRequest("services/api/documents/signing", signerData, "POST");
92
+ await httpRequest("services/api/documents/signingsdk", payload, "POST");
106
93
  return new ApacuanaSuccess({
107
94
  signer: signerData.typedoc + signerData.doc,
108
95
  });
package/src/index.js CHANGED
@@ -7,7 +7,6 @@ import {
7
7
  getCertStatus,
8
8
  getCertTypes,
9
9
  getRequerimentsByTypeUser,
10
- requestCertificate,
11
10
  } from "./api/certs";
12
11
  import {
13
12
  addSigner,
@@ -73,10 +72,6 @@ const apacuana = {
73
72
  );
74
73
  }
75
74
  },
76
-
77
- /**
78
- * Permite obtener la configuración actual del SDK.
79
- */
80
75
  close: () => close(),
81
76
  getConfig,
82
77
  requestRevocation,
@@ -95,7 +90,7 @@ const apacuana = {
95
90
  getRequerimentsByTypeUser,
96
91
  createFaceLivenessSession,
97
92
  validateFaceLiveness,
98
- requestCertificate,
93
+ // requestCertificate,
99
94
  };
100
95
 
101
96
  export default apacuana;
@@ -27,6 +27,7 @@
27
27
  * @typedef {object} GetDigestData
28
28
  * @property {string} cert - Certificado del firmante en formato base64.
29
29
  * @property {string} signatureId - Identificador único de la firma.
30
+ * @property {File} [document] - Documento a firmar en formato de archivo. Este campo es opcional.
30
31
  */
31
32
 
32
33
  /**
@@ -40,13 +41,15 @@
40
41
  * Define la estructura de datos para añadir un firmante.
41
42
  * @typedef {object} SignerData
42
43
  * @property {string} name - Nombre del firmante.
43
- * @property {string} reference - Referencia o identificador del firmante.
44
44
  * @property {string} typedoc - Tipo de documento de identidad del firmante.
45
45
  * @property {string} doc - Número de documento de identidad del firmante.
46
46
  * @property {Array<SignaturePlacement>} signature - Array con las coordenadas donde se estampará la firma.
47
+ * @property {string} [reference] - Referencia a un documento pre-cargado. Se debe proporcionar 'reference' o 'file', pero no ambos.
48
+ * @property {File} [file] - Archivo del documento a firmar. Se debe proporcionar 'reference' o 'file', pero no ambos.
47
49
  */
48
50
 
49
51
  /**
52
+ * Define la estructura de datos para la respuesta de la creación de una firma.
50
53
  * @typedef {object} GetDigestResponseData
51
54
  * @property {string} digest - El digest del documento que se va a firmar.
52
55
  */
@@ -86,6 +89,8 @@
86
89
  * @property {Array<object>} signature.positions - Posiciones de la firma.
87
90
  * @property {string} cert - Certificado en base64.
88
91
  * @property {string} signedDigest - Digest firmado.
92
+ * @property {File} [document] - Documento a firmar en formato de archivo. Este campo es opcional.
93
+
89
94
  */
90
95
 
91
96
  export {};
@@ -65,8 +65,31 @@ const encryptedCsr = (csr, encryptKey = KEY_HEX) => {
65
65
  return body;
66
66
  };
67
67
 
68
+ const createPayloadGetDigest = (signData) => {
69
+ const formData = new FormData();
70
+
71
+ // Iterate over the object's own properties
72
+ Object.keys(signData).forEach((key) => {
73
+ // Conditionally handle the 'document' field
74
+
75
+ if (key === "document" && signData[key] instanceof File) {
76
+ // Append the file directly
77
+ formData.append(key, signData[key]);
78
+ } else if (key === "cert") {
79
+ formData.append("publickey", signData[key]);
80
+ }
81
+ });
82
+ return formData;
83
+ };
84
+
68
85
  const validateOnBoardingSignerData = (signerData) => {
69
- if (!signerData.name || typeof signerData.name !== "string") {
86
+ // Create a copy of the original object to avoid modifying the function parameter
87
+ const validatedSignerData = { ...signerData };
88
+
89
+ if (
90
+ !validatedSignerData.name ||
91
+ typeof validatedSignerData.name !== "string"
92
+ ) {
70
93
  throw new ApacuanaAPIError(
71
94
  'El campo "name" es requerido y debe ser una cadena de texto.',
72
95
  400,
@@ -74,16 +97,46 @@ const validateOnBoardingSignerData = (signerData) => {
74
97
  );
75
98
  }
76
99
 
77
- if (!signerData.reference || typeof signerData.reference !== "string") {
100
+ // Limpia 'reference' si es inválido o está vacío.
101
+ if (
102
+ !validatedSignerData.reference ||
103
+ typeof validatedSignerData.reference !== "string" ||
104
+ validatedSignerData.reference.trim() === ""
105
+ ) {
106
+ delete validatedSignerData.reference;
107
+ }
108
+
109
+ // Limpia 'file' si no es una instancia de File.
110
+ if (
111
+ typeof File === "undefined" ||
112
+ !validatedSignerData.file ||
113
+ !(validatedSignerData.file instanceof File)
114
+ ) {
115
+ delete validatedSignerData.file;
116
+ }
117
+
118
+ // Valida que solo uno de los dos campos exista después de la limpieza.
119
+ if (validatedSignerData.reference && validatedSignerData.file) {
120
+ throw new ApacuanaAPIError(
121
+ "No se pueden proporcionar 'reference' y 'file' simultáneamente.",
122
+ 400,
123
+ "INVALID_PARAMETER_FORMAT"
124
+ );
125
+ }
126
+
127
+ if (!validatedSignerData.reference && !validatedSignerData.file) {
78
128
  throw new ApacuanaAPIError(
79
- 'El campo "reference" es requerido y debe ser una cadena de texto.',
129
+ "Se debe proporcionar un campo 'reference' o 'file' válido.",
80
130
  400,
81
131
  "INVALID_PARAMETER_FORMAT"
82
132
  );
83
133
  }
84
134
 
85
135
  const validDocTypes = ["V", "P", "E"];
86
- if (!signerData.typedoc || !validDocTypes.includes(signerData.typedoc)) {
136
+ if (
137
+ !validatedSignerData.typedoc ||
138
+ !validDocTypes.includes(validatedSignerData.typedoc)
139
+ ) {
87
140
  throw new ApacuanaAPIError(
88
141
  'El campo "typedoc" es requerido y debe ser uno de los siguientes valores: V, P, E.',
89
142
  400,
@@ -91,7 +144,7 @@ const validateOnBoardingSignerData = (signerData) => {
91
144
  );
92
145
  }
93
146
 
94
- if (!signerData.doc || typeof signerData.doc !== "string") {
147
+ if (!validatedSignerData.doc || typeof validatedSignerData.doc !== "string") {
95
148
  throw new ApacuanaAPIError(
96
149
  'El campo "doc" es requerido y debe ser una cadena de texto.',
97
150
  400,
@@ -100,8 +153,8 @@ const validateOnBoardingSignerData = (signerData) => {
100
153
  }
101
154
 
102
155
  if (
103
- !Array.isArray(signerData.signature) ||
104
- signerData.signature.length === 0
156
+ !Array.isArray(validatedSignerData.signature) ||
157
+ validatedSignerData.signature.length === 0
105
158
  ) {
106
159
  throw new ApacuanaAPIError(
107
160
  'El campo "signature" es requerido y debe ser un array no vacío.',
@@ -110,7 +163,7 @@ const validateOnBoardingSignerData = (signerData) => {
110
163
  );
111
164
  }
112
165
 
113
- signerData.signature.forEach((sig, index) => {
166
+ validatedSignerData.signature.forEach((sig, index) => {
114
167
  if (!Number.isInteger(sig.page) || sig.page < 1) {
115
168
  throw new ApacuanaAPIError(
116
169
  `La página de la firma ${
@@ -141,6 +194,19 @@ const validateOnBoardingSignerData = (signerData) => {
141
194
  );
142
195
  }
143
196
  });
197
+
198
+ const formData = new FormData();
199
+ Object.keys(validatedSignerData).forEach((key) => {
200
+ if (key === "signature") {
201
+ formData.append(key, JSON.stringify(validatedSignerData[key]));
202
+ } else if (key === "file") {
203
+ formData.append("document", validatedSignerData[key]);
204
+ } else if (validatedSignerData[key] !== undefined) {
205
+ formData.append(key, validatedSignerData[key]);
206
+ }
207
+ });
208
+
209
+ return formData;
144
210
  };
145
211
 
146
212
  const validateCsr = (encryptedCSR) => {
@@ -243,6 +309,26 @@ const validateOnBoardingSignDocumentData = (signData) => {
243
309
  "INVALID_PARAMETER"
244
310
  );
245
311
  }
312
+
313
+ const formData = new FormData();
314
+ formData.append(
315
+ "positions",
316
+ JSON.stringify(
317
+ signature.positions.map((p) => ({
318
+ x: p.x,
319
+ y: p.y,
320
+ page: p.page,
321
+ status: 1,
322
+ }))
323
+ )
324
+ );
325
+ formData.append("publickey", cert);
326
+ formData.append("signeddigest", signedDigest);
327
+ if (signData.document && signData.document instanceof File) {
328
+ formData.append("document", signData.document);
329
+ }
330
+
331
+ return formData;
246
332
  };
247
333
 
248
334
  export default {
@@ -255,4 +341,5 @@ export default {
255
341
  validateGetDocsData,
256
342
  validateGetDigestData,
257
343
  validateOnBoardingSignDocumentData,
344
+ createPayloadGetDigest,
258
345
  };
@@ -129,18 +129,23 @@ export const httpRequest = async (path, data = {}, method = "POST") => {
129
129
  let dataToSend;
130
130
  const requestConfig = {};
131
131
 
132
- const hasFile = Object.values(data).some(
133
- (value) => typeof File !== "undefined" && value instanceof File
134
- );
135
- if (hasFile) {
136
- const formData = new FormData();
137
- Object.keys(data).forEach((key) => {
138
- formData.append(key, data[key]);
139
- });
140
- dataToSend = formData;
132
+ if (typeof FormData !== "undefined" && data instanceof FormData) {
133
+ dataToSend = data;
141
134
  } else {
142
- dataToSend = { ...data };
135
+ const hasFile = Object.values(data).some(
136
+ (value) => typeof File !== "undefined" && value instanceof File
137
+ );
138
+ if (hasFile) {
139
+ const formData = new FormData();
140
+ Object.keys(data).forEach((key) => {
141
+ formData.append(key, data[key]);
142
+ });
143
+ dataToSend = formData;
144
+ } else {
145
+ dataToSend = { ...data };
146
+ }
143
147
  }
148
+
144
149
  try {
145
150
  let response;
146
151
  switch (method.toUpperCase()) {
@@ -24,9 +24,32 @@ jest.mock("../../src/utils/helpers", () => ({
24
24
  validateGetDocsData: jest.fn(),
25
25
  validateGetDigestData: jest.fn(),
26
26
  validateOnBoardingSignDocumentData: jest.fn(),
27
+ createPayloadGetDigest: jest.fn(),
27
28
  }));
28
29
 
29
30
  describe("API - Signatures", () => {
31
+ beforeAll(() => {
32
+ // FormData no existe en el entorno de Node.js de Jest.
33
+ // Creamos una simulación (mock) que incluye los métodos 'append' y 'forEach'.
34
+ global.FormData = class FormData {
35
+ constructor() {
36
+ this.data = new Map();
37
+ }
38
+ append(key, value) {
39
+ this.data.set(key, value);
40
+ }
41
+ forEach(callback) {
42
+ // Delegamos la llamada forEach al Map interno.
43
+ this.data.forEach((value, key) => callback(value, key));
44
+ }
45
+ };
46
+ });
47
+
48
+ afterAll(() => {
49
+ // Limpiamos la simulación global después de las pruebas.
50
+ delete global.FormData;
51
+ });
52
+
30
53
  afterEach(() => {
31
54
  jest.resetAllMocks();
32
55
  });
@@ -37,6 +60,12 @@ describe("API - Signatures", () => {
37
60
  integrationType: INTEGRATION_TYPE.ONBOARDING,
38
61
  });
39
62
  const signerData = { typedoc: "V", doc: "12345678" };
63
+
64
+ const formData = new FormData();
65
+ formData.append("typedoc", "V");
66
+ formData.append("doc", "12345678");
67
+
68
+ helpers.validateOnBoardingSignerData.mockReturnValue(formData);
40
69
  httpRequest.mockResolvedValue({ success: true });
41
70
 
42
71
  await addSigner(signerData);
@@ -45,8 +74,8 @@ describe("API - Signatures", () => {
45
74
  signerData
46
75
  );
47
76
  expect(httpRequest).toHaveBeenCalledWith(
48
- "services/api/documents/signing",
49
- signerData,
77
+ "services/api/documents/signingsdk",
78
+ formData,
50
79
  "POST"
51
80
  );
52
81
  });
@@ -177,12 +206,17 @@ describe("API - Signatures", () => {
177
206
  const mockResponse = { data: { digest: "test-digest" } };
178
207
  httpRequest.mockResolvedValue(mockResponse);
179
208
 
209
+ const expectedFormData = new FormData();
210
+ expectedFormData.append("publickey", "test-cert");
211
+ helpers.createPayloadGetDigest.mockReturnValue(expectedFormData);
212
+
180
213
  const result = await getDigest(signData);
181
214
 
182
215
  expect(helpers.validateGetDigestData).toHaveBeenCalledWith(signData);
216
+ expect(helpers.createPayloadGetDigest).toHaveBeenCalledWith(signData);
183
217
  expect(httpRequest).toHaveBeenCalledWith(
184
218
  `services/api/documents/getdigest/${signData.signatureId}`,
185
- { publickey: signData.cert },
219
+ expectedFormData,
186
220
  "POST"
187
221
  );
188
222
  expect(result).toBeInstanceOf(ApacuanaSuccess);
@@ -243,20 +277,33 @@ describe("API - Signatures", () => {
243
277
  const mockResponse = { success: true };
244
278
  httpRequest.mockResolvedValue(mockResponse);
245
279
 
280
+ const expectedFormData = new FormData();
281
+ expectedFormData.append(
282
+ "positions",
283
+ JSON.stringify(
284
+ signData.signature.positions.map((p) => ({
285
+ x: p.x,
286
+ y: p.y,
287
+ page: p.page,
288
+ status: 1,
289
+ }))
290
+ )
291
+ );
292
+ expectedFormData.append("publickey", signData.cert);
293
+ expectedFormData.append("signeddigest", signData.signedDigest);
294
+
295
+ helpers.validateOnBoardingSignDocumentData.mockReturnValue(
296
+ expectedFormData
297
+ );
298
+
246
299
  const result = await signDocument(signData);
247
300
 
248
- expect(
249
- helpers.validateOnBoardingSignDocumentData
250
- ).toHaveBeenCalledWith(signData);
301
+ expect(helpers.validateOnBoardingSignDocumentData).toHaveBeenCalledWith(
302
+ signData
303
+ );
251
304
  expect(httpRequest).toHaveBeenCalledWith(
252
305
  `services/api/documents/sign/${signData.signature.id}`,
253
- {
254
- positions: JSON.stringify([
255
- { x: 0.1, y: 0.2, page: 1, status: 1 },
256
- ]),
257
- publickey: signData.cert,
258
- signeddigest: signData.signedDigest,
259
- },
306
+ expectedFormData,
260
307
  "PUT"
261
308
  );
262
309
  expect(result).toBeInstanceOf(ApacuanaSuccess);
@@ -306,10 +353,18 @@ describe("API - Signatures", () => {
306
353
 
307
354
  it("should throw an error if data or data.file is not provided", async () => {
308
355
  await expect(uploadSignatureVariant(null)).rejects.toThrow(
309
- new ApacuanaAPIError("El parámetro 'file' es requerido.", 400, "INVALID_PARAMETER")
356
+ new ApacuanaAPIError(
357
+ "El parámetro 'file' es requerido.",
358
+ 400,
359
+ "INVALID_PARAMETER"
360
+ )
310
361
  );
311
362
  await expect(uploadSignatureVariant({})).rejects.toThrow(
312
- new ApacuanaAPIError("El parámetro 'file' es requerido.", 400, "INVALID_PARAMETER")
363
+ new ApacuanaAPIError(
364
+ "El parámetro 'file' es requerido.",
365
+ 400,
366
+ "INVALID_PARAMETER"
367
+ )
313
368
  );
314
369
  });
315
370
 
@@ -325,7 +380,9 @@ describe("API - Signatures", () => {
325
380
 
326
381
  it("should throw an error if file is not a PNG", async () => {
327
382
  const invalidFile = new File([], "test.txt", { type: "text/plain" });
328
- await expect(uploadSignatureVariant({ file: invalidFile })).rejects.toThrow(
383
+ await expect(
384
+ uploadSignatureVariant({ file: invalidFile })
385
+ ).rejects.toThrow(
329
386
  new ApacuanaAPIError(
330
387
  "El archivo debe ser de tipo PNG (image/png).",
331
388
  400,