UNPKG

mtt-simple

Version:

Biblioteca de componentes y helpers para desarrollo de formularios en SIMPLE digital

226 lines (204 loc) 7.64 kB
'use strict'; require('./components.typedefs') const $ = require('jquery') const { S3_FILE_UPLOADER_DEFAULTS } = require('./s3-input-file-uploader') const utils = require('../utils') // TODO: extraer lógica de carga del documento de modo de usarla en otros componentes // sin replicar código const S3_URL_FILE_UPLOADER_DEFAULTS = { metodo: 'GET' } /** * Componente que permite efectuar una carga de un documento generado en SIMPLE usando el idCampo asociado * y llevarlo al repositorio de SIMPLE de modo de dejarlo disponible para descarga. */ class S3UrlFileUploader { /** * @param {number|string} refCampo - asociado al campo Documento agregado aal formulario desde donde se extraerá la url * @param {} options */ constructor(refCampo, options) { this.cfg = Object.assign({}, S3_FILE_UPLOADER_DEFAULTS, S3_URL_FILE_UPLOADER_DEFAULTS, options || {}) this.refCampo = refCampo if (typeof refCampo === 'string') { this._$btn = $(`a.btn:contains(${refCampo})`) } else { this.idCampo = refCampo this._$btn = $(`[data-id=${refCampo}] a.btn`) } if (!this._$btn) { throw new Error(`Campo ${refCampo} no fue en encontrado dentro del formulario`) } if (typeof refCampo === 'string') { this.idCampo = parseInt(this._$btn.closest('.campo.control-group').attr('data-id'), 10) } } /** * Descargar documento y agregarlo al repositorio de SIMPLE * @param {string} nombreArchivo - nombre opcional para renombrar documento descargado al momento de * cargarlo al repositorio de SIMPLE */ async addFileToRepo(nombreArchivo) { this.url = this._$btn.attr('href') if (!this.url) { throw new Error(`El campo ${this.refCampo} (${typeof this.refCampo}) no posee una url válida para efectuar la descarga del docuemnto.`) } const file = await this.downloadFileContent(this.url) file.name = nombreArchivo || file.name return this._uploadFile(file, nombreArchivo) } /** * Descargar desde la url el contenido del archivo * @param {string} url * @returns {Promise<BlobFile>} */ async downloadFileContent(url) { if (!url) throw new Error(`S3UrlFileUploader: debe especificar una url para la descarga del documento original`) if (!utils.esUrlValida(url)) throw new Error(`S3UrlFileUploader: La url '${url}' no es válida`) console.log('downloadFileContent', url) return new Promise((res, _rej) => { var oReq = new XMLHttpRequest(); oReq.open(this.cfg.metodo, url, true); oReq.responseType = "blob"; oReq.onload = function (oEvent, oReqRef) { var blob = oReq.response; const regex = /filename="(.*)"/g if (blob) { blob.name = regex.exec(oReq.getResponseHeader('content-disposition'))[1] blob.url = url res(blob) } }; oReq.send(); }) } /** * envio de documento a repositorio S3 usando SIMPLE * @param {BlobFile} file - un objeto Blobl con información del archivo a cargar * @returns {Promise<S3UploadedDocumentTypedef>} */ async _uploadFile(file) { const totalSize = file.size let totalSegments = 1 if (totalSize > this.cfg.maxSize) { throw new Error(`Tamaño del archivo supera el máximo permitido`) } if (totalSize > this.cfg.chunkSize) { const rest = totalSize % this.cfg.chunkSize totalSegments = ((totalSize - rest) / this.cfg.chunkSize) + (rest > 0 ? 1 : 0) } let urlSIMPLE = '' for (let segmentNumber = 1; segmentNumber <= totalSegments; segmentNumber++) { const chunk = await this._getFileSegment(file, segmentNumber, totalSegments, this.cfg.chunkSize) const loaded = await this._uploadSegment(chunk) urlSIMPLE = loaded.url } return { name: file.name, urlOrig: file.url, url: urlSIMPLE, size: file.size, contentType: file.type } } /** * @param {S3ChunkTypedef} chunk */ _getSegmentUploadUrl(chunk) { const { totalSegments, segmentNumber } = chunk const { baseUrl, idEtapa } = this.cfg const urlParts = [baseUrl + '/uploader/datos_s3', this.idCampo, idEtapa] urlParts.push(totalSegments > 1 ? 'multi' : 'single') urlParts.push(...[segmentNumber, totalSegments]) return urlParts.join('/') } /** * @param {BlobFile} file * @param {number} segmentNumber * @param {number} chunkSize * @return {Promise<S3ChunkTypedef>} */ async _getFileSegment(file, segmentNumber, totalSegments, chunkSize) { return new Promise((resolve, reject) => { const reader = new FileReader(); const initialPos = chunkSize * (segmentNumber - 1) const content = file.slice(initialPos, initialPos + chunkSize) const chunk = { totalSize: file.size, size: content.size, segmentNumber, filename: file.name, totalSegments, contentType: file.type } reader.onload = (evt) => { if (evt.target.error) { reject(evt.target.error) } else { chunk.content = evt.target.result resolve(chunk) } } reader.readAsArrayBuffer(content); }) } _getUploadProgress(segmentNumber, chunkSize, previousUploadedSize, totalSize) { const uploaded = (segmentNumber - 1) * chunkSize + previousUploadedSize return Math.round((uploaded * 100) / totalSize) } /** * @param {S3ChunkTypedef} chunk */ async _uploadSegment(chunk) { const self = this return new Promise((resolve, reject) => { var uploadUrl = this._getSegmentUploadUrl(chunk) var xhr = new XMLHttpRequest(); var content = new Uint8Array(chunk.content); xhr.addEventListener('load', function (chunkNumber, totalSegments, filename, totalSize) { return function (evt) { try { if (evt.target.status === 200) { const uploadStatus = JSON.parse(evt.target.response); if (!uploadStatus.success) { console.error(`Servidor informa error segmento ${chunkNumber}`, uploadStatus.error); } else { const uploadedFile = { segmentNumber: chunkNumber, totalSegments, name: filename, url: `${self.cfg.baseUrl}${uploadStatus.URL}`, contentType: chunk.contentType, totalSize } resolve(uploadedFile) } } else { throw new Error('Error en la carga servidor informa STATUS=' + evt.target.status) } } catch (e) { reject(e) return; } } }(chunk.segmentNumber, chunk.totalSegments, chunk.filename, chunk.totalSize)); xhr.addEventListener("error", function (evt) { console.error('Error al enviar', evt); reject(evt) }); xhr.addEventListener("abort", function (evt) { console.error('Abortado al enviar', evt) resolve(null) }); xhr.open("POST", uploadUrl, true); xhr.setRequestHeader('X-CSRF-TOKEN', this.cfg.token); xhr.setRequestHeader('filename', encodeURI(chunk.filename)); xhr.setRequestHeader('Content-Type', chunk.contentType); xhr.send(content); }) } } module.exports = { S3_URL_FILE_UPLOADER_DEFAULTS, S3UrlFileUploader }