@simplito/privmx-webendpoint
Version:
PrivMX Web Endpoint library
217 lines (216 loc) • 7.52 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.FileUploader = exports.StreamReader = exports.FILE_DEFAULT_CHUNK_SIZE = void 0;
exports.downloadFile = downloadFile;
exports.FILE_DEFAULT_CHUNK_SIZE = 1_048_576;
class StreamReader {
_handle;
_offset;
_api;
chunkSize;
hasDataToRead = true;
/**
* Creates an instance of StreamReader.
*
* @param {number} handle - The file handle.
* @param {StoreApi} api {@link StoreApi `StoreApi`} instance
*/
constructor(handle, api, chunkSize) {
this._handle = handle;
this._offset = 0;
this._api = api;
this.chunkSize = chunkSize ?? exports.FILE_DEFAULT_CHUNK_SIZE;
}
static async readFile(api, fileID, chunkSize) {
const fileHandle = await api.openFile(fileID);
const reader = new StreamReader(fileHandle, api, chunkSize);
return reader;
}
/**
* Reads the next chunk of the file.
*
* @returns {Promise<boolean>} A promise that resolves to true if there are more chunks to read, or false if the end of the file is reached.
*/
async *[Symbol.asyncIterator]() {
while (this.hasDataToRead) {
const chunk = await this.readNextChunk();
yield [chunk, this._offset];
}
}
async readNextChunk() {
const chunkSizeToRead = this.chunkSize ?? exports.FILE_DEFAULT_CHUNK_SIZE;
const chunk = await this._api.readFromFile(this._handle, chunkSizeToRead);
//if chunk length is lesser than requested, the end of the file is reached
this.hasDataToRead = chunk.length === chunkSizeToRead;
return chunk;
}
async getFileContent() {
const chunks = [];
while (this.hasDataToRead) {
chunks.push(await this.readNextChunk());
}
await this.close();
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
const fileBuffer = new Uint8Array(totalLength);
let offset = 0;
for (const chunk of chunks) {
fileBuffer.set(chunk, offset);
offset += chunk.length;
}
return fileBuffer;
}
/**
* Aborts the reading process and closes the file handle.
*
* @returns {Promise<string>} A promise that resolves when the file handle is closed.
*/
async abort() {
return this._api.closeFile(this._handle);
}
/**
* Closes the file handle.
*
* @returns {Promise<string>} A promise that resolves when the file handle is closed and returns file ID.
*/
async close() {
return this._api.closeFile(this._handle);
}
}
exports.StreamReader = StreamReader;
class FileUploader {
_size;
offset = 0;
_api;
_reader;
/**
* Creates an instance of FileUploader.
*
* @param {number} handle - The file handle.
* @param {File} file - The data (file content) to upload.
* @param {StoreApi} api {@link StoreApi `StoreApi`} instance
*/
constructor(file, api) {
this._size = file.size;
this._api = api;
this._reader = file.stream().getReader();
}
static async uploadStoreFile({ storeApi, storeId, file, privateMeta, publicMeta, }) {
const meta = {
publicMeta: publicMeta || new Uint8Array(),
privateMeta: privateMeta || new Uint8Array(),
};
const handle = await storeApi.createFile(storeId, meta.publicMeta, meta.privateMeta, file.size);
const streamer = new FileUploader(file, {
closeFile() {
return storeApi.closeFile(handle);
},
writeToFile(chunk) {
return storeApi.writeToFile(handle, chunk);
},
});
return streamer;
}
static async uploadInboxFile({ inboxApi, inboxHandle, preparedFileUpload, }) {
const streamer = new FileUploader(preparedFileUpload.file, {
closeFile() {
return Promise.resolve("");
},
writeToFile(chunk) {
return inboxApi.writeToFile(inboxHandle, preparedFileUpload.handle, chunk);
},
});
return streamer;
}
static async prepareInboxUpload({ inboxApi, file, privateMeta, publicMeta, }) {
const meta = {
publicMeta: publicMeta || new Uint8Array(),
privateMeta: privateMeta || new Uint8Array(),
};
const handle = await inboxApi.createFileHandle(meta.publicMeta, meta.privateMeta, file.size);
return { file: file, handle };
}
/**
* Gets the progress of uploading the file as a percentage.
*
* @returns {number} The progress percentage.
*/
get progress() {
if (this._size === 0)
return 100;
return (this.offset / this._size) * 100;
}
/**
* Sends the next chunk of the file data to the server.
*
* @returns {Promise<boolean>} A promise that resolves to true if there are more chunks to send, or false if all data has been sent.
*/
async sendNextChunk() {
const { done, value } = await this._reader.read();
if (done) {
return false;
}
await this._api.writeToFile(value);
this.offset += value?.length;
return true;
}
async uploadFileContent() {
while (await this.sendNextChunk()) { }
return this.close();
}
/**
* Aborts the uploading process, closes the file handle, and deletes the uploaded part of the file.
*
* @returns {Promise<void>} A promise that resolves when the file handle is closed and the uploaded part is deleted.
*/
async abort() {
await this._api.closeFile();
}
/**
* Closes the file handle.
*
* @returns {Promise<string>} A promise that resolves when the file handle is closed and returns file ID.
*/
async close() {
this._reader.releaseLock();
return this._api.closeFile();
}
}
exports.FileUploader = FileUploader;
/**
* Downloads a file from the server.
*
* @param {StoreApi|InboxApi} api - The API instance used for file operations.
* @param {string} fileId - The ID of the file to download.
* @param {string} [targetFileName] - The target file name for saving the downloaded file.
* @returns {Promise<void>} A promise that resolves when the download is complete.
*/
async function downloadFile(api, fileId, targetFileName) {
const filename = targetFileName || fileId;
const apiReader = await StreamReader.readFile(api, fileId);
if ("showSaveFilePicker" in window && window.isSecureContext) {
//@ts-ignore
const systemHandle = (await window.showSaveFilePicker({
id: 0,
suggestedName: filename,
startIn: "downloads",
}));
const accessHandle = await systemHandle.createWritable();
for await (const [file] of apiReader) {
await accessHandle.write(file);
}
await accessHandle.close();
}
else {
const fileBuffer = await apiReader.getFileContent();
const anchor = document.createElement("a");
const reader = new FileReader();
reader.onload = (e) => {
if (!e.target)
return;
anchor.href = e.target.result;
anchor.download = filename;
anchor.click();
};
reader.readAsDataURL(new Blob([fileBuffer]));
}
}