/** @typedef { import('types/UploadchunkResponse.js').UploadchunkResponse} UploadchunkResponse */ export const CHUNK_SIZE = 8192; /** * An uploaded file instance. * Create a new file data. * * @param {string} name Filename * @param {number} totalChunks Total number of chunks of the file. * @param rawFile The JS file element relation. */ export function FSFileData(name, totalChunks, rawFile ) { return { /** * Filename. * @type {string} */ name, /** * Number of total chunks based on CHUNK_SIZE. * @type {number} */ totalChunks, /** * The JS file element relation. */ rawFile, /** * Upload progression value. * @type {number} */ progressValue: 0, /** * Current chunk uploaded. * @type {number} */ currentChunk: 0, /** * If the upload of the file is finished. * @type {boolean} */ done: false, /** * If there is an error during file uploading. * @type {any|null} */ error: null, /** * UUID v4 for the file. * @type {`${string}-${string}-${string}-${string}-${string}`} */ uuid: crypto.randomUUID(), /** * Current file state */ state: 'public', /** * If the online patcher is enabled */ meta_online_patcher: false, /** * If this patch is a secondary patch. */ meta_secondary_online_patcher: false, /** * Look if this file is currently uploading. * @returns {boolean} */ get isUploading() { return !this.done && !this.error; }, /** * Build API url. * @param {string} section * @returns {string} The API url. */ buildUrl(section) { return `/api/fs/upload-chunk/${section}`; }, /** * Upload the file. * @param {string} section section of the file. * @returns {Promise} */ async upload(section) { if (!this.rawFile) return; // Can't upload in that case. /** * Get CSRF token for uploading request. * @type {string} */ const CSRF = document.querySelector('meta[name=csrf-token]')?.content ?? ''; for (let i = 0; i < this.totalChunks; i++) { if (this.error) return; // Abort the process. const start = i * CHUNK_SIZE; const end = Math.min(start + CHUNK_SIZE, this.rawFile.size); const chunk = this.rawFile.slice(start, end); const formData = new FormData(); formData.append('file', chunk); formData.append('file_uuid', this.uuid); formData.append('current_chunk', i); formData.append('total_chunks', this.totalChunks); formData.append('filename', this.rawFile.name); formData.append('_token', CSRF); // ----- // UPLOAD TIME ! // ----- try { const RESPONSE = await fetch(this.buildUrl(section), {method: 'POST', body: formData}); if (!RESPONSE.ok) // Problem with the request. throw new Error(`${RESPONSE.status} ${RESPONSE.statusText}`); /** @type {UploadchunkResponse} */ const DATA = await RESPONSE.json(); if (DATA.success !== true || DATA.uploaded !== true) // The request reached the file server but could not be sent. throw new Error(`${DATA.error}`); this.currentChunk = i + 1; this.progressValue = Math.round(((i + 1) / this.totalChunks) * 100); if (DATA.finished === true) { this.done = true; return; } } catch (err) { this.error = 'Error on chunk ' + (i + 1) + '. ' + err.message; this.progressValue = 0; return; } } } } }