kennethjiang/js-file-download

Direct downloads from Google Cloud Storage and how to set up CORS

Opened this issue · 0 comments

Leaving this here for posterity, I got slogged with this one.

In order to direct download files from GCS you need a couple of things:

  • A valid signed URL to your cloud storage object
  • A valid CORS configuration installed in your bucket

I am using the official google cloud storage php library to generate signed URLs: https://github.com/googleapis/google-cloud-php-storage so check their docs for that.

In order to install your cors configuration you'll need to install gsutil: https://cloud.google.com/storage/docs/gsutil and use the below gsutil cors command:

gsutil cors set path/to/cors.json your-bucket-name

with the contents of your cors.json file looking like this:

[
    {
      "origin": ["https://domain1.com/"],
      "method": ["GET"],
      "responseHeader": ["*"],
      "maxAgeSeconds": 3600
    },
    {
      "origin": ["https://www.domain.com/"],
      "method": ["GET"],
      "responseHeader": ["*"],
      "maxAgeSeconds": 3600
    },
    {
      "origin": ["https://staging.domain.com/"],
      "method": ["GET"],
      "responseHeader": ["*"],
      "maxAgeSeconds": 3600
    }
]

(It's a good idea to put all your dev and prod urls in there).

Then in your app, serve the signed url somehow and use axios and js-file-download to download the file as per the readme in this repo:

	axios.get(signedUrl, {
		responseType: 'blob',
	}).then(res => {
		fileDownload(res.data, filename)
	}).catch(error => reject(error))

ALSO

In my UI I have a loading spinner that relies on the promise provided by axios there, and that promise resolves too early - there is a lag between when the promise resolves and when the file download actually starts, meaning my UI says "all done!" and then nothing happens until the blob finishes downloading. To get around this, I pass a function in as the filename to the fileDownload call:

	}).then(res => {
		fileDownload(res.data, filename()) // <-- here

that function returns a string for the filename, but first it manually flicks the UI over to the next step. full example:

const loading = false
const filename = 'some-supplied-string.mp4'

function resolveFilename(){
    loading = false
    return filename
}

function downloadFile(){
    loading = true
    axios.get('/fetch-signed-url').then(response => {
        axios.get(response.data.signedUrl, {
            responseType: 'blob',
        }).then(response => {
            fileDownload(res.data, resolveFilename())
        })
    })
}

Hope that helps someone!