3

I have a VueJS frontend that is connected to a Rails API backend.

In one of the endpoints, I am using WickedPDF to generate a PDF. When I open the URL itself in a browser, the PDF downloads fine and works exactly as expected. When I send a request via Vue, the API responds with a weird looking string that looks like this:

%PDF-1.4
1 0 obj
<<
/Title (��)
/Creator (��wkhtmltopdf 0.12.4)
/Producer (��Qt 4.8.7)
/CreationDate (D:20190222102025+02'00')
>>
endobj
3 0 obj
<<
/Type /ExtGState
/SA true
/SM 0.02
/ca 1.0
/CA 1.0
/AIS false
...

I am not really sure what data type this is? I initially thought that it might be a BLOB, but I have no idea. I followed the logic outlined here to parse the response from my rails api, which did download a PDF to chrome. When opening this PDF, it is blank and the file name at the top of the chrome browser is a combination of weird chars. This makes me think that I am not converting the response in the correct way and some encoding issue ends up happening.

Here is my Rails API code:

def pdf
  pdf_html = ActionController::Base.new.render_to_string(
    template: 'api/v1/exporters/pdf',
    layout: 'pdf',
    page_size: 'A4',
    formats: :html,
    encoding: 'utf8',
    margin: {
      top: 20,
      left: 20,
    }
  )
  pdf = WickedPdf.new.pdf_from_string(pdf_html)
  send_data(
    pdf,                                  
    filename: 'download.pdf',                     
    type: 'application/pdf',                      
    disposition: 'attachment'
  )
end

Here is the JS function from the link above. I am passing the Rails response body (which when console logged is the weird looking char set in the first code block) as the only param:

showFile(blob){
  var newBlob = new Blob([blob], {type: "application/pdf"})

  if (window.navigator && window.navigator.msSaveOrOpenBlob) {
    window.navigator.msSaveOrOpenBlob(newBlob);
    return;
  } 

  const data = window.URL.createObjectURL(newBlob);
  var link = document.createElement('a');
  link.href = data;
  link.download="file.pdf";
  link.click();
  setTimeout(function(){
    window.URL.revokeObjectURL(data);
  , 100}
}

Can anyone help point me in the right direction as to how to configure this in the right way or how to do it in a different/better way? Even a confirmation of what kind of data type the response is might help me.

HermannHH
  • 1,732
  • 1
  • 27
  • 57
  • Have you tried opening with `window.location.assign(url)`? – dan-klasson Feb 24 '19 at 10:55
  • Thanks @dan-klasson I have tried opening the data string as a url, but it just results in a blank screen – HermannHH Feb 24 '19 at 15:47
  • No I mean using `window.location.assign(url)` with `url` being the actual url. You don't seem to be passing any header data so isn't it just a matter of opening the url and letting the browser do the rest? – dan-klasson Feb 24 '19 at 16:46

1 Answers1

4

If you are using axios for the API call, you can specify the client to download the data as blob.

import axios from 'axios';

axios
    .request({
       url: '/api-url-to-download-pdf',
       responseType: 'blob',
    })
    .then(response => response.data)
    .then(blob => {
        const data = URL.createObjectURL(blob );

        // ... do your stuff here ...
    .catch((err) => {
        // handle error
    });

If you are using fetch api to make the API request,

fetch('/api-url-to-download-pdf', {
    headers: {
        Accept: 'application/pdf',
    },
    responseType: 'arraybuffer'
})
    .then(response => {
        console.log(response);
        if (response.ok) {
            return response.blob();
        }
    })
    .then(blob => {
        const data = URL.createObjectURL(blob);

        // ... do your stuff here ...
    })
    .catch((err) => {
        // handle error
    });
Sohail
  • 4,506
  • 2
  • 38
  • 42
  • URL is part of the "window" object and so the line where the createObjectURL should be something like `const data = window.URL.createObjectURL(blob);` – Askar Hussain Jun 25 '20 at 14:31
  • 1
    Yes correct @AskarHussain however, since all properties of the `window` are globally accessible, we don't really need to apped it. The sample code should work without too. – Sohail Jun 26 '20 at 12:08
  • `const data = URL.createObjectURL(blob);` is throwing the following error for me `TypeError: Failed to execute 'createObjectURL' on 'URL': Overload resolution failed.` I solved it by changing it to `const data = URL.createObjectURL(response.data);` and removing the `.then(blob => ...` part. – Dawesign Feb 25 '21 at 12:59
  • This answer is close. But this: https://stackoverflow.com/a/44021663/6772055 is what worked for me. Hope this helps others. – Karan Sapolia Oct 03 '21 at 14:30