Create and Download PDF file from binary string with JS/TS - javascript

An Axios request to server response the content of a PDF as a binary string.
export const fetchPDFfile = async (id: string): Promise<string> => {
const { data } = await http.get<string>(`${baseUrl}/${id}.pdf`);
return data;
};
The response in Chrome devtools and also console logging the data is like:
%PDF-1.4
%âãÏÓ
2 0 obj <</ColorSpa ......
..........
startxref
10991
%%EOF
is defining string as the expected type of Axios response body, correct? or it should be (cast to) Blob?
Now I want to download this as a PDF file in the client-side. There are plenty of questions regarding this but none worked for me and also none had a clear answer.
So what I did so far was (in a React component):
const data = await fetchPDFfile(props.id);
const blob = new Blob([data], { type: 'application/pdf' });
const href = window.URL.createObjectURL(blob);
const theLink = document.createElement('a');
theLink.href = href;
theLink.download = props.id + '.pdf';
document.body.appendChild(theLink);
theLink.click();
document.body.removeChild(theLink);
This downloads a PDF file with 3 blank pages. The number of pages is correct the original doc should bee 3 pages. But I see the white paper.
const href = window.URL.createObjectURL(data); // istead of blob throw Error.
How should I convert and download this PDF file? In general, is the process above needed, or should I directly download it from the server? (something like what cordova-plugin-file-transfer does)

Scenario
You want the file to be downloaded when the user clicks the link.
Solution 1-
Directly put the link in <a> tag.
Cons- Error message can not be shown on the screen if something went wrong.
So it leads to the next solution.
Solution 2-
Hit the URL as an API and download the file if you get the success message.
For this, I use File-server.js
**Don't forget to set the {responseType: 'blob'}, while making the request
http.get<string>(`${baseUrl}/${id}.pdf`, {responseType: 'blob'})
as we don't want the response with Content-Type: application/json
sample code:
import FileSaver from 'file-saver';
downloadPdf() {
var blob = new Blob([data], {type: "application/pdf"});
FileSaver.saveAs(blob, "filename");
}

Firstly use Blob as generic argument for Promise.
I will use fetch API as it can be tested quite easily.
fetch('https://www.jianjunchen.com/papers/CORS-USESEC18.slides.pdf').then(x => x.blob()).then(b => console.log(b.type))
This will log "application/pdf" it the file is trully pdf.
If you got a blob that is not PDF and you will re-wrap it to Blob with pdf type you might break the data. If you got trully a string and you convert it to Blob with pdf type the file will be broken as the PDF would be invalid.
If you want to know if b is trully a blob just console.log(b instanceof Blob) and it should say true. If you have recieved trully a blob you do not have to create new one as you did in new Blob([data]).
This example works just fine:
fetch('https://www.jianjunchen.com/papers/CORS-USESEC18.slides.pdf').then(x => x.blob()).then(b => {
const url = window.URL.createObjectURL(b);
var a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
a.href = url;
a.download = "a.pdf";
a.click();
window.URL.revokeObjectURL(url);
})
Sorry for broken code style but I was unable to paste it properly.

Related

Blob type open more than one file type like pdf and jpeg

I want to preview a file in another browser tab by clicking on a file which I uploaded before.
It's working fine with {type:'application/pdf'} or {type: 'image/jpeg'} but I need both of them.
Is there a way to set more than one type for a Blob?
When i get the Dokument from the Backend I don't geht the type of the file. So I can't check either a File is pdf or a jpeg.
const fileOpen = new Blob([response], {type: 'image/jpeg' }); // this works fine!! but i need to open a pdf file as well.
const fileUrl = URL.createObjectURL(fileOpen);
window.open(fileUrl);
If response is a Fetch API Response object, you can set the type of Blob according to the type of response:
const fileOpen = new Blob([response], {type: response.data.type });
const fileUrl = URL.createObjectURL(fileOpen);
window.open(fileUrl);

React:write to json file or export/download [no server]

I got really confused with file I/O in JS/TS. most examples I see works with DOM and has browser-based solutions.
Also, I did not understand how to make fs work, it seems to need a webpack config, where I use CRA and do not want to eject.
in a React component I want to fetch some data from a server then save them as a JSON file in the project folder (the same path, root, public folder, no matter) or directly download (no button needed).
//data type just in case
inteface IAllData{ name:string; allData:IData[];}
so after fetching some data want to save them to name.json
public componentDidMount(){
this.fetchData().then(()=>this.saveData())
}
public async fetchData(){/* sets data in state*/}
public saveData(){
const {myData}=this.state;
const fileName=myData.name;
const json=JSON.stringify(myData);
const blob=new Blob([json],{type:'application/json'})
/* How to write/download this blob as a file? */
}
here trying window.navigator.msSaveOrOpenBlob(blob, 'export.json'); did not work
note: I know it has security risks, it is not for production. save the file in the project folder is preferred but a download is totally ok.
I had a blob containing data and I had found a solution on stackoverflow and manipulated a bit, and succeded to download as a xlsx file. I am adding my code below, it might help you, too.
const blob = await res.blob(); // blob just as yours
const href = await URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = href;
link.download = "file.xlsx";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
EDIT: You can use the function below, but be sure to switch out fileName and myData from this.state to something that will work in your application.
const downloadFile = () => {
const { myData } = this.state; // I am assuming that "this.state.myData"
// is an object and I wrote it to file as
// json
// create file in browser
const fileName = "my-file";
const json = JSON.stringify(myData, null, 2);
const blob = new Blob([json], { type: "application/json" });
const href = URL.createObjectURL(blob);
// create "a" HTLM element with href to file
const link = document.createElement("a");
link.href = href;
link.download = fileName + ".json";
document.body.appendChild(link);
link.click();
// clean up "a" element & remove ObjectURL
document.body.removeChild(link);
URL.revokeObjectURL(href);
}
More documentation for URL.createObjectURL is available on MDN. It's critical to release the object with URL.revokeObjectURL to prevent a memory leak. In the function above, since we've already downloaded the file, we can immediately revoke the object.
Each time you call createObjectURL(), a new object URL is created, even if you've already created one for the same object. Each of these must be released by calling URL.revokeObjectURL() when you no longer need them.
Browsers will release object URLs automatically when the document is unloaded; however, for optimal performance and memory usage, if there are safe times when you can explicitly unload them, you should do so.
For the ones like me here that are looking for an easier solution when you already have your JSON as a variable:
<button
href={`data:text/json;charset=utf-8,${encodeURIComponent(
JSON.stringify(YOURJSON)
)}`}
download="filename.json"
>
{`Download Json`}
</button>
<button
type="button"
href={`data:text/json;charset=utf-8,${encodeURIComponent(
JSON.stringify(YOURJSON)
)}`}
download="filename.json"
>
{`Download Json`}
</button>
if you are using the Loic V method just ad the type for the button on the button element and should work just fine.

Opening a file with C# as a BLOB, and downloading with JS as a BLOB

I have a PDF on my .NET Core server, which I need to somehow send across the wire as a BLOB, so that my JS AJAX request can convert it back to a PDF so it can be downloaded.
The reason for the indirection is because the file comes from my API, which is only accessed through AJAX. Due to the need for a Bearer token, I can't just use a form behind the scenes, as there's no cookie for the site created. (Weird, I know, but that's how it is presently, and I'm not looking to change that part)
So, on my C# side, I've tried several variations, shown below. ApiResponse is just a container I use that holds a bool and a string (named message) so I can tell if the request was good or not.
These are what I've been trying
return new ApiResponse(true, File.ReadAllText(path));
return new ApiResponse(true, Convert.ToBase64String(File.ReadAllBytes(path)));
return new ApiResponse(true, JsonConvert.SerializeObject(File.ReadAllBytes(path)));
And on the JS side, in the same order to parse it back out, I have:
// Get the response in object form, since it's sent as an ApiResponse
const response = JSON.parse(xmlhttp.response);
const text = response.message;
const text = atob(response.message)
const text = JSON.parse(response.message)
I've also tried things like
const text = atob(JSON.parse(response.message))
Then, with the text I'm doing this:
const blob = new Blob([text], {type: "application/pdf"});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "file.pdf";
document.body.appendChild(a);
a.click();
And this does correctly generate a file that's downloaded. However, that file is not valid: it's corrupted.
I'm pretty much stuck at this point, and I haven't been able to find something that goes from start to finish using this method to download files with Javascript. It's either the back side, or the front side, but never tied together.
So, how can I successfully send a PDF BLOB across the wire, and recreate it on the front end so it can be downloaded?
The easy answer to how to do the convert is don't.
Every modern browser supports base64 encoding natively, so there's no need to convert the data back to a BLOB before putting it into download.
Thus, the end code is:
const a = document.createElement("a");
a.href = "data:application/pdf;base64," + response.message;
a.download = "file.pdf";
document.body.appendChild(a);
a.click();

Download a file from a server with JavaScript / Typescript

I have a problem when I try to download a file stored on a server. I make a call and get a right response in which I have all the information I need, in the headers I have the content type and the file name, and I have the file body in the response body.
What I want to do is to simply make a download process, so I tried to do so, data being the http call response :
// Get headers info
let headers = data.headers
let contentType = headers.get("Content-Type")
let name = headers.get("name")
// Initialize Blob
let blob = new Blob([data.text()], {type: contentType})
// Make the download process
let a = window.document.createElement("a")
a.href = window.URL.createObjectURL(blob)
a.download = name
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
For a text file, it's working as it's an easy format, but for like a picture or a PDF file it makes download a file of the right name and type, but they can't be well read.
Has anyone an idea ? Thanks !
Found a way to do it using the way described below by simulating an tag with href and download parameters :)
how to set a file name using window.open

Javascript Blob anchortag download produces corrupted file

the following code downloads a file that can't be opened(corrupt) and I have absolutely no idea why. I've tried this in so many ways but it never works, it always produces a corrupt file. The original file isn't the problem because it opens fine. I'm trying to open mp4, mp3, and image files.
//$scope.fileContents is a string
$scope.fileContents = $scope.fileContents.join(",");
var blob = new Blob([$scope.fileContents], {type: $scope.file.fileDetails.type});
var dlURL = window.URL.createObjectURL(blob);
document.getElementById("downloadFile").href = dlURL;
document.getElementById("downloadFile").download = $scope.file.fileDetails.name;
document.getElementById("downloadFile").click();
window.URL.revokeObjectURL(dlURL);
You need to download the file contents as binary using an ArrayBuffer e.g.
$http.get(yourFileUrl, { responseType: 'arraybuffer' })
.then(function (response) {
var blob = new Blob([response.data], {type: $scope.file.fileDetails.type});
// etc...
});
Sources:
angular solution
plain javascript solution

Categories