I have a Node.js end-point that properly triggers an arbitrarily-large file download when accessed using the following:
response.setHeader('Content-disposition', 'attachment; filename=' + fileName);
response.set('Content-Type', 'text/csv');
response.status(200);
result.pipe(response);
where result
is a transform stream, and response
is an Express object.
This works fine when directly accessing the end-point in Chrome, Firefox, Internet Explorer, etc. The problem arises from trying to hit the end-point when token-based authentication is enabled.
From a user's perspective, when they click on a button, a file is downloaded.
How do I make the button hit this end-point with the correct authentication token in the request's header and cause a file to be downloaded?
When the user clicks on the button, it fires an action that's handled by the redux-api-middleware, which makes the GET
request to the end-point (with the authentication token automatically included in the request). This middleware saves the response in a variable, which is picked up by a React component. In this React component, if the browser being used (i.e. Chrome and Opera) supports streams, response.body
will exist so you can do something like the following.
if (response.body) {
const csvReader = response.body.getReader();
const textDecoder = new TextDecoder();
const processCsvRow = (csvRow) => {
if (csvRow.done) {
console.log('Finished downloading file.');
return Promise.resolve();
}
// Write to new file for user to download
console.log(textDecoder.decode(csvRow.value, {stream: true}));
return csvReader.read().then(processCsvRow);
};
csvReader.read().then(processCsvRow);
}
With this approach, I'd need to investigate how to handle the data if it's being received by a non-stream-supporting browser.
When the user clicks on the button, it fires an action that saves the end-point into the Redux store by the reducer, which triggers a React component to create an anchor tag that is automatically clicked.
const link = document.createElement('a');
document.body.appendChild(link); // Firefox requires the link to be in the body
link.href = endPoint;
link.target = '_blank';
link.click();
document.body.removeChild(link); // Remove the link when done
where endPoint
is the end-point that responds with the file.
This approach works when authentication is disabled. When authentication is re-enabled, the authentication token must somehow be injected into the anchor tag's request header.
GET
request to the end-point, which returns a second temporary unsecured end-point that returns the actual file response. Pass this temporary unsecured end-point to the anchor tag in #2.I've decided to go with Approach #7 for now.