Skip to content

Instantly share code, notes, and snippets.

@hiromi2424
Created June 6, 2024 04:59
Show Gist options
  • Save hiromi2424/e2b7c5baf4f6e8e5a1c71445a296d6bb to your computer and use it in GitHub Desktop.
Save hiromi2424/e2b7c5baf4f6e8e5a1c71445a296d6bb to your computer and use it in GitHub Desktop.
import axios from 'axios';
/**
* @typedef { Object } UploadMeta
*/
/**
* @typedef { Object } UploadResponse
*/
/**
* @typedef { import('@uppy/core').Uppy<UploadMeta, UploadResponse> } TypedUppy
*/
/**
* @typedef { import('@uppy/core').UppyFile } UppyFile
*/
export default class ResumeHandler {
/**
* @type { TypedUppy }
*/
#uppy;
#recoveredState;
#uploadIds;
#resolve;
/**
* @param { TypedUppy } uppy
*/
constructor(uppy) {
this.#uppy = uppy;
this.#recoveredState = this.#uppy.getState().recoveredState;
this.checkAllFilesRemoved = this.checkAllFilesRemoved.bind(this);
this.resolveAndCleanup = this.resolveAndCleanup.bind(this);
this.resolveAndCleanupWithError = this.resolveAndCleanupWithError.bind(this);
}
/**
* @return {Promise<import('@uppy/core').UploadResult | false>}
*/
async handle() {
if (!this.#shouldHandle()) {
return false;
}
try {
await this.#fixFileStatuses();
} catch (error) {
this.#uppy.log(error, 'error');
}
this.#uploadIds = Object.keys(this.#recoveredState.currentUploads);
return new Promise((resolve) => {
this.#resolve = resolve;
this.#uppy.on('file-removed', this.checkAllFilesRemoved);
this.#uppy.once('upload-error', this.resolveAndCleanupWithError);
this.#uppy.once('complete', this.resolveAndCleanup);
this.#uppy.emit('restore-confirmed');
});
}
#shouldHandle() {
if (!this.#recoveredState) {
return false;
}
return Object.values(this.#recoveredState.files).some(
(file) => file.progress.uploadStarted,
);
}
resolveAndCleanup(result) {
this.#resolve(result);
this.#uppy.off('file-removed', this.checkAllFilesRemoved);
this.#uppy.off('upload-error', this.resolveAndCleanupWithError);
this.#uppy.off('complete', this.resolveAndCleanup);
}
resolveAndCleanupWithEmptyResult() {
this.resolveAndCleanup({ failed: [], successful: [] });
}
resolveAndCleanupWithError(file) {
this.resolveAndCleanup({ failed: [file], successful: [] });
}
checkAllFilesRemoved(removedFile) {
const { currentUploads } = this.#uppy.getState();
if (currentUploads[removedFile.id]) {
return;
}
const currentUploadIds = Object.keys(currentUploads);
if (currentUploadIds.length === 0) {
this.resolveAndCleanupWithEmptyResult();
return;
}
const uploadIdExists = this.#uploadIds.some((uploadId) =>
currentUploadIds.includes(uploadId),
);
if (!uploadIdExists) {
this.resolveAndCleanupWithEmptyResult();
}
}
async #fixFileStatuses() {
const { files } = this.#recoveredState;
await Promise.all(Object.keys(files).map(async (fileID) => {
const file = this.#uppy.getFile(fileID);
if (!file) return;
if (!file.progress.uploadStarted) return;
if (file.progress.uploadComplete) return;
if (!file.s3Multipart) return;
const { key, uploadId } = file.s3Multipart;
if (!key || !uploadId) return;
await this.#fixFileStatus(file, key, uploadId);
}));
}
/**
* @param { TypedUppyFile } file
* @param { string } key
* @param { string } uploadId
* @return { Promise<void> }
*/
async #fixFileStatus(file, key, uploadId) {
try {
const status = await this.constructor.#fetchStatus(uploadId, key);
if (status.aborted) {
this.#forceInitial(file);
} else if (status.completed) {
this.#forceCompleted(file);
}
} catch(error) {
this.#uppy.log(error, 'warning');
}
}
/**
* @param { string } uploadId
* @param { string } key
* @return { Promise<{ aborted: boolean, completed: boolean }> }
*/
static async #fetchStatus(uploadId, key) {
const url = `/s3/multipart/${uploadId}/status?${new URLSearchParams({ key })}`;
const response = await axios.get(url);
return response.data;
}
/**
* @param { TypedUppyFile } file
*/
#forceInitial(file) {
const updates = {
progress: {
...file.progress,
bytesUploaded: 0,
percentage: 0,
uploadStarted: 0, // will be false in uppy >= 4.0.0
uploadComplete: false,
},
s3Multipart: {},
};
Object.assign(file, updates);
this.#uppy.setFileState(file.id, updates);
}
/**
* @param { TypedUppyFile } file
*/
#forceCompleted(file) {
const updates = {
progress: {
...file.progress,
bytesUploaded: file.progress.bytesTotal,
percentage: 100,
uploadComplete: true,
},
};
Object.assign(file, updates);
this.#uppy.setFileState(file.id, updates);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment