207 lines
5.1 KiB
JavaScript
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}`;
|
|
}
|
|
}
|