mtt-simple
Version:
Biblioteca de componentes y helpers para desarrollo de formularios en SIMPLE digital
203 lines (182 loc) • 7.47 kB
JavaScript
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
}