UNPKG

mtt-simple

Version:

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

203 lines (182 loc) 7.47 kB
'use strict' require('./components.typedefs') const utils = require('../utils') const $ = require('jquery') /** * @typedef {Object} S3InputFileUploaderOpsTypedef * @property {number} idEtapa - identificador de etapa usado para regisgtrar archivo en repositorio S3 de SIMPLE * @property {number} maxSize - tamaño máximo por omisión, en bytes (8) * @property {number} chunkSize - tamaño máximo de la partición definido: 5242880, 6291456, 8388608 * @property {string} baseUrl - url el servidor de trabajo * @property {string} token - token CSRF usado en encabezado de POST para servicio */ /** @type {S3InputFileUploaderOpsTypedef} */ const S3_FILE_UPLOADER_DEFAULTS = { idEtapa: utils.etapaActual().idEtapa, maxSize: 5242880, // rebajado a 5MB para todos los trámites, 10485760, chunkSize: 5242880, // tamaños permitidos, otros fallan : 5242880, 6291456, 8388608 // singleFileMaxSize: 5242800, // cambiado para usar solo el tamaño de chunk baseUrl: `${window.location.protocol}//${window.location.host}`, token: utils.obtenerTokenCSRF() } /** Administra la carga del documento al repositorio indicando avances y errores para ser usados en la modificación de la UI */ class S3InputFileUploader { /** * @param {any} inputfile - input type=file conteniendo el archivo seleccionado * @param {S3InputFileUploaderOpsTypedef} cfg - configuraciones */ constructor(idCampo, inputfile, cfg) { /** @type {S3InputFileUploaderOpsTypedef} */ this.cfg = Object.assign({}, S3_FILE_UPLOADER_DEFAULTS, cfg || {}) this.idCampo = idCampo const self = this this._$inputFile = $(inputfile) this._$inputFile.on('change', function (evt) { if (!this.files || this.files.length <= 0) { return } $(this).trigger('load-start') self.uploadFile(this.files[0]) .catch(err => console.log('error', err)) }) } /** * @param {Blob} file */ async uploadFile(file) { const totalSize = file.size let totalSegments = 1 if (totalSize > this.cfg.maxSize) { this._$inputFile.trigger('error', { message: `Tamaño del archivo (${utils._format(totalSize)} bytes) supera el máximo permitido (${utils._format(this.cfg.maxSize)} bytes)` }) return } if (totalSize > this.cfg.chunkSize) { const rest = totalSize % this.cfg.chunkSize totalSegments = ((totalSize - rest) / this.cfg.chunkSize) + (rest > 0 ? 1 : 0) } let fileUrl = '' for (let segmentNumber = 1; segmentNumber <= totalSegments; segmentNumber++) { const chunk = await this._getFileSegment(file, segmentNumber, totalSegments, this.cfg.chunkSize) const chunkLoaded = await this._uploadSegment(chunk) fileUrl = chunkLoaded.url } return { name: file.name, size: file.size, contentType: file.type, url: fileUrl, urlOrig: null } } /** * @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 {any} 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, contentType: file.type, totalSegments } reader.onload = (evt) => { if (evt.target.error) { reject(evt.target.error) } else { chunk.content = evt.target.result resolve(chunk) } } reader.readAsArrayBuffer(content); }) } _calculateUploadProgress(segmento, tamanoParticion, porcionCargada, contenidoTotal) { const totalCargado = (segmento - 1) * tamanoParticion + porcionCargada return Math.round((totalCargado * 100) / contenidoTotal) } /** * @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.upload.addEventListener("progress", function (chunkNumber, totalSegments, chunkSize, totalSize) { return function (evt) { const totalLoaded = self._calculateUploadProgress(chunkNumber, self.cfg.chunkSize, evt.loaded, totalSize) self._$inputFile.trigger('progress', { chunkNumber, totalSegments, chunkSize, totalSize, chunkLoaded: evt.loaded, totalLoaded }) } }(chunk.segmentNumber, chunk.totalSegments, content.byteLength, chunk.totalSize)); 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); self._$inputFile.trigger('error', [{ message: uploadStatus.error }]) } else { const uploadedFile = { segmentNumber: chunkNumber, totalSegments, name: filename, url: `${self.cfg.baseUrl}${uploadStatus.URL}`, contentType: chunk.contentType, totalSize } self._$inputFile.trigger(chunkNumber === totalSegments ? 'complete' : 'partial', [uploadedFile]) resolve(uploadedFile) } } else { throw new Error('Error en la carga servidor informa STATUS=' + evt.target.status) } } catch (e) { self._$inputFile.trigger('error', [{ message: e.message, evt }]) reject(e) return; } } }(chunk.segmentNumber, chunk.totalSegments, chunk.filename, chunk.totalSize)); xhr.addEventListener("error", function (evt) { console.error('Error al enviar', evt); self._$inputFile.trigger('error', [evt]) reject(evt) }); xhr.addEventListener("abort", function (evt) { console.error('Abortado al enviar', evt) self._$inputFile.trigger('abort', [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', 'application/octet-stream'); xhr.send(content); }) } } module.exports = { S3_FILE_UPLOADER_DEFAULTS, S3InputFileUploader }