apacuana-sdk-core 0.1.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 (53) hide show
  1. package/.babelrc +3 -0
  2. package/.eslintrc.cjs +22 -0
  3. package/CHANGELOG.md +1 -0
  4. package/README.md +291 -0
  5. package/coverage/clover.xml +171 -0
  6. package/coverage/coverage-final.json +8 -0
  7. package/coverage/lcov-report/base.css +224 -0
  8. package/coverage/lcov-report/block-navigation.js +87 -0
  9. package/coverage/lcov-report/certs.js.html +175 -0
  10. package/coverage/lcov-report/favicon.png +0 -0
  11. package/coverage/lcov-report/index.html +161 -0
  12. package/coverage/lcov-report/prettify.css +1 -0
  13. package/coverage/lcov-report/prettify.js +2 -0
  14. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  15. package/coverage/lcov-report/sorter.js +196 -0
  16. package/coverage/lcov-report/src/api/certs.js.html +295 -0
  17. package/coverage/lcov-report/src/api/index.html +146 -0
  18. package/coverage/lcov-report/src/api/revocations.js.html +214 -0
  19. package/coverage/lcov-report/src/api/signatures.js.html +334 -0
  20. package/coverage/lcov-report/src/api/users.js.html +307 -0
  21. package/coverage/lcov-report/src/config/index.html +116 -0
  22. package/coverage/lcov-report/src/config/index.js.html +175 -0
  23. package/coverage/lcov-report/src/errors/index.html +116 -0
  24. package/coverage/lcov-report/src/errors/index.js.html +145 -0
  25. package/coverage/lcov-report/src/index.html +116 -0
  26. package/coverage/lcov-report/src/index.js.html +286 -0
  27. package/coverage/lcov-report/src/utils/constant.js.html +145 -0
  28. package/coverage/lcov-report/src/utils/helpers.js.html +583 -0
  29. package/coverage/lcov-report/src/utils/index.html +131 -0
  30. package/coverage/lcov.info +303 -0
  31. package/dist/index.js +32927 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/index.mjs +32925 -0
  34. package/dist/index.mjs.map +1 -0
  35. package/jest.config.cjs +7 -0
  36. package/package.json +47 -0
  37. package/rollup.config.js +32 -0
  38. package/src/api/certs.js +70 -0
  39. package/src/api/revocations.js +43 -0
  40. package/src/api/signatures.js +77 -0
  41. package/src/api/users.js +74 -0
  42. package/src/auth/index.js +24 -0
  43. package/src/config/index.js +41 -0
  44. package/src/errors/index.js +20 -0
  45. package/src/index.js +67 -0
  46. package/src/utils/constant.js +20 -0
  47. package/src/utils/helpers.js +166 -0
  48. package/src/utils/httpClient.js +187 -0
  49. package/tests/api/certs.test.js +6 -0
  50. package/tests/api/revocations.test.js +6 -0
  51. package/tests/api/signatures.test.js +6 -0
  52. package/tests/api/users.test.js +115 -0
  53. package/tests/index.test.js +122 -0
@@ -0,0 +1,7 @@
1
+ // jest.config.js
2
+ module.exports = {
3
+ testEnvironment: "node", // Para tests de backend, si tu SDK es universal
4
+ collectCoverage: true,
5
+ coverageDirectory: "coverage",
6
+ // Puedes añadir más configuraciones de cobertura si lo deseas
7
+ };
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "apacuana-sdk-core",
3
+ "version": "0.1.0",
4
+ "description": "Core SDK para interacciones con las APIs de Apacuana.",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "type": "module",
8
+ "scripts": {
9
+ "test": "jest",
10
+ "lint": "eslint src/",
11
+ "build": "rollup -c",
12
+ "prepublishOnly": "npm test && npm run build",
13
+ "validate": "npm run lint && npm test && npm run build"
14
+ },
15
+ "keywords": [
16
+ "apacuana",
17
+ "sdk",
18
+ "core",
19
+ "certificados",
20
+ "firma"
21
+ ],
22
+ "author": "Gega",
23
+ "license": "ISC",
24
+ "devDependencies": {
25
+ "@babel/core": "^7.24.7",
26
+ "@babel/preset-env": "^7.24.7",
27
+ "@rollup/plugin-babel": "^6.0.4",
28
+ "@rollup/plugin-commonjs": "^26.0.1",
29
+ "@rollup/plugin-json": "^6.1.0",
30
+ "@rollup/plugin-node-resolve": "^15.2.3",
31
+ "eslint": "^8.57.0",
32
+ "eslint-config-airbnb-base": "^15.0.0",
33
+ "eslint-config-prettier": "^10.1.8",
34
+ "eslint-plugin-import": "^2.29.1",
35
+ "jest": "^29.7.0",
36
+ "rollup": "^4.18.0"
37
+ },
38
+ "engines": {
39
+ "node": ">=14.0.0"
40
+ },
41
+ "dependencies": {
42
+ "asn1js": "^3.0.6",
43
+ "axios": "^1.11.0",
44
+ "crypto-js": "^4.2.0",
45
+ "pkijs": "^3.2.5"
46
+ }
47
+ }
@@ -0,0 +1,32 @@
1
+ import babel from '@rollup/plugin-babel';
2
+ import { nodeResolve } from '@rollup/plugin-node-resolve';
3
+ import commonjs from '@rollup/plugin-commonjs';
4
+ import json from '@rollup/plugin-json'; // Add this import
5
+
6
+ export default {
7
+ input: 'src/index.js',
8
+ output: [
9
+ {
10
+ file: 'dist/index.js',
11
+ format: 'cjs',
12
+ sourcemap: true,
13
+ },
14
+ {
15
+ file: 'dist/index.mjs',
16
+ format: 'es',
17
+ sourcemap: true,
18
+ },
19
+ ],
20
+ plugins: [
21
+ json(), // Add this plugin before nodeResolve
22
+ nodeResolve({
23
+ preferBuiltins: false,
24
+ }),
25
+ commonjs(),
26
+ babel({
27
+ babelHelpers: 'bundled',
28
+ exclude: 'node_modules/**',
29
+ }),
30
+ ],
31
+ external: ['axios'], // Mark axios as external to prevent bundling issues
32
+ };
@@ -0,0 +1,70 @@
1
+ import CryptoJS from "crypto-js";
2
+ import { getConfig } from "../config";
3
+ import { INTEGRATION_TYPE } from "../utils/constant";
4
+ import { helpers } from "../utils/helpers";
5
+ import { ApacuanaAPIError } from "../errors/index";
6
+ import { httpRequest } from "../utils/httpClient";
7
+
8
+ const generateCertOnBoarding = async () => {
9
+ try {
10
+ const config = getConfig();
11
+ const { userData } = config;
12
+ const keyPair = await helpers.generateKeyPair();
13
+ const { csrBase64 } = await helpers.generateCSR(keyPair, userData);
14
+
15
+ const body = {
16
+ csr: CryptoJS.AES.encrypt(csrBase64, config.secretKey, {
17
+ mode: CryptoJS.mode.ECB,
18
+ padding: CryptoJS.pad.Pkcs7,
19
+ }).toString(),
20
+ };
21
+
22
+ const response = await httpRequest(
23
+ "services/api/register/certificate",
24
+ body,
25
+ "POST"
26
+ );
27
+
28
+ const { cert } = response;
29
+ if (!cert) {
30
+ throw new ApacuanaAPIError(
31
+ "La respuesta de la API no contiene el certificado.",
32
+ response.status,
33
+ "INVALID_API_RESPONSE"
34
+ );
35
+ }
36
+
37
+ const exportedPrivateKey = await helpers.exportPrivateKey(
38
+ keyPair.privateKey
39
+ );
40
+
41
+ return { cert, privateKey: exportedPrivateKey };
42
+ } catch (error) {
43
+ // Se lanza el error capturado, que ya será de tipo ApacuanaAPIError o un Error nativo.
44
+ if (error instanceof ApacuanaAPIError) {
45
+ throw error;
46
+ }
47
+ throw new Error(`Fallo en la generación del certificado: ${error.message}`);
48
+ }
49
+ };
50
+
51
+ export const generateCert = async () => {
52
+ const { integrationType } = getConfig();
53
+
54
+ if (integrationType === INTEGRATION_TYPE.ONBOARDING) {
55
+ await generateCertOnBoarding();
56
+ } else if (integrationType === INTEGRATION_TYPE.ONPREMISE) {
57
+ // eslint-disable-next-line no-console
58
+ console.log("-> Lógica para On-premise");
59
+ // Aquí iría la llamada a httpRequest para el endpoint de on-premise
60
+ // return httpRequest('/certs/generate-onpremise', data);
61
+ }
62
+ };
63
+
64
+ export const getCertStatus = () => {
65
+ const config = getConfig();
66
+ const status = helpers.getCertificateStatus(config.userData, false);
67
+ return {
68
+ status,
69
+ };
70
+ };
@@ -0,0 +1,43 @@
1
+ // src/api/revocations.js
2
+ import { httpRequest } from '../utils/httpClient';
3
+ import { ApacuanaAPIError } from '../errors/index';
4
+
5
+ /**
6
+ * Simula la solicitud de revocación de un certificado.
7
+ * @param {string} reasonCode - Código o descripción del motivo de la revocación.
8
+ * @returns {Promise<object>} Objeto con el estado de la solicitud de revocación.
9
+ */
10
+ const requestRevocation = async (reasonCode) => {
11
+ console.log('-> Función \'requestRevocation\' ejecutada.');
12
+ console.log('Parámetros recibidos para solicitud de revocación:', {
13
+ reasonCode,
14
+ });
15
+
16
+ if (!reasonCode) {
17
+ throw new Error(
18
+ 'ID de certificado y motivo son requeridos para requestRevocation.',
19
+ );
20
+ }
21
+
22
+ try {
23
+ const response = await httpRequest(
24
+ 'users/api/customers/requestcert',
25
+ { reason: reasonCode },
26
+ 'POST',
27
+ );
28
+ console.log('Respuesta del servidor', response);
29
+ if (!response.success) {
30
+ throw new ApacuanaAPIError(
31
+ response.message || 'Error desconocido al solicitar revocación.',
32
+ );
33
+ }
34
+ return {
35
+ revocationStatus: response.status || 'pending',
36
+ requestId: response.id,
37
+ };
38
+ } catch (error) {
39
+ throw new Error(`Fallo en la solicitud de revocación: ${error.message}`);
40
+ }
41
+ };
42
+
43
+ export default requestRevocation;
@@ -0,0 +1,77 @@
1
+ // src/api/signatures.js
2
+ import { httpRequest } from '../utils/httpClient';
3
+ import { ApacuanaAPIError } from '../errors/index';
4
+
5
+ /**
6
+ * Simula la firma de un documento.
7
+ * @param {object} documentData - Datos del documento a firmar.
8
+ * @param {object} signatureData - Datos de la firma.
9
+ * @returns {Promise<object>} Objeto con el resultado de la firma.
10
+ */
11
+ export const signDocument = async (documentData, signatureData) => {
12
+ console.log("-> Función 'signDocument' ejecutada.");
13
+ console.log('Parámetros recibidos para firmar documento:', {
14
+ documentData,
15
+ signatureData,
16
+ });
17
+
18
+ if (!documentData || !signatureData) {
19
+ throw new Error(
20
+ 'Datos de documento y firma son requeridos para signDocument.',
21
+ );
22
+ }
23
+
24
+ try {
25
+ const response = await httpRequest(
26
+ '/docs/sign',
27
+ { documentData, signatureData },
28
+ 'POST',
29
+ );
30
+ console.log(
31
+ 'Respuesta del servidor simulada para firmar documento:',
32
+ response,
33
+ );
34
+ if (!response.success) {
35
+ throw new ApacuanaAPIError(
36
+ response.message || 'Error desconocido al firmar documento.',
37
+ );
38
+ }
39
+ return { signedDocId: response.id, status: response.message };
40
+ } catch (error) {
41
+ throw new Error(`Fallo en la firma del documento: ${error.message}`);
42
+ }
43
+ };
44
+
45
+ /**
46
+ * Simula la adición de un firmante a un documento.
47
+ * @param {object} signerData - Datos del firmante a agregar.
48
+ * @returns {Promise<object>} Objeto con el resultado de agregar el firmante.
49
+ */
50
+ export const addSigner = async (signerData) => {
51
+ console.log("-> Función 'addSigner' ejecutada.");
52
+ console.log('Parámetros recibidos para agregar firmante:', signerData);
53
+
54
+ if (!signerData || typeof signerData !== 'object') {
55
+ throw new Error('Datos de firmante son requeridos para addSigner.');
56
+ }
57
+
58
+ try {
59
+ const response = await httpRequest(
60
+ '/docs/signers',
61
+ signerData,
62
+ 'POST',
63
+ );
64
+ console.log(
65
+ 'Respuesta del servidor simulada para agregar firmante:',
66
+ response,
67
+ );
68
+ if (!response.success) {
69
+ throw new ApacuanaAPIError(
70
+ response.message || 'Error desconocido al agregar firmante.',
71
+ );
72
+ }
73
+ return { signerId: response.id, status: response.message };
74
+ } catch (error) {
75
+ throw new Error(`Fallo al agregar firmante: ${error.message}`);
76
+ }
77
+ };
@@ -0,0 +1,74 @@
1
+ import { httpRequest } from "../utils/httpClient";
2
+ import { ApacuanaAPIError } from "../errors/index";
3
+ import { getConfig } from "../config/index";
4
+
5
+ /**
6
+ * Obtiene el token de un usuario a través de una petición POST.
7
+ * Este método es útil para endpoints que requieren datos en el cuerpo de la petición
8
+ * para buscar un usuario, como un ID de sesión o un token de acceso.
9
+ *
10
+ * @returns {Promise<object>} Objeto con los detalles del usuario.
11
+ * @throws {Error} Si los parámetros de entrada son inválidos.
12
+ * @throws {ApacuanaAPIError} Si ocurre un error en la API de Apacuana.
13
+ */
14
+ const getCustomer = async () => {
15
+ const { verificationId, customerId } = getConfig();
16
+ const body = {
17
+ verificationid: verificationId,
18
+ customerid: customerId,
19
+ };
20
+ // eslint-disable-next-line no-console
21
+ console.log("-> Función 'getCustomer' ejecutada.");
22
+ // eslint-disable-next-line no-console
23
+ console.log("Parámetros recibidos para buscar usuario:", body);
24
+
25
+ // 1. Validación de Parámetros de Entrada (Lado del SDK)
26
+ if (!body || !body.verificationid || !body.customerid) {
27
+ throw new Error(
28
+ "El 'body' con 'userId' es un parámetro requerido para getCustomer."
29
+ );
30
+ }
31
+
32
+ try {
33
+ // 2. Realizar la Llamada al Backend a través del HttpClient
34
+ // Usamos POST ya que la búsqueda podría requerir datos sensibles o complejos en el cuerpo.
35
+ const response = await httpRequest(
36
+ "services/api/register/init", // Endpoint de ejemplo
37
+ body, // Los datos a enviar en el cuerpo de la petición
38
+ "POST"
39
+ );
40
+
41
+ // 3. Procesar la Respuesta del Backend
42
+ // Asumiendo que la respuesta exitosa del backend incluye un objeto 'user'
43
+ // eslint-disable-next-line no-console
44
+ console.log("Respuesta del servidor para el token:", response);
45
+
46
+ if (!response.sessionid || !response.entry) {
47
+ // Si el backend no devuelve el objeto 'user' esperado
48
+ throw new ApacuanaAPIError(
49
+ "La respuesta de la API no contiene el usuario.",
50
+ 200,
51
+ "INVALID_API_RESPONSE"
52
+ );
53
+ }
54
+
55
+ return {
56
+ token: response.sessionid,
57
+ userData: response.entry,
58
+ }; // Retorna el objeto de usuario directamente
59
+ } catch (error) {
60
+ // 4. Manejo de Errores
61
+ // eslint-disable-next-line no-console
62
+ console.error("Error en getCustomer:", error);
63
+ if (error instanceof ApacuanaAPIError) {
64
+ throw error;
65
+ }
66
+ throw new ApacuanaAPIError(
67
+ `Fallo inesperado al obtener el token: ${
68
+ error.message || "Error desconocido"
69
+ }`
70
+ );
71
+ }
72
+ };
73
+
74
+ export default getCustomer;
@@ -0,0 +1,24 @@
1
+ // src/auth/index.js
2
+ // Si httpClient construye los headers directamente de la config,
3
+ // este módulo se vuelve menos crítico o puede desaparecer por ahora.
4
+ // Lo dejaremos por si hay lógica de auth más compleja después.
5
+ import { getConfig } from '../config/index';
6
+
7
+ export const getAuthHeaders = () => {
8
+ const { secretKey, apiKey } = getConfig();
9
+ if (!secretKey || !apiKey) {
10
+ throw new Error('Apacuana SDK: secretKey o apiKey no configuradas.');
11
+ }
12
+ return {
13
+ 'X-Api-Key': apiKey,
14
+ Authorization: `Bearer ${secretKey}`,
15
+ };
16
+ };
17
+
18
+ export const getVerificationId = () => {
19
+ const { verificationId } = getConfig();
20
+ if (!verificationId) {
21
+ throw new Error('Apacuana SDK: verificationId no configurado.');
22
+ }
23
+ return verificationId;
24
+ };
@@ -0,0 +1,41 @@
1
+ // src/config/index.js
2
+ import { INTEGRATION_TYPE } from '../utils/constant';
3
+
4
+ let config = {
5
+ apiUrl: "",
6
+ secretKey: "",
7
+ apiKey: "",
8
+ verificationId: "",
9
+ customerId: "",
10
+ integrationType: "",
11
+ userData: undefined,
12
+ token: undefined,
13
+ };
14
+
15
+ export const setConfig = (newConfig) => {
16
+ const { apiUrl, secretKey, apiKey, verificationId, integrationType } = newConfig;
17
+
18
+ if (!apiUrl || !secretKey || !apiKey || !verificationId || !integrationType) {
19
+ throw new Error(
20
+ "Apacuana SDK: La configuración inicial debe incluir apiUrl, " +
21
+ "secretKey, apiKey, verificationId e integrationType."
22
+ );
23
+ }
24
+
25
+ // Nueva validación para el valor de integrationType
26
+ if (!Object.values(INTEGRATION_TYPE).includes(integrationType)) {
27
+ throw new Error(
28
+ `Apacuana SDK: El valor de integrationType ('${integrationType}') no es válido. ` +
29
+ `Valores permitidos: ${Object.values(INTEGRATION_TYPE).join(', ')}`
30
+ );
31
+ }
32
+
33
+ config = { ...config, ...newConfig };
34
+ // eslint-disable-next-line no-console
35
+ console.log("[Config] SDK Configuración actualizada (desde config):", {
36
+ ...config,
37
+ secretKey: "********",
38
+ });
39
+ };
40
+
41
+ export const getConfig = () => ({ ...config });
@@ -0,0 +1,20 @@
1
+ // src/errors/index.js
2
+
3
+ /**
4
+ * Clase de error personalizada para errores de la API de Apacuana.
5
+ */
6
+ export class ApacuanaAPIError extends Error {
7
+ constructor(message, statusCode = 500, errorCode = null) {
8
+ super(message);
9
+ this.name = "ApacuanaAPIError";
10
+ this.statusCode = statusCode;
11
+ this.errorCode = errorCode;
12
+
13
+ // Mantener el stack trace correcto
14
+ if (Error.captureStackTrace) {
15
+ Error.captureStackTrace(this, ApacuanaAPIError);
16
+ }
17
+ }
18
+ }
19
+
20
+ export default ApacuanaAPIError;
package/src/index.js ADDED
@@ -0,0 +1,67 @@
1
+ // src/index.js
2
+ import { setConfig, getConfig } from "./config/index";
3
+ import { initHttpClient, setAuthToken } from "./utils/httpClient";
4
+ import getCustomer from "./api/users";
5
+ import requestRevocation from "./api/revocations";
6
+ import { getCertStatus } from "./api/certs";
7
+
8
+ const apacuana = {
9
+ /**
10
+ * Inicializa el Apacuana SDK con la configuración necesaria y obtiene
11
+ * los detalles del usuario.
12
+ * @param {object} config - Objeto de configuración.
13
+ * @param {string} config.apiUrl - La URL base de la API de Apacuana.
14
+ * @param {string} config.secretKey - La clave secreta para la autenticación.
15
+ * @param {string} config.apiKey - La clave de API para la autenticación.
16
+ * @param {string} config.verificationId - El ID de verificación del usuario.
17
+ * @param {string} config.customerId - El ID del cliente.
18
+ * @param {string} config.integrationType - El tipo de integración ('onpremise' o 'onboarding').
19
+ * @returns {Promise<object>} retorna los detalles del usuario si la inicialización es exitosa.
20
+ */
21
+ init: async (config) => {
22
+ // eslint-disable-next-line no-console
23
+ console.log(
24
+ "-> apacuana-sdk: Iniciando configuración y validando usuario..."
25
+ );
26
+
27
+ try {
28
+ // 1. Guardar la configuración inicial
29
+ setConfig(config);
30
+
31
+ // 2. Inicializar el cliente HTTP con la configuración guardada
32
+ initHttpClient();
33
+
34
+ // 3. Opcional: Validar que la configuración se guardó correctamente
35
+ const currentConfig = getConfig();
36
+ if (!currentConfig.customerId) {
37
+ throw new Error(
38
+ "Apacuana SDK: La configuración de CustomerId no se ha guardado" +
39
+ " correctamente."
40
+ );
41
+ }
42
+
43
+ // 4. Obtener los detalles del usuario como parte de la inicialización
44
+ const { token, userData } = await getCustomer();
45
+
46
+ // Opcional: Guardar los detalles del usuario en la configuración
47
+ // para acceso futuro si fuera necesario
48
+ setConfig({ ...currentConfig, token, userData });
49
+ setAuthToken(token);
50
+ // eslint-disable-next-line no-console
51
+ console.log(
52
+ "-> apacuana-sdk: Inicialización completa. Usuario validado:",
53
+ token
54
+ );
55
+ return true;
56
+ } catch (error) {
57
+ // eslint-disable-next-line no-console
58
+ console.error("Error durante la inicialización del SDK:", error);
59
+ throw error;
60
+ }
61
+ },
62
+ getConfig,
63
+ requestRevocation,
64
+ getCertStatus,
65
+ };
66
+
67
+ export default apacuana;
@@ -0,0 +1,20 @@
1
+ export const STATUS_CERTIFICATE = {
2
+ request: "SOLICITUD",
3
+ generate: "GENERAR",
4
+ current: "VIGENTE",
5
+ revoque: "REVOCADO",
6
+ expire: "EXPIRADO",
7
+ request_revoque: "SOLICITUD_DE_REVOCACION",
8
+ certificate_another_device: "CERTIFICADO_EN_OTRO_DISPOSITIVO",
9
+ sign_pending: "PENDIENTE_POR_FIRMAR",
10
+ };
11
+
12
+ export const VERIFICATION_STATUS = {
13
+ IN_REGISTER: 0,
14
+ APPROVED: 1,
15
+ };
16
+
17
+ export const INTEGRATION_TYPE = {
18
+ ONBOARDING: 'onboarding',
19
+ ONPREMISE: 'onpremise',
20
+ };