mirror of
https://github.com/kmc7468/arkvault.git
synced 2026-02-03 23:56:53 +00:00
111 lines
3.0 KiB
TypeScript
111 lines
3.0 KiB
TypeScript
import axios from "axios";
|
|
import { limitFunction } from "p-limit";
|
|
import { CHUNK_SIZE, ENCRYPTION_OVERHEAD } from "$lib/constants";
|
|
import { decryptChunk, concatenateBuffers } from "$lib/modules/crypto";
|
|
|
|
export interface FileDownloadState {
|
|
id: number;
|
|
status:
|
|
| "download-pending"
|
|
| "downloading"
|
|
| "decryption-pending"
|
|
| "decrypting"
|
|
| "decrypted"
|
|
| "canceled"
|
|
| "error";
|
|
progress?: number;
|
|
rate?: number;
|
|
estimated?: number;
|
|
result?: ArrayBuffer;
|
|
}
|
|
|
|
type LiveFileDownloadState = FileDownloadState & {
|
|
status: "download-pending" | "downloading" | "decryption-pending" | "decrypting";
|
|
};
|
|
|
|
let downloadingFiles: FileDownloadState[] = $state([]);
|
|
|
|
export const isFileDownloading = (
|
|
status: FileDownloadState["status"],
|
|
): status is LiveFileDownloadState["status"] =>
|
|
["download-pending", "downloading", "decryption-pending", "decrypting"].includes(status);
|
|
|
|
export const getFileDownloadState = (fileId: number) => {
|
|
return downloadingFiles.find((file) => file.id === fileId && isFileDownloading(file.status));
|
|
};
|
|
|
|
export const getDownloadingFiles = () => {
|
|
return downloadingFiles.filter((file) => isFileDownloading(file.status));
|
|
};
|
|
|
|
export const clearDownloadedFiles = () => {
|
|
downloadingFiles = downloadingFiles.filter((file) => isFileDownloading(file.status));
|
|
};
|
|
|
|
const requestFileDownload = limitFunction(
|
|
async (state: FileDownloadState, id: number) => {
|
|
state.status = "downloading";
|
|
|
|
const res = await axios.get(`/api/file/${id}/download`, {
|
|
responseType: "arraybuffer",
|
|
onDownloadProgress: ({ progress, rate, estimated }) => {
|
|
state.progress = progress;
|
|
state.rate = rate;
|
|
state.estimated = estimated;
|
|
},
|
|
});
|
|
const fileEncrypted: ArrayBuffer = res.data;
|
|
|
|
state.status = "decryption-pending";
|
|
return fileEncrypted;
|
|
},
|
|
{ concurrency: 1 },
|
|
);
|
|
|
|
const decryptFile = limitFunction(
|
|
async (
|
|
state: FileDownloadState,
|
|
fileEncrypted: ArrayBuffer,
|
|
encryptedChunkSize: number,
|
|
dataKey: CryptoKey,
|
|
) => {
|
|
state.status = "decrypting";
|
|
|
|
const chunks: ArrayBuffer[] = [];
|
|
let offset = 0;
|
|
|
|
while (offset < fileEncrypted.byteLength) {
|
|
const nextOffset = Math.min(offset + encryptedChunkSize, fileEncrypted.byteLength);
|
|
chunks.push(await decryptChunk(fileEncrypted.slice(offset, nextOffset), dataKey));
|
|
offset = nextOffset;
|
|
}
|
|
|
|
const fileBuffer = concatenateBuffers(...chunks).buffer;
|
|
state.status = "decrypted";
|
|
state.result = fileBuffer;
|
|
return fileBuffer;
|
|
},
|
|
{ concurrency: 4 },
|
|
);
|
|
|
|
export const downloadFile = async (id: number, dataKey: CryptoKey, isLegacy: boolean) => {
|
|
downloadingFiles.push({
|
|
id,
|
|
status: "download-pending",
|
|
});
|
|
const state = downloadingFiles.at(-1)!;
|
|
|
|
try {
|
|
const fileEncrypted = await requestFileDownload(state, id);
|
|
return await decryptFile(
|
|
state,
|
|
fileEncrypted,
|
|
isLegacy ? fileEncrypted.byteLength : CHUNK_SIZE + ENCRYPTION_OVERHEAD,
|
|
dataKey,
|
|
);
|
|
} catch (e) {
|
|
state.status = "error";
|
|
throw e;
|
|
}
|
|
};
|