Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detect when browser receives file download

I have a page that allows the user to download a dynamically-generated file. It takes a long time to generate, so I'd like to show a "waiting" indicator. The problem is, I can't figure out how to detect when the browser has received the file so that I can hide the indicator.

I'm requesting a hidden form, which POSTs to the server, and targets a hidden iframe for its results. This is, so I don't replace the entire browser window with the result. I listen for a "load" event on the iframe, hoping that it will fire when the download is complete.

I return a "Content-Disposition: attachment" header with the file, which causes the browser to show the "Save" dialog. But the browser doesn't fire a "load" event in the iframe.

One approach I tried is using a multi-part response. So it would send an empty HTML file, as well as the attached downloadable file.

For example:

Content-type: multipart/x-mixed-replace;boundary="abcde"  --abcde Content-type: text/html  --abcde Content-type: application/vnd.fdf Content-Disposition: attachment; filename=foo.fdf  file-content --abcde 

This works in Firefox; it receives the empty HTML file, fires the "load" event, and then shows the "Save" dialog for the downloadable file. But it fails on Internet Explorer and Safari; Internet Explorer fires the "load" event, but it doesn't download the file, and Safari downloads the file (with the wrong name and content-type) and doesn't fire the "load" event.

A different approach might be to call to start the file creation, poll the server until it's ready, and then download the already-created file. But I'd rather avoid creating temporary files on the server.

What should I do?

like image 647
JW. Avatar asked Jul 09 '09 20:07

JW.


People also ask

How do I know if my browser is downloading a file?

In most browsers, clicking on the link will open the file directly in the browser. But, if you add the download attribute to the link, it will tell the browser to download the file instead. The download attribute works in all modern browsers, including MS Edge, but not Internet Explorer.

How can I tell where a download came from?

Right-click the downloaded in Finder, and then click the “Get Info” command. You can also select the file and then press Command+I. In the Info window, expand the More Info section. You should see two URLs: the exact one for the download, and also the site you where clicked the link.

How do I trigger a download when clicking HTML button or JavaScript?

To trigger a file download on a button click we will use a custom function or HTML 5 download attribute. The download attribute simply uses an anchor tag to prepare the location of the file that needs to be downloaded.


2 Answers

One possible solution uses JavaScript on the client.

The client algorithm:

  1. Generate a random unique token.
  2. Submit the download request, and include the token in a GET/POST field.
  3. Show the "waiting" indicator.
  4. Start a timer, and every second or so, look for a cookie named "fileDownloadToken" (or whatever you decide).
  5. If the cookie exists, and its value matches the token, hide the "waiting" indicator.

The server algorithm:

  1. Look for the GET/POST field in the request.
  2. If it has a non-empty value, drop a cookie (e.g. "fileDownloadToken"), and set its value to the token's value.

Client source code (JavaScript):

function getCookie( name ) {   var parts = document.cookie.split(name + "=");   if (parts.length == 2) return parts.pop().split(";").shift(); }  function expireCookie( cName ) {     document.cookie =          encodeURIComponent(cName) + "=deleted; expires=" + new Date( 0 ).toUTCString(); }  function setCursor( docStyle, buttonStyle ) {     document.getElementById( "doc" ).style.cursor = docStyle;     document.getElementById( "button-id" ).style.cursor = buttonStyle; }  function setFormToken() {     var downloadToken = new Date().getTime();     document.getElementById( "downloadToken" ).value = downloadToken;     return downloadToken; }  var downloadTimer; var attempts = 30;  // Prevents double-submits by waiting for a cookie from the server. function blockResubmit() {     var downloadToken = setFormToken();     setCursor( "wait", "wait" );      downloadTimer = window.setInterval( function() {         var token = getCookie( "downloadToken" );          if( (token == downloadToken) || (attempts == 0) ) {             unblockSubmit();         }          attempts--;     }, 1000 ); }  function unblockSubmit() {   setCursor( "auto", "pointer" );   window.clearInterval( downloadTimer );   expireCookie( "downloadToken" );   attempts = 30; } 

Example server code (PHP):

$TOKEN = "downloadToken";  // Sets a cookie so that when the download begins the browser can // unblock the submit button (thus helping to prevent multiple clicks). // The false parameter allows the cookie to be exposed to JavaScript. $this->setCookieToken( $TOKEN, $_GET[ $TOKEN ], false );  $result = $this->sendFile(); 

Where:

public function setCookieToken(     $cookieName, $cookieValue, $httpOnly = true, $secure = false ) {      // See: http://stackoverflow.com/a/1459794/59087     // See: http://shiflett.org/blog/2006/mar/server-name-versus-http-host     // See: http://stackoverflow.com/a/3290474/59087     setcookie(         $cookieName,         $cookieValue,         2147483647,            // expires January 1, 2038         "/",                   // your path         $_SERVER["HTTP_HOST"], // your domain         $secure,               // Use true over HTTPS         $httpOnly              // Set true for $AUTH_COOKIE_NAME     ); } 
like image 71
bulltorious Avatar answered Sep 20 '22 12:09

bulltorious


A very simple (and lame) one line solution is to use the window.onblur() event to close the loading dialog. Of course, if it takes too long and the user decides to do something else (like reading emails) the loading dialog will close.

like image 43
birdman Avatar answered Sep 20 '22 12:09

birdman