Marc-Olivier Blouin
Marc-Olivier Blouin

Reputation: 151

Nodejs - React download file from s3 bucket using pre-signed url

I am trying to make an onClick button to download a file from S3 bucket using pre-signet url. The problem comes when I received my url. I want an automatic redirect or kind of. In other words, how can I lunch the download file after getting back my signed url?

this is my document list

The onClick event is on the Download button.

redux action

Redux action call my nodejs route api route nodejs

Ask for pre-signed url then send it to my redux reducer.

Now in my front-end page, I got my link but I want an automatic redirect to start the file download. Part of Component

Hope my first post isn't too messy.

Upvotes: 5

Views: 19210

Answers (5)

Tim Rutter
Tim Rutter

Reputation: 4679

There is a much simpler way of making this work. You just need to set the Content-Disposition header in the presigned url. When set to "attachment", this header field tells the browser to download the file rather than trying to display it in the browser. If the presigned url contains this header you can simply navigate to that url and it'll initiate the download.

In my backend (C#) this is done like this:

var request = new GetPreSignedUrlRequest
{
  BucketName = bucketName,
  Key = fileKey,
  Expires = DateTime.UtcNow.AddSeconds(expirationTimeInSeconds),
  Verb = HttpVerb.GET,
  ResponseHeaderOverrides = new ResponseHeaderOverrides
  {
    ContentDisposition =  $"attachment; filename = \"test.json\""
  }
};
string url = client.GetPreSignedURL(request);

Then in the front end I simply do

window.location.href = url

Upvotes: 0

meadosc
meadosc

Reputation: 103

Others have mentioned simulating a click within the DOM or React, but another option is to use window.open(). You can set the target attribute to _blank to open a tab, but you do need window.open() inside the click event to prevent popup blockers from stopping the functionality. There's some good discussion on the subject here. I found this to be a better solution than simulating a click event.

Here's an example (though there may be more needed depending on how you fetch the signed_url).

function downloadDocument() {
  const signedurlPromise = fetch("/signed_url")
  signedurlPromise.then((response) => {
      window.open(response.signed_url, "_blank");
  })
}

Upvotes: 1

n00b
n00b

Reputation: 6360

The way I did it was different and has the advantage of being able to see the progress of the download as the file is being downloaded. If you're downloading a large file then it makes a difference UX wise as you see feedback immediately.

What I did was:

  1. When creating the S3 presigned URL I set the content-disposition to `attachment
  2. I used an anchor element to download the actual item <a url='https://presigned-url' download>Download me</a>

Upvotes: 1

user2213117
user2213117

Reputation: 74

The other answer does direct DOM manipulation, creates a blob, which looks as though it buffers the whole file in memory before sending it to the user and also creates a new link each time you download. A react-y of doing is:

const downloadFileRef = useRef<HTMLAnchorElement | null>(null);
const [downloadFileUrl, setDownloadFileUrl] = useState<string>();
const [downloadFileName, setDownloadFileName] = useState<string>();

const onLinkClick = (filename: string) => {
    axios.get("/presigned-url")
      .then((response: { url: string }) => {
        setDownloadFileUrl(response.url);
        setDownloadFileName(filename);
        downloadFileRef.current?.click();
      })
      .catch((err) => {
        console.log(err);
      });
  };

return (
<>
   <a onClick={() => onLinkClick("document.pdf")} aria-label="Download link">
       Download
   </a>
   <a
        href={downloadFileUrl}
        download={downloadFileName}
        className="hidden"
        ref={downloadFileRef}
   />
</>)

See here for more info https://levelup.gitconnected.com/react-custom-hook-typescript-to-download-a-file-through-api-b766046db18a

Upvotes: 2

Marc-Olivier Blouin
Marc-Olivier Blouin

Reputation: 151

I resolved my problem with a redux action. With one click I call my action, who return my pre-signed URL, then automatically click the link. This trigger download event with the original file name when I upload it to S3.

export const downDoc = (docId) => async dispatch => {
    
    const res = await axios({ url: 'myApiCall', method: 'GET', responseType: 'blob' })
    .then((response) => {
      console.log(response)
      const url = window.URL.createObjectURL(new Blob([response.data]));
      const link = document.createElement('a');
      link.href = url;
      link.setAttribute('download', `${docId.originalName}`);
      document.body.appendChild(link);
      link.click();
    });

Upvotes: 6

Related Questions