FileSaver.js doesn't download PDF with Safari - javascript

I have an issue with FileSaver.js, I can not download a PDF (or PNG or excel file) on Safari, but it works on any other web browser. I get the error in the console : 'Failed to load resource: The network connection was lost.'
What is weird is that, this PDF file doesn't get downloaded if Tomcat serves it, but if it is Apache that serves the file, the download works fine.
Here is a sample of code (I am working with angular 1.5.8):
$http.get(url, { responseType: 'arraybuffer' })
.success(function (response) {
var file = new Blob([response], {type: 'application/pdf'});
fileSaverService(file, filename);
});

I had a similar issue and I was using axios to make a call (it was post request in my case) to the download service. The code below worked for me:
axios.post(url, downloadRequest, {responseType:'blob'})
.then(response =>{
var filename = 'example.zip';
var blob = new Blob([response.data], {type:"application/octet-stream"});
saveAs(blob , filename);
})
.catch(error => {
console.error(error);
});

I had a issue with base64,Pdf was not able to open.
This i have resolved converting base64 to bin and than converted into Uint8Array(byteNumbers).
Check below snippet which has worked for me.
const byteCharacters = atob(b64);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
var blob = new Blob([byteArray], {type: "application/pdf"});
saveAs(blob, "sample.pdf");

Related

React - Downloading a Zip File from API Response Body in Bytes String?

I have a working API call that returns in the response body a string prepared in Bytes format on the Python side that is a zip file. The String looks something like this, but longer:
PK��Q��F���������������/export_file.csv��uX\M�
This is a zip file containing one csv file. In tools such as postman, hitting the same POST endpoint with the same parameters in the body, I can successfully download a valid zip file, unzip the contents, and view the .csv file. In the browser debugger tools, I can see the API endpoint returning a successful response, with the same string above in the body.
Where I have failed at every attempt is on the react side, doing the work necessary to take this string and download the same zip file. Every suggestion I've read on SO and everywhere else has failed me. Here is what some of my failed attempts look like:
(Also note that a successful API call returns a 26kb payload from this example)
export function downloadZipFile(responseBody){
/* Blob Attempts */
// These download a 46kb file. Attempting to open gives "The compressed zip folder is invalid"
// var blob = new Blob([responseBody], {type: "content-type"});
// var blob = new Blob([responseBody], {type: "application/zip"});
// var blob = new Blob([responseBody], {type: "application/zip, application/octet-stream"});
// var blob = new Blob([responseBody], {type: "application/octet-stream"});
// var blob = new Blob([responseBody], {type: "octet/stream"});
var fileName = "export.zip";
saveAs(blob,fileName);
/* Data String Attempts */
// const dataStr = "data:application/zip;" + responseBody; // "Failed - Network Error"
// const dataStr = "data:application/zip, application/octet-stream;" + responseBody; // Downloads 1kb File "The compressed zip folder is invalid"
// const dataStr = "data:application/zip,application/octet-stream;" + responseBody; // Downloads 1kb File "The compressed zip folder is invalid"
// const dataStr = "data:application/octet-stream;" + responseBody; // "Failed - Network Error"
let downloadElement = document.createElement('a');
downloadElement.setAttribute("href", dataStr);
downloadElement.setAttribute("download", "export.zip");
document.body.appendChild(downloadElement);
downloadElement.click();
downloadElement.remove();
}
I've arrived late, but hope to help someone.
It is really important to set responseType as 'arraybuffer' in the api call, like this:
...
export const getZip = async (id) => {
const { data } = await axios.get(
"/sypa-applications/export",
{
id
},
{ responseType: "arraybuffer" }
);
return data;
}
...
Then you can download the ZIP from both ways:
1.- Using file-saver npm dependency:
let blob = new Blob([data], { type: "application/zip" });
saveAs(blob, "fileName.zip");
2.- Using DOM:
const url = window.URL.createObjectURL(new Blob([data], { type: "application/zip" });
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", "fileName.zip");
document.body.appendChild(link);
link.click();
link.parentNode.removeChild(link);
I couldn't download the ZIP until I set the responseType param to "arraybuffer" in the GET request.
Best regard,
Alberto.
Did you check this tool? https://gildas-lormeau.github.io/zip.js/
There is an example for unzipping a file using js in the browser. http://gildas-lormeau.github.io/zip.js/demos/demo2.html
It seems that this is what you want. First unzip the file and the use the .csv as wanted

Piping PDF to Express Response results in blank PDF

I'm trying to create a PDF in a Lambda function and I am having trouble using the .pipe() function from PDF Kit. When opening the downloaded PDF, it is blank. I have had success by converting the PDF to a base64 string and opening that, but this won't be feasible when the PDF size & # of requests increase.
My ideal approach is to use .pipe() as I've seen in multiple guides. This is the code below that is returning a blank PDF. I have tried using both responseType: 'blob' and responseType: arraybuffer on my client. Both open blank files.
let pdf = new PDFDocument();
pdf.text("hello world", 50, 50);
res.setHeader("Content-Type", "application/pdf");
res.setHeader(
"Content-Disposition",
`attachment; filename=test.pdf`
);
res.status(200);
pdf.pipe(res);
pdf.end();
This approach works, the PDF downloaded includes the "hello world" text, but this isn't a feasible approach due to performance/memory issues.
let chunks = [];
let pdf = new PDFDocument();
pdf.text("hello world", 50, 50);
pdf.on("data", data => {
chunks.push(data);
});
pdf.on("end", () => {
res.setHeader("Content-Type", "application/pdf");
res.setHeader(
"Content-Disposition",
`attachment; filename=test.pdf`
);
res.status(200);
const result = Buffer.concat(chunks);
res.send(
"data:application/pdf;base64," + result.toString("base64")
);
});
pdf.end();
I checked the contents of both PDF files (from both approaches above) and saw that the actual content is different between the two. This was also observed in the raw response logged in Chrome. I won't paste the entire file contents (unless someone thinks that's necessary), but here's where the files differ:
Blank PDF
stream
x�e�;
�0�=Ż���g� )-���*����7na'�c��pFǦ<yԛ�_[�d1�>�zӰ1�C�����ͻ��a��} .��d�J,pt�U���*
endstream
Working PDF
stream
xœeŒ;
€0û=Å»€šÍg£ )-ì„íÄ*ÎÂû7na'ÃcŠÇpFǦ<yÔ›â_[ô‹Œd1„>ŒzÓ°1ØC³Œ’¤Í»œØa––±«d³J,pt§Ué ÝÎ*
endstream
I don't know much about encoding, so not sure if this is helpful or just gibberish, but it does show there's something different in the encoding between the two files, and maybe I'm not setting something properly in Express.
Ended up using a workaround by converting the stream to base64 then decoding it in client.
Express function:
const { Base64Encode } = require("base64-stream");
let pdf = new PDFDocument();
pdf.text("hello world", 50, 50);
res.setHeader("Content-Type", "application/pdf");
res.setHeader(
"Content-Disposition",
`attachment; filename=test.pdf`
);
res.status(200);
pdf.pipe(new Base64Encode()).pipe(res);
pdf.end();
Client Code:
function base64ToArrayBuffer(data) {
var binaryString = window.atob(data);
var binaryLen = binaryString.length;
var bytes = new Uint8Array(binaryLen);
for (var i = 0; i < binaryLen; i++) {
var ascii = binaryString.charCodeAt(i);
bytes[i] = ascii;
}
return bytes;
}
// In my response handler:
const arraybuffer = base64ToArrayBuffer(response);
const blob = new Blob([arraybuffer], { type: "application/pdf" });
saveAs(blob, "test.pdf");

Downloaded PDF looks empty although it contains some data

I'm trying to implement a PDF file download functionality with JavaScript.
As a response to a POST request I get a PDF file, in Chrome DevTools console it looks like (the oResult data container, fragment):
"%PDF-1.4↵%����↵4 0 obj↵<</Filter/FlateDecode/Length 986>>stream↵x��
Now I'm trying to initialize the download process:
let blob = new Blob([oResult], {type: "application/pdf"});
let link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = "tstPDF";
link.click();
As a result, upon a click on a button I get tstPDF.pdf, it contains the correct number of pages, but the PDF itself is empty, no content is displayed, although it is 6 KB.
When I test the Java server-side module, which generates the PDF, everything is working fine, it sends InputStream through ServletOutputStream. Thus I assume that the issue is somewhere on a client side, perhaps something with MIME, BLOB, encoding, or similar.
Why doesn't the generated PDF display any data?
I solved the issue.
The problem was in a way the data is delivered from the server to the client.
It is critical to assure that the server sends the data in Base64 encoding, otherwise the client side can't deserialize the PDF string back to the binary format. Below, you can find the full solution.
Server-side:
OutputStream pdfStream = PDFGenerator.pdfGenerate(data);
String pdfFileName = "test_pdf";
// represent PDF as byteArray for further serialization
byte[] byteArray = ((java.io.ByteArrayOutputStream) pdfStream).toByteArray();
// serialize PDF to Base64
byte[] encodedBytes = java.util.Base64.getEncoder().encode(byteArray);
response.reset();
response.addHeader("Pragma", "public");
response.addHeader("Cache-Control", "max-age=0");
response.setHeader("Content-disposition", "attachment;filename=" + pdfFileName);
response.setContentType("application/pdf");
// avoid "byte shaving" by specifying precise length of transferred data
response.setContentLength(encodedBytes.length);
// send to output stream
ServletOutputStream servletOutputStream = response.getOutputStream();
servletOutputStream.write(encodedBytes);
servletOutputStream.flush();
servletOutputStream.close();
Client side:
let binaryString = window.atob(data);
let binaryLen = binaryString.length;
let bytes = new Uint8Array(binaryLen);
for (let i = 0; i < binaryLen; i++) {
let ascii = binaryString.charCodeAt(i);
bytes[i] = ascii;
}
let blob = new Blob([bytes], {type: "application/pdf"});
let link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = pdfFileName;
link.click();
Reference topics:
How to convert a PDF generating in response.outputStream to a Base64 encoding
Download File from Bytes in JavaScript
Thanks to this. It really works.
BTW, here's how I do it using spring controller and ajax with pdf generated by jasper
The Controller:
public ResponseEntity<?> printPreview(#ModelAttribute("claim") Claim claim)
{
try
{
//Code to get the byte[] from jasper report.
ReportSource source = new ReportSource(claim);
byte[] report = reportingService.exportToByteArrayOutputStream(source);
//Conversion of bytes to Base64
byte[] encodedBytes = java.util.Base64.getEncoder().encode(report);
//Setting Headers
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType("application/pdf"));
headers.setContentDispositionFormData("pdfFileName.pdf", "pdfFileName.pdf");
headers.setCacheControl("must-revalidate, post-check=0, pre-check=0");
headers.setContentLength(encodedBytes.length);
return new ResponseEntity<>(encodedBytes, headers, HttpStatus.OK);
}
catch (Exception e)
{
LOG.error("Error on generating report", e);
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
The ajax:
$.ajax({
type: "POST",
url: "",
data: form.serialize(), //Data from my form
success: function(response)
{
let binaryString = window.atob(response);
let binaryLen = binaryString.length;
let bytes = new Uint8Array(binaryLen);
for (let i = 0; i < binaryLen; i++) {
let ascii = binaryString.charCodeAt(i);
bytes[i] = ascii;
}
let blob = new Blob([bytes], {type: "application/pdf"});
let link = URL.createObjectURL(blob);
window.open(link, '_blank');
},
error: function()
{
}
});
This will load the pdf in new window.
References: Return generated pdf using spring MVC

How to get a File() or Blob() from an URL in javascript?

I try to upload an image to the Firebase storage from an URL (with ref().put(file))(www.example.com/img.jpg).
To do so i need a File or Blob, but whenever I try new File(url) it says "not enough arguments“…
EDIT:
And I actually want to upload a whole directory of files, that’s why i can’t upload them via Console
Try using the fetch API. You can use it like so:
fetch('https://upload.wikimedia.org/wikipedia/commons/7/77/Delete_key1.jpg')
.then(res => res.blob()) // Gets the response and returns it as a blob
.then(blob => {
// Here's where you get access to the blob
// And you can use it for whatever you want
// Like calling ref().put(blob)
// Here, I use it to make an image appear on the page
let objectURL = URL.createObjectURL(blob);
let myImage = new Image();
myImage.src = objectURL;
document.getElementById('myImg').appendChild(myImage)
});
<div id="myImg"></div>
As of July 2022, the fetch API has about 97% browser support worldwide, with basically just IE missing it. You can get that to near 100% using a polyfill, which I recommend if you're still targeting IE.
#James answer is correct but it is not compatible with all browsers. You can try this jQuery solution :
$.get('blob:yourbloburl').then(function(data) {
var blob = new Blob([data], { type: 'audio/wav' });
});
It did not work until I added credentials
fetch(url, {
headers: {
"Content-Type": "application/octet-stream",
},
credentials: 'include'
})
async function url2blob(url) {
try {
const data = await fetch(url);
const blob = await data.blob();
await navigator.clipboard.write([
new ClipboardItem({
[blob.type]: blob
})
]);
console.log("Success.");
} catch (err) {
console.error(err.name, err.message);
}
}
If you are using homepage in package.json then ypu have to use:
CurrentUrl: http://localhost:3008/tempAppHomepageFromPackageJson/
const file: File = this.state.file;
const localUrlToFile = URL.createObjectURL(file);
const fileHash = localUrlToFile.split('/');
const objectUrl = location.href + fileHash[fileHash.length - 1];
objectUrl is the local url to file. For example to show in runtime you uploading file.
Second solution is (if you have no homepage in package.json):
const file: File = this.state.file;
const objectUrl = window.URL.createObjectURL(file);
Third solution (Not working in safari):
const file: File = this.state.file;
let reader = new FileReader();
reader.readAsDataURL(file);
const objectUrl = reader.result as string;
Enjoy ;)

creating an image file object from an image web url

I would like to take an image's URL and use that to get the image file object and send it to my server. i have looked around and have gotten this far but the code does not work properly. When i send it to my server it does not upload as an image file but simply uploads as a file without the .JPG extension or any image file extension.
var byteNumbers = new Array(this.imgUrl.length);
for (var i = 0; i < this.imgUrl.length; i++)
{
byteNumbers[i] = this.imgUrl.charCodeAt(i);
}
this.img = new File(byteNumbers, "imgFromUrl", { type: "image/jpeg" });
This gets the file and shows me the file details on my log such as lastmodified, filename and such. but when i upload it to my server i just get a file with no extension as mentioned above.
The file is being uploaded by being attached to a form and sent to the server.This is because i'm sending other data as well.
this.http.post(this.serverUrl + "face/detect/", data, { headers: this.headers })
.toPromise()
.then(response => response.json())
.catch(error => error);
You would need to specify the extention in the file name
var byteNumbers = new Array(this.imgUrl.length);
for (var i = 0; i < this.imgUrl.length; i++)
{
byteNumbers[i] = this.imgUrl.charCodeAt(i);
}
this.img = new File(byteNumbers, "imgFromUrl.jpg", { type: "image/jpeg" });
Tell me if this solves the problem.

Categories