apacuana-sdk-core 1.0.0 → 1.2.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/.eslintrc.cjs +22 -0
- package/CHANGELOG.md +1 -0
- package/README.md +794 -0
- package/babel.config.cjs +11 -0
- package/coverage/clover.xml +628 -0
- package/coverage/coverage-final.json +13 -0
- package/coverage/lcov-report/api/index.html +131 -0
- package/coverage/lcov-report/api/signatures.js.html +1093 -0
- package/coverage/lcov-report/api/users.js.html +292 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/certs.js.html +175 -0
- package/coverage/lcov-report/config/index.html +116 -0
- package/coverage/lcov-report/config/index.js.html +208 -0
- package/coverage/lcov-report/errors/index.html +116 -0
- package/coverage/lcov-report/errors/index.js.html +148 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +191 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov-report/src/api/certs.js.html +856 -0
- package/coverage/lcov-report/src/api/faceLiveness.js.html +523 -0
- package/coverage/lcov-report/src/api/index.html +176 -0
- package/coverage/lcov-report/src/api/revocations.js.html +412 -0
- package/coverage/lcov-report/src/api/signatures.js.html +1420 -0
- package/coverage/lcov-report/src/api/users.js.html +466 -0
- package/coverage/lcov-report/src/config/index.html +116 -0
- package/coverage/lcov-report/src/config/index.js.html +256 -0
- package/coverage/lcov-report/src/errors/index.html +116 -0
- package/coverage/lcov-report/src/errors/index.js.html +148 -0
- package/coverage/lcov-report/src/index.html +116 -0
- package/coverage/lcov-report/src/index.js.html +592 -0
- package/coverage/lcov-report/src/success/index.html +116 -0
- package/coverage/lcov-report/src/success/index.js.html +106 -0
- package/coverage/lcov-report/src/utils/constant.js.html +256 -0
- package/coverage/lcov-report/src/utils/helpers.js.html +1195 -0
- package/coverage/lcov-report/src/utils/httpClient.js.html +622 -0
- package/coverage/lcov-report/src/utils/index.html +146 -0
- package/coverage/lcov-report/utils/constant.js.html +145 -0
- package/coverage/lcov-report/utils/httpClient.js.html +646 -0
- package/coverage/lcov-report/utils/index.html +131 -0
- package/coverage/lcov.info +1259 -0
- package/dist/api/certs.d.ts +15 -0
- package/dist/api/faceLiveness.d.ts +6 -0
- package/dist/api/revocations.d.ts +6 -0
- package/dist/api/signatures.d.ts +19 -0
- package/dist/api/users.d.ts +47 -0
- package/dist/config/index.d.ts +22 -0
- package/dist/errors/index.d.ts +10 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +34493 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +34491 -0
- package/dist/index.mjs.map +1 -0
- package/dist/success/index.d.ts +7 -0
- package/dist/types/certs.d.ts +83 -0
- package/dist/types/faceLiveness.d.ts +11 -0
- package/dist/types/revocations.d.ts +51 -0
- package/dist/types/signatures.d.ts +162 -0
- package/dist/types/users.d.ts +260 -0
- package/dist/utils/constant.d.ts +49 -0
- package/dist/utils/helpers.d.ts +25 -0
- package/dist/utils/httpClient.d.ts +3 -0
- package/jest.config.cjs +12 -0
- package/package.json +51 -9
- package/rollup.config.js +32 -0
- package/src/api/certs.js +257 -0
- package/src/api/faceLiveness.js +146 -0
- package/src/api/revocations.js +109 -0
- package/src/api/signatures.js +445 -0
- package/src/api/users.js +127 -0
- package/src/config/index.js +57 -0
- package/src/errors/index.js +21 -0
- package/src/index.js +169 -0
- package/src/success/index.js +8 -0
- package/src/types/certs.js +67 -0
- package/src/types/faceLiveness.js +16 -0
- package/src/types/revocations.js +45 -0
- package/src/types/signatures.js +96 -0
- package/src/types/users.js +73 -0
- package/src/utils/constant.js +57 -0
- package/src/utils/helpers.js +370 -0
- package/src/utils/httpClient.js +179 -0
- package/tests/api/certs.test.js +311 -0
- package/tests/api/faceLiveness.test.js +170 -0
- package/tests/api/revocations.test.js +119 -0
- package/tests/api/signatures.test.js +530 -0
- package/tests/api/users.test.js +117 -0
- package/tests/index.test.js +131 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import CryptoJS from "crypto-js";
|
|
2
|
+
import { getCrypto } from "pkijs";
|
|
3
|
+
import {
|
|
4
|
+
INTEGRATION_TYPE,
|
|
5
|
+
STATUS_CERTIFICATE,
|
|
6
|
+
VERIFICATION_STATUS,
|
|
7
|
+
} from "./constant";
|
|
8
|
+
import { ApacuanaAPIError } from "../errors/index";
|
|
9
|
+
|
|
10
|
+
const KEY_HEX = "dRgUkXp2s5v8y/B?";
|
|
11
|
+
|
|
12
|
+
const getCertificateStatus = (
|
|
13
|
+
userData,
|
|
14
|
+
certificateInDevice,
|
|
15
|
+
integrationType
|
|
16
|
+
) => {
|
|
17
|
+
if (!userData) {
|
|
18
|
+
throw new ApacuanaAPIError(
|
|
19
|
+
"El parámetro userData es requerido.",
|
|
20
|
+
404,
|
|
21
|
+
"INVALID_USER_DATA"
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
if (typeof certificateInDevice !== "boolean") {
|
|
25
|
+
throw new ApacuanaAPIError(
|
|
26
|
+
"El parámetro certificateInDevice es requerido y debe ser un valor booleano.",
|
|
27
|
+
404,
|
|
28
|
+
"INVALID_CERTIFICATE_IN_DEVICE"
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (integrationType === INTEGRATION_TYPE.ONBOARDING) {
|
|
33
|
+
if (userData?.verificationstatus?.id !== VERIFICATION_STATUS.APPROVED)
|
|
34
|
+
return STATUS_CERTIFICATE.request;
|
|
35
|
+
if (
|
|
36
|
+
userData?.certifiedid &&
|
|
37
|
+
!userData?.certificatestatus?.cangenerate &&
|
|
38
|
+
!userData?.requestscert?.pending &&
|
|
39
|
+
!certificateInDevice &&
|
|
40
|
+
userData?.certificatestatus !== STATUS_CERTIFICATE.revoque
|
|
41
|
+
)
|
|
42
|
+
return STATUS_CERTIFICATE.certificate_another_device;
|
|
43
|
+
if (userData?.requestscert?.pending)
|
|
44
|
+
return STATUS_CERTIFICATE.request_revoque;
|
|
45
|
+
if (
|
|
46
|
+
!userData?.certificatestatus?.cangenerate &&
|
|
47
|
+
userData?.certificatestatus === STATUS_CERTIFICATE.revoque
|
|
48
|
+
)
|
|
49
|
+
return STATUS_CERTIFICATE.revoque;
|
|
50
|
+
if (userData?.certificatestatus?.cangenerate)
|
|
51
|
+
return STATUS_CERTIFICATE.generate;
|
|
52
|
+
if (userData?.certifiedid) return STATUS_CERTIFICATE.current;
|
|
53
|
+
|
|
54
|
+
return STATUS_CERTIFICATE.revoque;
|
|
55
|
+
}
|
|
56
|
+
if (integrationType === INTEGRATION_TYPE.ONPREMISE) {
|
|
57
|
+
throw new ApacuanaAPIError(
|
|
58
|
+
"Getting certificate status is not supported for integration type: ONPREMISE",
|
|
59
|
+
501,
|
|
60
|
+
"NOT_IMPLEMENTED"
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
throw new ApacuanaAPIError(
|
|
64
|
+
`Unsupported integration type: ${integrationType}`,
|
|
65
|
+
400,
|
|
66
|
+
"UNSUPPORTED_INTEGRATION_TYPE"
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Función para convertir ArrayBuffer a Base64
|
|
71
|
+
const arrayBufferToBase64 = (buffer) => {
|
|
72
|
+
const binary = String.fromCharCode.apply(null, new Uint8Array(buffer));
|
|
73
|
+
return window.btoa(binary);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
async function exportPrivateKey(key) {
|
|
77
|
+
const crypto = getCrypto();
|
|
78
|
+
const exported = await crypto.exportKey("pkcs8", key);
|
|
79
|
+
const exportablePrivateKey = new Uint8Array(exported);
|
|
80
|
+
return arrayBufferToBase64(exportablePrivateKey);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const encryptedCsr = (csr, encryptKey = KEY_HEX) => {
|
|
84
|
+
const body = {
|
|
85
|
+
csr: CryptoJS.AES.encrypt(csr, encryptKey, {
|
|
86
|
+
mode: CryptoJS.mode.ECB,
|
|
87
|
+
padding: CryptoJS.pad.Pkcs7,
|
|
88
|
+
}).toString(),
|
|
89
|
+
};
|
|
90
|
+
return body;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const createPayloadGetDigest = (signData) => {
|
|
94
|
+
const formData = new FormData();
|
|
95
|
+
|
|
96
|
+
// Iterate over the object's own properties
|
|
97
|
+
Object.keys(signData).forEach((key) => {
|
|
98
|
+
// Conditionally handle the 'document' field
|
|
99
|
+
|
|
100
|
+
if (key === "document" && signData[key] instanceof File) {
|
|
101
|
+
// Append the file directly
|
|
102
|
+
formData.append(key, signData[key]);
|
|
103
|
+
} else if (key === "cert") {
|
|
104
|
+
formData.append("publickey", signData[key]);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
return formData;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const validateOnBoardingSignerData = (signerData) => {
|
|
111
|
+
// Create a copy of the original object to avoid modifying the function parameter
|
|
112
|
+
const validatedSignerData = { ...signerData };
|
|
113
|
+
|
|
114
|
+
if (
|
|
115
|
+
!validatedSignerData.name ||
|
|
116
|
+
typeof validatedSignerData.name !== "string"
|
|
117
|
+
) {
|
|
118
|
+
throw new ApacuanaAPIError(
|
|
119
|
+
'El campo "name" es requerido y debe ser una cadena de texto.',
|
|
120
|
+
400,
|
|
121
|
+
"INVALID_PARAMETER_FORMAT"
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Limpia 'reference' si es inválido o está vacío.
|
|
126
|
+
if (
|
|
127
|
+
!validatedSignerData.reference ||
|
|
128
|
+
typeof validatedSignerData.reference !== "string" ||
|
|
129
|
+
validatedSignerData.reference.trim() === ""
|
|
130
|
+
) {
|
|
131
|
+
delete validatedSignerData.reference;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Limpia 'file' si no es una instancia de File.
|
|
135
|
+
if (
|
|
136
|
+
typeof File === "undefined" ||
|
|
137
|
+
!validatedSignerData.file ||
|
|
138
|
+
!(validatedSignerData.file instanceof File)
|
|
139
|
+
) {
|
|
140
|
+
delete validatedSignerData.file;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Valida que solo uno de los dos campos exista después de la limpieza.
|
|
144
|
+
if (validatedSignerData.reference && validatedSignerData.file) {
|
|
145
|
+
throw new ApacuanaAPIError(
|
|
146
|
+
"No se pueden proporcionar 'reference' y 'file' simultáneamente.",
|
|
147
|
+
400,
|
|
148
|
+
"INVALID_PARAMETER_FORMAT"
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (!validatedSignerData.reference && !validatedSignerData.file) {
|
|
153
|
+
throw new ApacuanaAPIError(
|
|
154
|
+
"Se debe proporcionar un campo 'reference' o 'file' válido.",
|
|
155
|
+
400,
|
|
156
|
+
"INVALID_PARAMETER_FORMAT"
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const validDocTypes = ["V", "P", "E"];
|
|
161
|
+
if (
|
|
162
|
+
!validatedSignerData.typedoc ||
|
|
163
|
+
!validDocTypes.includes(validatedSignerData.typedoc)
|
|
164
|
+
) {
|
|
165
|
+
throw new ApacuanaAPIError(
|
|
166
|
+
'El campo "typedoc" es requerido y debe ser uno de los siguientes valores: V, P, E.',
|
|
167
|
+
400,
|
|
168
|
+
"INVALID_PARAMETER_FORMAT"
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (!validatedSignerData.doc || typeof validatedSignerData.doc !== "string") {
|
|
173
|
+
throw new ApacuanaAPIError(
|
|
174
|
+
'El campo "doc" es requerido y debe ser una cadena de texto.',
|
|
175
|
+
400,
|
|
176
|
+
"INVALID_PARAMETER_FORMAT"
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (
|
|
181
|
+
!Array.isArray(validatedSignerData.signature) ||
|
|
182
|
+
validatedSignerData.signature.length === 0
|
|
183
|
+
) {
|
|
184
|
+
throw new ApacuanaAPIError(
|
|
185
|
+
'El campo "signature" es requerido y debe ser un array no vacío.',
|
|
186
|
+
400,
|
|
187
|
+
"INVALID_PARAMETER_FORMAT"
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
validatedSignerData.signature.forEach((sig, index) => {
|
|
192
|
+
if (!Number.isInteger(sig.page) || sig.page < 1) {
|
|
193
|
+
throw new ApacuanaAPIError(
|
|
194
|
+
`La página de la firma ${
|
|
195
|
+
index + 1
|
|
196
|
+
} debe ser un número entero positivo.`,
|
|
197
|
+
400,
|
|
198
|
+
"INVALID_PARAMETER_FORMAT"
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (typeof sig.x !== "number" || sig.x < 0 || sig.x > 1) {
|
|
203
|
+
throw new ApacuanaAPIError(
|
|
204
|
+
`La coordenada X de la firma ${
|
|
205
|
+
index + 1
|
|
206
|
+
} debe ser un número entre 0 y 1.`,
|
|
207
|
+
400,
|
|
208
|
+
"INVALID_PARAMETER_FORMAT"
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (typeof sig.y !== "number" || sig.y < 0 || sig.y > 1) {
|
|
213
|
+
throw new ApacuanaAPIError(
|
|
214
|
+
`La coordenada Y de la firma ${
|
|
215
|
+
index + 1
|
|
216
|
+
} debe ser un número entre 0 y 1.`,
|
|
217
|
+
400,
|
|
218
|
+
"INVALID_PARAMETER_FORMAT"
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const formData = new FormData();
|
|
224
|
+
Object.keys(validatedSignerData).forEach((key) => {
|
|
225
|
+
if (key === "signature") {
|
|
226
|
+
formData.append(key, JSON.stringify(validatedSignerData[key]));
|
|
227
|
+
} else if (key === "file") {
|
|
228
|
+
formData.append("document", validatedSignerData[key]);
|
|
229
|
+
} else if (validatedSignerData[key] !== undefined) {
|
|
230
|
+
formData.append(key, validatedSignerData[key]);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
return formData;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const validateCsr = (encryptedCSR) => {
|
|
238
|
+
if (
|
|
239
|
+
!encryptedCSR ||
|
|
240
|
+
typeof encryptedCSR !== "object" ||
|
|
241
|
+
!encryptedCSR.csr ||
|
|
242
|
+
typeof encryptedCSR.csr !== "string" ||
|
|
243
|
+
encryptedCSR.csr.trim() === ""
|
|
244
|
+
) {
|
|
245
|
+
throw new ApacuanaAPIError(
|
|
246
|
+
'The "encryptedCSR" parameter is required and must be an object ' +
|
|
247
|
+
'with a non-empty "csr" string property.',
|
|
248
|
+
400, // Bad Request
|
|
249
|
+
"INVALID_PARAMETER_FORMAT"
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const validateGetDocsData = (data) => {
|
|
255
|
+
if (
|
|
256
|
+
!data ||
|
|
257
|
+
typeof data.page === "undefined" ||
|
|
258
|
+
typeof data.size === "undefined"
|
|
259
|
+
) {
|
|
260
|
+
throw new ApacuanaAPIError(
|
|
261
|
+
"Los parámetros 'page' y 'size' son requeridos.",
|
|
262
|
+
400,
|
|
263
|
+
"INVALID_PARAMETER"
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (
|
|
268
|
+
typeof data.status !== "undefined" &&
|
|
269
|
+
![-1, 0, 1, 2].includes(data.status)
|
|
270
|
+
) {
|
|
271
|
+
throw new ApacuanaAPIError(
|
|
272
|
+
"El parámetro 'status' no es válido. Los valores permitidos son: -1, 0, 1, 2.",
|
|
273
|
+
400,
|
|
274
|
+
"INVALID_PARAMETER"
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const validateGetDigestData = (signData) => {
|
|
280
|
+
if (
|
|
281
|
+
!signData ||
|
|
282
|
+
!signData.cert ||
|
|
283
|
+
typeof signData.cert !== "string" ||
|
|
284
|
+
!signData.signatureId ||
|
|
285
|
+
typeof signData.signatureId !== "string"
|
|
286
|
+
) {
|
|
287
|
+
throw new ApacuanaAPIError(
|
|
288
|
+
"Los parámetros 'cert' y 'signatureId' son requeridos y deben ser cadenas de texto.",
|
|
289
|
+
400,
|
|
290
|
+
"INVALID_PARAMETER"
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const validateOnBoardingSignDocumentData = (signData) => {
|
|
296
|
+
if (!signData || typeof signData !== "object") {
|
|
297
|
+
throw new ApacuanaAPIError(
|
|
298
|
+
"Sign data is required and must be an object.",
|
|
299
|
+
400,
|
|
300
|
+
"INVALID_PARAMETER"
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const { signature, cert, signedDigest } = signData;
|
|
305
|
+
|
|
306
|
+
if (!signature || typeof signature !== "object" || !signature.id) {
|
|
307
|
+
throw new ApacuanaAPIError(
|
|
308
|
+
"Signature object with an 'id' property is required.",
|
|
309
|
+
400,
|
|
310
|
+
"INVALID_PARAMETER"
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (!Array.isArray(signature.positions)) {
|
|
315
|
+
throw new ApacuanaAPIError(
|
|
316
|
+
"Signature 'positions' must be an array.",
|
|
317
|
+
400,
|
|
318
|
+
"INVALID_PARAMETER"
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (!cert || typeof cert !== "string") {
|
|
323
|
+
throw new ApacuanaAPIError(
|
|
324
|
+
"Certificate 'cert' is required and must be a string.",
|
|
325
|
+
400,
|
|
326
|
+
"INVALID_PARAMETER"
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (!signedDigest || typeof signedDigest !== "string") {
|
|
331
|
+
throw new ApacuanaAPIError(
|
|
332
|
+
"Signed digest 'signedDigest' is required and must be a string.",
|
|
333
|
+
400,
|
|
334
|
+
"INVALID_PARAMETER"
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const formData = new FormData();
|
|
339
|
+
formData.append(
|
|
340
|
+
"positions",
|
|
341
|
+
JSON.stringify(
|
|
342
|
+
signature.positions.map((p) => ({
|
|
343
|
+
x: p.x,
|
|
344
|
+
y: p.y,
|
|
345
|
+
page: p.page,
|
|
346
|
+
status: 1,
|
|
347
|
+
}))
|
|
348
|
+
)
|
|
349
|
+
);
|
|
350
|
+
formData.append("publickey", cert);
|
|
351
|
+
formData.append("signeddigest", signedDigest);
|
|
352
|
+
if (signData.document && signData.document instanceof File) {
|
|
353
|
+
formData.append("document", signData.document);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return formData;
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
export default {
|
|
360
|
+
getCertificateStatus,
|
|
361
|
+
exportPrivateKey,
|
|
362
|
+
arrayBufferToBase64,
|
|
363
|
+
encryptedCsr,
|
|
364
|
+
validateOnBoardingSignerData,
|
|
365
|
+
validateCsr,
|
|
366
|
+
validateGetDocsData,
|
|
367
|
+
validateGetDigestData,
|
|
368
|
+
validateOnBoardingSignDocumentData,
|
|
369
|
+
createPayloadGetDigest,
|
|
370
|
+
};
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// src/utils/httpClient.js
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
import { ApacuanaAPIError } from "../errors/index";
|
|
4
|
+
import { getConfig } from "../config/index";
|
|
5
|
+
|
|
6
|
+
let axiosInstance;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Inicializa el cliente HTTP (Axios) para todas las peticiones del SDK.
|
|
10
|
+
* @throws {Error} Si la configuración es incompleta.
|
|
11
|
+
*/
|
|
12
|
+
export const initHttpClient = () => {
|
|
13
|
+
const config = getConfig();
|
|
14
|
+
|
|
15
|
+
if (
|
|
16
|
+
!config.apiUrl ||
|
|
17
|
+
!config.secretKey ||
|
|
18
|
+
!config.apiKey ||
|
|
19
|
+
!config.verificationId
|
|
20
|
+
) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
"HttpClient: Configuración de inicialización incompleta. Llama a apacuana.init() primero."
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
axiosInstance = axios.create({
|
|
27
|
+
baseURL: config.apiUrl,
|
|
28
|
+
headers: {
|
|
29
|
+
"APACUANA-API-Key": config.apiKey,
|
|
30
|
+
"APACUANA-SECRET-Key": config.secretKey,
|
|
31
|
+
// La cabecera Authorization se deja vacía al inicio.
|
|
32
|
+
// Se actualizará de forma dinámica con setAuthToken().
|
|
33
|
+
},
|
|
34
|
+
timeout: 30000,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Configura los interceptores de Axios
|
|
38
|
+
axiosInstance.interceptors.response.use(
|
|
39
|
+
(response) => {
|
|
40
|
+
// Maneja errores lógicos de la API (si el backend devuelve status 2xx con { success: false })
|
|
41
|
+
if (response.data && response.data.success === false) {
|
|
42
|
+
const errorMessage =
|
|
43
|
+
response.data?.msg ||
|
|
44
|
+
response.data?.message ||
|
|
45
|
+
"Error lógico desconocido desde la API.";
|
|
46
|
+
throw new ApacuanaAPIError(
|
|
47
|
+
errorMessage,
|
|
48
|
+
response.status,
|
|
49
|
+
response.data.code || "LOGICAL_API_ERROR"
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
return response;
|
|
53
|
+
},
|
|
54
|
+
(error) => {
|
|
55
|
+
// Maneja errores HTTP (status 4xx, 5xx) o errores de red
|
|
56
|
+
// eslint-disable-next-line no-console
|
|
57
|
+
console.error(
|
|
58
|
+
"[HTTP Client] Interceptor de error de Axios:",
|
|
59
|
+
error.response || error.message
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
if (error.response) {
|
|
63
|
+
const { status, data } = error.response;
|
|
64
|
+
const errorMessage =
|
|
65
|
+
data?.msg || data?.message || "Error desconocido desde la API.";
|
|
66
|
+
throw new ApacuanaAPIError(
|
|
67
|
+
errorMessage,
|
|
68
|
+
status,
|
|
69
|
+
data?.code || "API_ERROR"
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
if (error.request) {
|
|
73
|
+
// La petición fue hecha, pero no se recibió respuesta (red caída, CORS, timeout)
|
|
74
|
+
throw new ApacuanaAPIError(
|
|
75
|
+
"Error de conexión a la red, timeout o CORS.",
|
|
76
|
+
0,
|
|
77
|
+
"NETWORK_ERROR"
|
|
78
|
+
);
|
|
79
|
+
} else {
|
|
80
|
+
// Algo más ocurrió al configurar la petición
|
|
81
|
+
throw new ApacuanaAPIError(
|
|
82
|
+
`Error desconocido en la petición: ${error.message}`,
|
|
83
|
+
0,
|
|
84
|
+
"UNKNOWN_REQUEST_ERROR"
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Establece el token de autenticación Bearer para las peticiones subsiguientes.
|
|
93
|
+
* @param {string} token - El token de sesión del usuario.
|
|
94
|
+
* @throws {Error} Si el token es inválido o el cliente no está inicializado.
|
|
95
|
+
*/
|
|
96
|
+
export const setAuthToken = (token) => {
|
|
97
|
+
if (!axiosInstance) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
"HttpClient no inicializado. Llama a apacuana.init() primero."
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
if (!token || typeof token !== "string") {
|
|
103
|
+
throw new Error("Token de sesión inválido.");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Actualizamos la cabecera Authorization de la instancia de Axios.
|
|
107
|
+
axiosInstance.defaults.headers.common.Authorization = `Bearer ${token}`;
|
|
108
|
+
// eslint-disable-next-line no-console
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Envía una petición HTTP a un endpoint de la API.
|
|
113
|
+
* Inyecta automáticamente los parámetros de inicialización en el cuerpo de la petición.
|
|
114
|
+
* @param {string} path - La ruta del endpoint de la API.
|
|
115
|
+
* @param {object} [data={}] - Los datos para enviar en la petición.
|
|
116
|
+
* @param {string} [method='POST'] - El método HTTP.
|
|
117
|
+
* @returns {Promise<object>} Los datos de la respuesta del backend.
|
|
118
|
+
* @throws {ApacuanaAPIError} Si la petición falla.
|
|
119
|
+
*/
|
|
120
|
+
export const httpRequest = async (path, data = {}, method = "POST") => {
|
|
121
|
+
if (!axiosInstance) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
"Apacuana SDK: Cliente HTTP no inicializado. " +
|
|
124
|
+
"Llama a apacuana.init() primero."
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let dataToSend;
|
|
129
|
+
const requestConfig = {};
|
|
130
|
+
|
|
131
|
+
if (typeof FormData !== "undefined" && data instanceof FormData) {
|
|
132
|
+
dataToSend = data;
|
|
133
|
+
} else {
|
|
134
|
+
const hasFile = Object.values(data).some(
|
|
135
|
+
(value) => typeof File !== "undefined" && value instanceof File
|
|
136
|
+
);
|
|
137
|
+
if (hasFile) {
|
|
138
|
+
const formData = new FormData();
|
|
139
|
+
Object.keys(data).forEach((key) => {
|
|
140
|
+
formData.append(key, data[key]);
|
|
141
|
+
});
|
|
142
|
+
dataToSend = formData;
|
|
143
|
+
} else {
|
|
144
|
+
dataToSend = { ...data };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
let response;
|
|
150
|
+
switch (method.toUpperCase()) {
|
|
151
|
+
case "GET":
|
|
152
|
+
response = await axiosInstance.get(path, { params: dataToSend });
|
|
153
|
+
break;
|
|
154
|
+
case "POST":
|
|
155
|
+
response = await axiosInstance.post(path, dataToSend, requestConfig);
|
|
156
|
+
break;
|
|
157
|
+
case "PUT":
|
|
158
|
+
response = await axiosInstance.put(path, dataToSend, requestConfig);
|
|
159
|
+
break;
|
|
160
|
+
case "DELETE":
|
|
161
|
+
response = await axiosInstance.delete(path, {
|
|
162
|
+
data: dataToSend,
|
|
163
|
+
...requestConfig,
|
|
164
|
+
});
|
|
165
|
+
break;
|
|
166
|
+
default:
|
|
167
|
+
throw new ApacuanaAPIError(
|
|
168
|
+
`Método HTTP no soportado: ${method}`,
|
|
169
|
+
405,
|
|
170
|
+
"UNSUPPORTED_HTTP_METHOD"
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return response.data;
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.error(`[HTTP Client] Fallo en la petición a ${path}:`, error);
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
};
|