apacuana-sdk-web
Version: 
Apacuana SDK for Web
486 lines • 21.1 kB
JavaScript
import ApacuanaCore from "apacuana-sdk-core";
import { ApacuanaWebError, ApacuanaWebErrorCode } from "./lib/errors.js";
import { ApacuanaSuccess } from "./lib/responses.js";
import { checkRecordExists, decryptData, encryptAndStoreValue, encryptData, getRecord, retrieveAndDecryptValue, signDigest, storeValue, } from "./lib/indexedDB.js";
import { createObjectStore, createPKCS12, encryptCSR, exportPrivateKey, generateCSR, generateKeyPair, transformCSR, } from "./lib/index.js";
import "./components/liveness-amplify.js"; // Asegúrate de que esta importación esté presente
export class ApacuanaWeb {
    constructor() {
        this.isInitialized = false;
    }
    // =================================================================
    //
    //                          PUBLIC METHODS
    //
    // =================================================================
    async init(config) {
        try {
            const init = await ApacuanaCore.init(config);
            this.isInitialized = true;
            return init;
        }
        catch (error) {
            if (error instanceof Error && error.name === "ApacuanaAPIError") {
                throw error;
            }
            throw new ApacuanaWebError(`Error initializing Apacuana SDK for Web: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    async getCustomer() {
        this.ensureInitialized();
        try {
            return await ApacuanaCore.getCustomer();
        }
        catch (error) {
            throw error;
        }
    }
    async getRevocationReasons() {
        this.ensureInitialized();
        try {
            return await ApacuanaCore.getRevocationReasons();
        }
        catch (error) {
            throw error;
        }
    }
    async getCertStatus() {
        this.ensureInitialized();
        try {
            const { customerId } = ApacuanaCore.getConfig();
            const isCertificateInDevice = await checkRecordExists(customerId);
            return ApacuanaCore.getCertStatus(isCertificateInDevice);
        }
        catch (error) {
            if (error) {
                throw error;
            }
            throw new ApacuanaWebError("UNKNOWN_ERROR", ApacuanaWebErrorCode.UNKNOWN_ERROR);
        }
    }
    async generateCert(pin) {
        this.ensureInitialized();
        if (!pin || typeof pin !== "string" || pin.trim() === "") {
            throw new ApacuanaWebError("PIN is required and must be a non-empty string.", ApacuanaWebErrorCode.VALIDATION_ERROR);
        }
        try {
            const config = ApacuanaCore.getConfig();
            const userData = config.userData;
            const keyPair = await generateKeyPair();
            if (userData && keyPair) {
                const { csr } = await generateCSR(keyPair, userData.email);
                const csrBase64 = transformCSR(csr);
                const csrEncrypted = encryptCSR(csrBase64);
                const result = await ApacuanaCore.generateCert(csrEncrypted);
                await encryptAndStoreValue("c", result === null || result === void 0 ? void 0 : result.data.cert, userData.id, pin);
                await storeValue("serial", result === null || result === void 0 ? void 0 : result.data.certifiedid, userData.id);
                const exportedPrivateKey = await exportPrivateKey(keyPair.privateKey);
                if (exportedPrivateKey) {
                    await encryptAndStoreValue("pk", exportedPrivateKey, userData.id, pin);
                }
                return result;
            }
        }
        catch (error) {
            if (error) {
                throw error;
            }
            throw new ApacuanaWebError("UNKNOWN_ERROR", ApacuanaWebErrorCode.UNKNOWN_ERROR);
        }
    }
    async getCertTypes() {
        this.ensureInitialized();
        try {
            return await ApacuanaCore.getCertTypes();
        }
        catch (error) {
            throw error;
        }
    }
    async getCertRequerimentsByType(params) {
        this.ensureInitialized();
        try {
            return await ApacuanaCore.getRequerimentsByTypeUser(params);
        }
        catch (error) {
            throw error;
        }
    }
    async uploadSignatureVariant(params) {
        try {
            this.ensureInitialized();
            const result = await ApacuanaCore.uploadSignatureVariant(params);
            return result;
        }
        catch (error) {
            if (error) {
                throw error;
            }
            throw new ApacuanaWebError("UNKNOWN_ERROR", ApacuanaWebErrorCode.UNKNOWN_ERROR);
        }
    }
    async getSignatureVariant() {
        try {
            this.ensureInitialized();
            const result = await ApacuanaCore.getSignatureVariant();
            return result;
        }
        catch (error) {
            if (error) {
                throw error;
            }
            throw new ApacuanaWebError("UNKNOWN_ERROR", ApacuanaWebErrorCode.UNKNOWN_ERROR);
        }
    }
    async deleteSignatureVariant() {
        try {
            this.ensureInitialized();
            const result = await ApacuanaCore.deleteSignatureVariant();
            return result;
        }
        catch (error) {
            if (error) {
                throw error;
            }
            throw new ApacuanaWebError("UNKNOWN_ERROR", ApacuanaWebErrorCode.UNKNOWN_ERROR);
        }
    }
    async getDocsByCustomer(params) {
        this.ensureInitialized();
        try {
            return await ApacuanaCore.getDocs(params);
        }
        catch (error) {
            throw error;
        }
    }
    async addSigner(params) {
        this.ensureInitialized();
        try {
            return await ApacuanaCore.addSigner(params);
        }
        catch (error) {
            throw error;
        }
    }
    async requestRevocation(params) {
        this.ensureInitialized();
        try {
            return await ApacuanaCore.requestRevocation(params);
        }
        catch (error) {
            throw error;
        }
    }
    async createApacuanaUser(params) {
        this.ensureInitialized();
        try {
            return await ApacuanaCore.createApacuanaUser(params);
        }
        catch (error) {
            throw error;
        }
    }
    async isCertificateInDevice() {
        const { customerId } = this.getConfig();
        const isExist = await checkRecordExists(customerId);
        return new ApacuanaSuccess({
            isExist: isExist ? true : false,
        });
    }
    async close() {
        return ApacuanaCore.close();
    }
    async signDocument(params) {
        var _a;
        this.ensureInitialized();
        const { customerId } = this.getConfig();
        try {
            const cert = await retrieveAndDecryptValue("c", customerId, params.pin);
            if (cert) {
                const digest = await ApacuanaCore.getDigest({
                    cert: cert,
                    signatureId: params.signature.id,
                    document: params.document ? params.document : undefined,
                });
                const privateKey = await retrieveAndDecryptValue("pk", customerId, params.pin);
                if (!privateKey) {
                    throw new ApacuanaWebError("Private key not found or could not be decrypted.", ApacuanaWebErrorCode.NOT_FOUND);
                }
                if (!((_a = digest === null || digest === void 0 ? void 0 : digest.data) === null || _a === void 0 ? void 0 : _a.digest)) {
                    throw new ApacuanaWebError("Failed to get digest for signing.", ApacuanaWebErrorCode.UNKNOWN_ERROR);
                }
                const signedDigest = await signDigest(digest.data.digest, privateKey);
                const sign = await ApacuanaCore.signDocument({
                    cert: cert,
                    signature: params.signature,
                    signedDigest,
                    document: params.document ? params.document : undefined,
                });
                return sign;
            }
        }
        catch (error) {
            throw error;
        }
    }
    async exportCertificateInJSON(params) {
        this.ensureInitialized();
        const { customerId } = this.getConfig();
        try {
            if (!params.pin || !params.filename) {
                throw new ApacuanaWebError("PIN and filename are required to generate the integrity seal.", ApacuanaWebErrorCode.VALIDATION_ERROR);
            }
            const pkRecord = await retrieveAndDecryptValue("pk", customerId, params.pin);
            const certRecord = await getRecord(customerId);
            if (!certRecord) {
                throw new ApacuanaWebError("Certificate record not found in device.", ApacuanaWebErrorCode.NOT_FOUND);
            }
            const dataForIntegrityCheck = {
                id: customerId,
                c: certRecord.c, // Agrega el certificado
                pk: pkRecord, // Agrega la clave privada descifrada
            };
            const dataString = JSON.stringify(dataForIntegrityCheck);
            const { key: encryptedData, salt, iv, } = await encryptData(dataString, params.pin);
            const uint8ArrayToObject = (arr) => Object.fromEntries(arr.entries());
            certRecord.serialize = {
                data: uint8ArrayToObject(encryptedData),
                salt: uint8ArrayToObject(salt),
                iv: uint8ArrayToObject(iv),
            };
            const jsonData = JSON.stringify(certRecord, null, 2);
            const blob = new Blob([jsonData], { type: "application/json" });
            const url = URL.createObjectURL(blob);
            const a = document.createElement("a");
            a.href = url;
            const lastDotIndex = params.filename.lastIndexOf(".");
            const baseFilename = lastDotIndex > 0
                ? params.filename.substring(0, lastDotIndex)
                : params.filename;
            a.download = `${baseFilename}.apacuana`;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
            return new ApacuanaSuccess({
                isExport: true,
            });
        }
        catch (error) {
            throw error;
        }
    }
    async startLivenessCheck(container) {
        return new Promise(async (resolve, reject) => {
            let targetContainer = container;
            const isModal = !targetContainer;
            try {
                // 1. Si no se proporciona un contenedor, creamos uno para el modo modal.
                if (isModal) {
                    targetContainer = document.createElement("div");
                    Object.assign(targetContainer.style, {
                        position: "fixed",
                        top: "0",
                        left: "0",
                        width: "100vw",
                        height: "100vh",
                        backgroundColor: "rgba(0, 0, 0, 0.7)",
                        display: "flex",
                        justifyContent: "center",
                        alignItems: "center",
                        zIndex: "9999",
                    });
                    document.body.appendChild(targetContainer);
                }
                else if (!(targetContainer instanceof HTMLElement)) {
                    // Si se proporciona un contenedor pero no es válido, rechazamos.
                    const error = new ApacuanaWebError("El contenedor proporcionado no es un elemento HTML válido.", ApacuanaWebErrorCode.VALIDATION_ERROR);
                    return reject(error);
                }
                const faceLiveness = await ApacuanaCore.createFaceLivenessSession();
                const { sessionId } = faceLiveness.data;
                const livenessElement = document.createElement("apacuana-liveness-amplify");
                // CORRECCIÓN: Usar 'session-id' (kebab-case) para que coincida
                // con la definición del @property en el componente.
                livenessElement.setAttribute("session-id", sessionId);
                const cleanup = () => {
                    livenessElement.removeEventListener("liveness-complete", onComplete);
                    livenessElement.removeEventListener("liveness-error", onError);
                    // 2. Si creamos el contenedor modal, lo eliminamos del body.
                    if (isModal && targetContainer) {
                        document.body.removeChild(targetContainer);
                    }
                    else if (targetContainer === null || targetContainer === void 0 ? void 0 : targetContainer.contains(livenessElement)) {
                        // Si no, solo limpiamos el contenido del contenedor del cliente.
                        targetContainer.removeChild(livenessElement);
                    }
                };
                const onComplete = () => {
                    console.log("Liveness check complete: Success");
                    const validateFaceLiveness = this.handleLivenessSuccess(sessionId);
                    cleanup();
                    resolve(new ApacuanaSuccess(validateFaceLiveness));
                };
                const onError = (event) => {
                    const customEvent = event;
                    const amplifyError = customEvent.detail;
                    console.error("Liveness check failed:", amplifyError);
                    cleanup();
                    const error = new ApacuanaWebError((amplifyError === null || amplifyError === void 0 ? void 0 : amplifyError.error) || "Error en la prueba de vida.", ApacuanaWebErrorCode.UNKNOWN_ERROR);
                    reject(error);
                };
                livenessElement.addEventListener("liveness-complete", onComplete);
                livenessElement.addEventListener("liveness-error", onError);
                // 3. Añadimos el componente al contenedor (el nuestro o el del cliente).
                targetContainer.appendChild(livenessElement);
            }
            catch (error) {
                // Si algo falla, nos aseguramos de limpiar el contenedor modal si lo creamos.
                if (isModal &&
                    targetContainer &&
                    document.body.contains(targetContainer)) {
                    document.body.removeChild(targetContainer);
                }
                const unknownError = new ApacuanaWebError("Ocurrió un error inesperado durante la verificación.", ApacuanaWebErrorCode.UNKNOWN_ERROR);
                console.error("Error en startLivenessCheck:", error);
                reject(unknownError);
            }
        });
    }
    async importCertificate(params) {
        this.ensureInitialized();
        const { file, pin } = params;
        // --- Validación de la extensión .apacuana ---
        const fileName = file.name;
        const fileExtension = fileName
            .substring(fileName.lastIndexOf(".") + 1)
            .toLowerCase();
        if (fileExtension !== "apacuana") {
            throw new ApacuanaWebError("Por favor, selecciona un archivo con extensión .apacuana.", ApacuanaWebErrorCode.VALIDATION_ERROR);
        }
        const fileContent = await this.readFileAsText(file);
        let jsonContent;
        try {
            jsonContent = JSON.parse(fileContent);
        }
        catch (error) {
            console.log(error);
            throw new ApacuanaWebError("Error al procesar el archivo de certificado. Asegúrate de que sea un archivo válido.", ApacuanaWebErrorCode.PARSE_ERROR);
        }
        // --- Validación de la estructura del JSON ---
        const requiredKeys = ["c", "pk", "id", "serialize"];
        const requiredEncryptedKeys = ["iv", "key", "salt"];
        const requiredSerializeKeys = ["data", "salt", "iv"];
        const isValid = requiredKeys.every((key) => key in jsonContent) &&
            requiredEncryptedKeys.every((key) => key in jsonContent.c) &&
            requiredEncryptedKeys.every((key) => key in jsonContent.pk) &&
            requiredSerializeKeys.every((key) => key in jsonContent.serialize);
        if (!isValid) {
            throw new ApacuanaWebError("La estructura del archivo JSON no es válida.", ApacuanaWebErrorCode.VALIDATION_ERROR);
        }
        // --- Verificación de integridad ---
        const toUint8Array = (obj) => new Uint8Array(Object.values(obj));
        const encryptedPayload = {
            key: toUint8Array(jsonContent.serialize.data),
            salt: toUint8Array(jsonContent.serialize.salt),
            iv: toUint8Array(jsonContent.serialize.iv),
        };
        let decryptedDataBuffer;
        try {
            decryptedDataBuffer = await decryptData(encryptedPayload, pin);
        }
        catch (error) {
            console.log(error);
            throw new ApacuanaWebError("Credenciales incorrectas.", ApacuanaWebErrorCode.VALIDATION_ERROR);
        }
        const decryptedDataString = new TextDecoder().decode(decryptedDataBuffer);
        // --- CORRECCIÓN: Reconstruye el objeto de verificación con los datos desencriptados
        const decryptedDataObject = JSON.parse(decryptedDataString);
        const dataForIntegrityCheck = {
            id: decryptedDataObject.id,
            c: decryptedDataObject.c,
            pk: decryptedDataObject.pk,
        };
        const canonicalString = JSON.stringify(dataForIntegrityCheck);
        if (decryptedDataString !== canonicalString) {
            throw new ApacuanaWebError("Error de verificación de integridad. El archivo ha sido alterado o la contraseña es incorrecta.", ApacuanaWebErrorCode.VALIDATION_ERROR);
        }
        const customerId = jsonContent.id;
        const config = this.getConfig();
        if (config.customerId !== customerId) {
            throw new ApacuanaWebError("Error de verificación de usuario. El certificado a importar no pertenece al usuario.", ApacuanaWebErrorCode.VALIDATION_ERROR);
        }
        // --- Nuevo flujo: No se comprueba si el registro existe. Se sobreescribe directamente. ---
        jsonContent.c = {
            iv: toUint8Array(jsonContent.c.iv),
            key: toUint8Array(jsonContent.c.key),
            salt: toUint8Array(jsonContent.c.salt),
        };
        jsonContent.pk = {
            iv: toUint8Array(jsonContent.pk.iv),
            key: toUint8Array(jsonContent.pk.key),
            salt: toUint8Array(jsonContent.pk.salt),
        };
        createObjectStore(jsonContent);
        new ApacuanaSuccess({
            isImport: true,
        });
    }
    // =================================================================
    //
    //                          PRIVATE METHODS
    //
    // =================================================================
    handleLivenessSuccess(sessionId) {
        return ApacuanaCore.validateFaceLiveness({ sessionId });
    }
    getConfig() {
        this.ensureInitialized();
        return ApacuanaCore.getConfig();
    }
    ensureInitialized() {
        if (!this.isInitialized) {
            throw new ApacuanaWebError("ApacuanaWeb SDK has not been initialized. Please call init() first.", ApacuanaWebErrorCode.NOT_INITIALIZED);
        }
    }
    async exportCertificateInP12(params) {
        this.ensureInitialized();
        const { customerId } = this.getConfig();
        try {
            const privateKey = await retrieveAndDecryptValue("pk", customerId, params.pin);
            const cert = await retrieveAndDecryptValue("c", customerId, params.pin);
            let formattedPrivateKey = privateKey;
            if (formattedPrivateKey && cert) {
                if (!formattedPrivateKey.includes("-----BEGIN PRIVATE KEY-----")) {
                    formattedPrivateKey = `-----BEGIN PRIVATE KEY-----\n${formattedPrivateKey}\n-----END PRIVATE KEY-----`;
                }
                const p12Base64 = await createPKCS12(formattedPrivateKey, cert, params.pin);
                const element = document.createElement("a");
                element.setAttribute("href", "data:application/x-pkcs12;base64," + p12Base64);
                element.setAttribute("download", params.filename);
                element.style.display = "none";
                document.body.appendChild(element);
                element.click();
                document.body.removeChild(element);
                return true;
            }
            else {
                throw new ApacuanaWebError("Error in privateKey || cert", ApacuanaWebErrorCode.UNKNOWN_ERROR);
            }
        }
        catch (e) {
            throw e;
        }
    }
    readFileAsText(file) {
        return new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.onload = () => resolve(reader.result);
            reader.onerror = () => reject(reader.error);
            reader.readAsText(file);
        });
    }
}
export const apacuanaWeb = new ApacuanaWeb();
//# sourceMappingURL=index.js.map