207 lines
5.1 KiB
JavaScript

import { v4 as uuidv4 } from 'uuid';
import Axios from 'axios';
import { baseURLApi } from '../../config';
function extractExtensionFrom(filename) {
if (!filename) {
return null;
}
const regex = /(?:\.([^.]+))?$/;
return regex.exec(filename)[1];
}
export default class FileUploader {
static validate(file, schema) {
if (!schema) {
return;
}
if (schema.image) {
if (!file.type.startsWith('image')) {
throw new Error('You must upload an image');
}
}
if (schema.size && file.size > schema.size) {
throw new Error('File is too big.');
}
const extension = extractExtensionFrom(file.name);
if (schema.formats && !schema.formats.includes(extension)) {
throw new Error('Invalid format');
}
}
static async upload(path, file, schema) {
try {
this.validate(file, schema);
} catch (error) {
return Promise.reject(error);
}
const extension = extractExtensionFrom(file.name);
const id = uuidv4();
const filename = `${id}.${extension}`;
const privateUrl = `${path}/${filename}`;
const publicUrl = await this.uploadToServer(file, path, filename);
return {
id: id,
name: file.name,
sizeInBytes: file.size,
privateUrl,
publicUrl,
new: true,
};
}
static async uploadChunked(path, file, schema, options = {}) {
try {
this.validate(file, schema);
} catch (error) {
return Promise.reject(error);
}
const chunkSize =
Number(options.chunkSize) > 0
? Number(options.chunkSize)
: 5 * 1024 * 1024;
const maxRetries =
Number(options.maxRetries) > 0 ? Number(options.maxRetries) : 3;
const onProgress =
typeof options.onProgress === 'function' ? options.onProgress : null;
const onStatus =
typeof options.onStatus === 'function' ? options.onStatus : null;
const signal = options.signal || null;
const extension = extractExtensionFrom(file.name);
const id = uuidv4();
const filename = extension ? `${id}.${extension}` : id;
const privateUrl = `${path}/${filename}`;
const totalChunks = Math.max(1, Math.ceil(file.size / chunkSize));
if (signal?.aborted) {
throw new Error('Upload aborted');
}
const initResponse = await Axios.post(
'/file/upload-sessions/init',
{
folder: path,
filename,
size: file.size,
contentType: file.type || '',
totalChunks,
},
{ signal },
);
const sessionId = initResponse?.data?.sessionId;
if (!sessionId) {
throw new Error('Upload session was not created');
}
for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex += 1) {
if (signal?.aborted) {
throw new Error('Upload aborted');
}
const start = chunkIndex * chunkSize;
const end = Math.min(file.size, start + chunkSize);
const chunk = file.slice(start, end);
if (onStatus) {
onStatus('uploading', {
chunkIndex,
totalChunks,
});
}
let retry = 0;
while (retry <= maxRetries) {
try {
await Axios.put(
`/file/upload-sessions/${sessionId}/chunks/${chunkIndex}`,
chunk,
{
headers: {
'Content-Type': 'application/octet-stream',
},
signal,
},
);
break;
} catch (error) {
if (signal?.aborted) {
throw new Error('Upload aborted');
}
retry += 1;
if (retry > maxRetries) {
throw error;
}
await new Promise((resolve) => setTimeout(resolve, 500 * retry));
}
}
if (onProgress) {
const percent = Math.round(((chunkIndex + 1) / totalChunks) * 100);
onProgress(percent, {
chunkIndex: chunkIndex + 1,
totalChunks,
});
}
}
if (signal?.aborted) {
throw new Error('Upload aborted');
}
if (onStatus) {
onStatus('finalizing', null);
}
const finalizeResponse = await Axios.post(
`/file/upload-sessions/${sessionId}/finalize`,
null,
{ signal },
);
const responsePublicUrl = finalizeResponse?.data?.url;
const publicUrl = responsePublicUrl
? responsePublicUrl.startsWith('http')
? responsePublicUrl
: `${baseURLApi}${responsePublicUrl}`
: `${baseURLApi}/file/download?privateUrl=${encodeURIComponent(privateUrl)}`;
return {
id,
name: file.name,
sizeInBytes: file.size,
privateUrl,
publicUrl,
new: true,
};
}
static async uploadToServer(file, path, filename) {
const formData = new FormData();
formData.append('file', file);
formData.append('filename', filename);
const uri = `/file/upload/${path}`;
await Axios.post(uri, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
const privateUrl = `${path}/${filename}`;
// Debug logging removed for production builds
return `${baseURLApi}/file/download?privateUrl=${privateUrl}`;
}
}