diff --git a/packages/react-storage/src/components/StorageBrowser/actions/handlers/download.ts b/packages/react-storage/src/components/StorageBrowser/actions/handlers/download.ts index f4dfab0372b..b0e05b35a76 100644 --- a/packages/react-storage/src/components/StorageBrowser/actions/handlers/download.ts +++ b/packages/react-storage/src/components/StorageBrowser/actions/handlers/download.ts @@ -38,7 +38,7 @@ function downloadFromUrl(fileName: string, url: string) { document.body.removeChild(a); } -export const downloadHandler: DownloadHandler = ({ config, data }) => { +export const downloadHandler: DownloadHandler = ({ config, data }): DownloadHandlerOutput => { const { accountId, credentials, customEndpoint } = config; const { key } = data; @@ -53,14 +53,14 @@ export const downloadHandler: DownloadHandler = ({ config, data }) => { expectedBucketOwner: accountId, }, }) - .then(({ url }) => { - downloadFromUrl(key, url.toString()); - return { status: 'COMPLETE' as const, value: { url } }; - }) - .catch((error: Error) => { - const { message } = error; - return { error, message, status: 'FAILED' as const }; - }); + .then(({ url }) => { + downloadFromUrl(key, url.toString()); + return { status: 'COMPLETE' as const, value: { url } }; + }) + .catch((error: Error) => { + const { message } = error; + return { error, message, status: 'FAILED' as const }; + }); return { result }; }; diff --git a/packages/react-storage/src/components/StorageBrowser/actions/handlers/zipdownload.ts b/packages/react-storage/src/components/StorageBrowser/actions/handlers/zipdownload.ts new file mode 100644 index 00000000000..58269ad182d --- /dev/null +++ b/packages/react-storage/src/components/StorageBrowser/actions/handlers/zipdownload.ts @@ -0,0 +1,105 @@ +import { + DownloadHandlerInput, + DownloadHandlerOutput, +} from "@aws-amplify/ui-react-storage/browser"; +import { getUrl } from "@aws-amplify/storage/internals"; +import { + ZipWriterAddDataOptions, + ZipWriter, + BlobWriter, + BlobReader, +} from "@zip.js/zip.js"; + +const model = (() => { + let zipWriter: ZipWriter | null; + return { + addFile(file: Blob, name: string, options: ZipWriterAddDataOptions) { + if (!zipWriter) { + zipWriter = new ZipWriter(new BlobWriter("application/zip"), { + bufferedWrite: true, + }); + } + return zipWriter.add(name, new BlobReader(file), options); + }, + async getBlobURL() { + if (zipWriter) { + const blobURL = URL.createObjectURL(await zipWriter.close()); + zipWriter = null; + return blobURL; + } else { + throw new Error("Zip file closed"); + } + }, + }; +})(); + +const constructBucket = ({ + bucket: bucketName, + region, +}: DownloadHandlerInput["config"]) => ({ bucketName, region }); + +const download = async ({ config, data: { key } }: DownloadHandlerInput) => { + const { customEndpoint, credentials, accountId } = config; + const { url } = await getUrl({ + path: key, + options: { + bucket: constructBucket(config), + customEndpoint, + locationCredentialsProvider: credentials, + validateObjectExistence: true, + contentDisposition: "attachment", + expectedBucketOwner: accountId, + }, + }); + const response = await fetch(url, { mode: "cors" }); + const blob = await response.blob(); + const [filename] = key.split("/").reverse(); + await model.addFile(blob, filename, {}); +}; + +const customDownloadHandler = (() => { + const q = new Set(); + let timer: ReturnType; + return (input: DownloadHandlerInput): DownloadHandlerOutput => { + const { + data: { key }, + } = input; + const [, folder] = key.split("/").reverse(); + q.add(key); + const result = download(input) + .then(() => { + q.delete(key); + return { + status: "COMPLETE", + }; + }) + .catch((e) => { + const error = e as Error; + q.delete(key); + return { + status: "FAILED", + message: error.message, + error, + }; + }) + .finally(() => { + if (timer) clearTimeout(timer); + timer = setTimeout(() => { + if (q.size === 0) { + model.getBlobURL().then((blobURL) => { + if (blobURL) { + const anchor = document.createElement("a"); + const clickEvent = new MouseEvent("click"); + anchor.href = blobURL; + anchor.download = `${folder || "archive"}.zip`; + anchor.dispatchEvent(clickEvent); + } + }); + } + }, 250); + }); + return { result: result as DownloadHandlerOutput["result"] }; + }; +})(); + +export { customDownloadHandler };