Getting pdf file from api response - javascript

I am calling an api and getting pdf in return.
fetch(`api` + guid, {
method: "GET",
headers: {
"Accept": "application/octet-stream",
"Authorization": "Bearer " + token,
},
responseType: 'arraybuffer',
})
.then((res) => res.text())
.then((data) => {
fs.writeFileSync('file.pdf', data);
});
I get the pdf file but the issue is the pdf file is always empty. But when I accept response as json, it works fine.
I found similar problems like this but none of the solution worked for me yet.
It would be great if someone can point out the issue.

I found the issue.
As I am using fetch not Axios.
We cannot pass responseType as Fetch's option.
fetch(`api` + guid, {
method: "GET",
headers: {
"Accept": "application/octet-stream",
"Authorization": "Bearer " + token,
},
// responseType: 'arraybuffer' //#1 remove this,
})
Instead the response in itself can be passed as arraybuffer as below.
.then((res) => res.arraybuffer())
instead of
.then((res) => res.text())
Now instead of directly using the response to write our pdf file. We can change the data to base64 string and decode it back again to create our pdf file. I used base64ToPdf npm package to handle that.
.then(data => {
var base64Str = Buffer.from(data).toString('base64');
base64.base64Decode(base64Str, "file.pdf");
})
I hope this help others. :)

Change res.arraybuffer() to res.arrayBuffer()
Below is the working code with webdriverio-
var headers = {
Authorization: "Bearer " + accessToken,
Accept: 'application/pdf'
}
fetch(
apiurl,
{
headers: {
Accept: "application/octet-stream",
Authorization: "Bearer " + accessToken
},
},
)
.then((res) => {
if (!res.ok) {
return res.status.toString()
}
return res.arrayBuffer()
})
.then((data) => {
var base64Str = Buffer.from(data).toString('base64');
base64.base64Decode(base64Str, filename);
})
.catch(
(err) => {
return err.Message;
})

Here's example which works for me:
async createPdf(context, data) {
let url = new URL(baseURL + '/invoice/createPdf');
url.search = new URLSearchParams({
id: data
})
await fetch(url, {
method: 'GET',
headers: {
'Authorization': "Bearer " + localStorage.getItem("jwt"),
'Accept': 'application/octet-stream'
},
}).then((res) => res.arrayBuffer())
.then(data => {
var base64Str = Buffer.from(data).toString('base64');
var binaryString = window.atob(base64Str);
var binaryLen = binaryString.length;
var bytes = new Uint8Array(binaryLen);
for (var i = 0; i < binaryLen; i++) {
var ascii = binaryString.charCodeAt(i);
bytes[i] = ascii;
}
var arrBuffer = bytes;
var newBlob = new Blob([arrBuffer], { type: "application/pdf" });
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(newBlob);
return;
}
data = window.URL.createObjectURL(newBlob);
var link = document.createElement('a');
document.body.appendChild(link);
link.href = data;
link.download = "Faktura.pdf";
link.click();
window.URL.revokeObjectURL(data);
link.remove();
})
}

In my case, the response is same as yours and I'm trying to convert it to a pdf file so that I can preview it on the UI.
For this, I need to fetch the URL already present in the response which is of type blob... to fetch the URL I did URL.createObjectURL(myblob)
const [url,seturl] = useState('');
response
.then((resp) => resp.blob())
.then((myBlob) => {
seturl(URL.createObjectURL(myBlob)); //<-- use this for fetching url from your response
console.log(myBlob);
})
.catch((err) => {
console.log(err.message());
});

Related

Unable to select text in pdf when downloading from NGINX server

I have the following code in a NodeJS app using the library node-html-pdf:
pdf.create(PDF_html).toStream((err, pdfStream) => {
if (err) {
console.log(err)
return res.sendStatus(500)
} else {
res.statusCode = 200
res.setHeader('Content-type', 'application/pdf')
res.attachment()
pdfStream.on('end', () => {
return res.end()
})
pdfStream.pipe(res)
}
})
On the client side I am using fetch to retrieve and download the PDF using the following code:
document.getElementById('pdf_button').addEventListener("click", function() {
let query_nr = ''
let query_spnr = ''
let url = 'https://{my.public.server.url}/getPDF/'
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"nr": query_nr,
"spnr": query_spnr
})
})
.then((response) => response.blob())
.then((blob) => {
blob = new Blob([blob], {type: "application/pdf"});
const objectURL = URL.createObjectURL(blob);
const a = document.createElement("a")
document.body.appendChild(a)
a.style = "display: none"
a.href = objectURL
a.type = 'application/pdf'
a.download = query_nr + '-' + query_spnr + '.pdf'
console.log(a);
a.click()
// window.open(objectURL, '_blank');
})
.catch((error) => {
console.error(error);
})
});
The above code works fine as long I am using localhost as url, and I am able to select text from the downloaded text.
When I download the PDF from the server it seems like the whole PDF-file gets converted to an image (I might be very wrong), and I am not able to select any text.
Does anyone know how I can fix this? And why is this happening?

Generate body for multipart file upload using fetch in Javascript

I am trying to upload a file(uploadType=multipart) to Drive API V3 using fetch but the body is wrong as it is creating a file with the title unnamed.
var tmpFile=document.getElementById('inputFile').files;
tmpFile=tmpFile[0];
await fetch('https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart', {
method: 'POST', // or 'PUT'
headers: {
'Authorization': 'Bearer '+accessToken,
},
body: {
metadata:{
'name':tmpFile.name,
'Content-Type':'application/json; charset=UTF-8'
},
media:{
'Content-Type': '*/*',
'name':tmpFile
}
}
})
.then(response => response.json())
.then(data => {
console.log('Success:', data);
})
.catch((error) => {
console.error('Error:', error);
});
Your metadata is not being properly uploaded if its uploading with a name of unnamed
const fs = require("fs");
const FormData = require("form-data");
const fetch = require("node-fetch");
const filePath = "./sample.txt";
const accessToken = "###";
token = req.body.token;
var formData = new FormData();
var fileMetadata = {
name: "sample.txt",
};
formData.append("metadata", JSON.stringify(fileMetadata), {
contentType: "application/json",
});
formData.append("data", fs.createReadStream(filePath), {
filename: "sample.txt",
contentType: "text/plain",
});
fetch("https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart", {
method: "POST",
body: formData,
headers: { Authorization: "Bearer " + accessToken },
})
.then((res) => res.json())
.then(console.log);
Uploading Files of multipart/form-data to Google Drive using Drive API with Node.js

How to download a ReadableStream on the browser that has been returned from fetch

I am receiving a ReadableStream from a server, returned from my fetch call.
A ReadableStream is returned but I don't know how to trigger a download from this stage. I can't use the url in an href because it requires an Authorization token.
I don't want to install fs on the client so what options do I have?
try {
const res = await fetch(url, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/octet-stream'
}
});
const blob = await res.blob();
const newBlob = new Blob([blob]);
const newUrl = window.URL.createObjectURL(newBlob);
const link = document.createElement('a');
link.href = newUrl;
link.setAttribute('download', 'filename');
document.body.appendChild(link);
link.click();
link.parentNode.removeChild(link);
window.URL.revokeObjectURL(newBlob);
} catch (error) {
console.log(error);
}
Update 1
I converted the file to a Blob, then passed it into a newly generated href. Successfully downloaded a file. The end result was the ReadStream contents as a .txt file.
Meaning stuff like this
x:ÚêÒÓ%¶âÜTb∞\܃
I have found 2 solutions, both worked but I was missing a simple addition to make them work.
The native solution is
try {
const res = await fetch(url, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`
}
});
const blob = await res.blob();
const newBlob = new Blob([blob]);
const blobUrl = window.URL.createObjectURL(newBlob);
const link = document.createElement('a');
link.href = blobUrl;
link.setAttribute('download', `${filename}.${extension}`);
document.body.appendChild(link);
link.click();
link.parentNode.removeChild(link);
// clean up Url
window.URL.revokeObjectURL(blobUrl);
This version is using the npm package steamSaver for anyone who would prefer it.
try {
const res = await fetch(url, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`
}
});
const fileStream = streamSaver.createWriteStream(`${filename}.${extension}`);
const writer = fileStream.getWriter();
const reader = res.body.getReader();
const pump = () => reader.read()
.then(({ value, done }) => {
if (done) writer.close();
else {
writer.write(value);
return writer.ready.then(pump);
}
});
await pump()
.then(() => console.log('Closed the stream, Done writing'))
.catch(err => console.log(err));
The key for why it was not working was because I did not include the extension, so it either errored out because of the mimetype was wrong or it opens a .txt file with a string of the body instead of the image.

PDF is blank and damaged when downloading it from API using JavaScript and React JS

I am downloading a pdf file from API, but I am getting a blank PDF. I have tested the API endpoint and able to get the byte stream on the console and when I save it to File, it got saved and the file looks good. Getting the same response back to the front end using React and I could see the PDF byte stream in the response.
However, I could not see the content. It says the file is damaged or corrupted when I opened the downloaded PDF from my local.
I have looked at many examples and are following the same pattern, but I think I am missing something here.
My API Java endpoint definition looks like below
#GetMapping(value = "/fetchFile")
public ResponseEntity<byte[]> fetchFile(#RequestParam final String key) {
FileResponse response = myService.readFile(key);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + key.substring(key.lastIndexOf('/') + 1) + "\"");
return Mono.just(ResponseEntity.ok().headers(httpHeaders).contentLength(response.getContentLength())
.contentType(MediaType.parseMediaType(response.getContentType()))
.body(response.getResponseBytes()));
}
Frontend:
rounterFetchFile.js
router.get('/', (request, resp) => {
axios({
method: 'get',
baseURL: 'http://mybackend.apibase.url',
responseType: 'blob',
url: '/fetchFile',
params: {
fileKey: 'myfile.pdf'
}
})
.then(response => {
return resp.send(response.data)
})
.catch(error => {
console.error(error)
return resp.status(error.response.status).end()
})
})
in myFileComoponent.js
//a function that reads the response from rounterFetchFile.js
const getDocumentOnClick = async () => {
try {
var {data} = await pullMyPDF()
var blob = new Blob([data], { type: "application/pdf" });
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = "myFileName.pdf";
link.click();
} catch (e) {
console.log(e)
}
}
Here
var {data} = await pullMyPDF()
is returning the following content. I compared it with the result returned by the Postman, and it is the same. The generated file size is not empty from the react too. I am not able to find out where is it wrong
Below is the response from API endpoint for the fetchFile
I had a similar problem and I fixed it with this:
spa
axios.post(
'api-url',
formData,
{
responseType: 'blob',
headers: {
'Accept': 'application/pdf'
}
})
.then( response => {
const url = URL.createObjectURL(response.data);
this.setState({
filePath: url,
fileType: 'pdf',
})
})
.catch(function (error) {
console.log(error);
});
api
[HttpPost]
public async Task<IActionResult> Post()
{
var request = HttpContext.Request;
var pdfByteArray = await convertToPdfService.ConvertWordStreamToPdfByteArray(request.Form.Files[0], "application/msword");
return File(pdfByteArray, "application/pdf");
}
When the response type is a blob and accepted 'application / pdf' in the header, with that config the job is done ;) ...
Something that worked for me was to send the bytes as base64 from the controller.
API:
public async Task<ActionResult> GetAsync() {
var documentBytes = await GetDocumentAsync().ConfigureAwait(false);
return Ok(Convert.ToBase64String(documentBytes))
}
Front End:
client.get(url, {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
})
.then(response => {
const link = document.createElement('a');
link.href = "data:application/octet-stream;base64," + response.data;
link.download = 'file.pdf';
link.click();
})
.catch(error => {
console.log(error);
})
I hope this solves your problem.

Creating form data without using FormData in node-fetch

I'm using node fetch to make API Calls inside a platform.
I need to make an API Call to pass file in form-data.
Below is my stub code:
const fetch = require("node-fetch")
var myHeaders = {"Authorization": "Basic Y2hhdEJvdDpJRkxjYkAxMjM="
,'cache-control': 'no-cache'
,"content-type": "multipart/form-data;"
};
let file_content = "base 64 file content";
let getFormDataForWhatsAppStatement = (data,fileContent,fileExt)=>{
let jsonData = { "data":{ "templateName":"Test_123", "fieldName":"Document_Purpose,Policy_Number,Document_Category", "fieldValue":`AttachDocument, ${data.policyNumber}, Customer_Requirement`, "docList":"Test_Doc" } }
let formDataPairs = [];
let finalFormData = '';
formDataPairs.push(encodeURIComponent("") + '=' + encodeURIComponent(jsonData));
formDataPairs.push(encodeURIComponent("") + '=' + encodeURIComponent(fileContent));
finalFormData = formDataPairs.join('&').replace(/%20/g, '+');
return finalFormData;
}
let formdata = getFormData({"policyNumber":"006558144"},file_content,"png");
var requestOptions = {
method: 'POST',
headers: myHeaders,
body: formdata
};
fetch(url, requestOptions)
.then(response => response.json())
.then(result => console.log(result))
.catch(error => console.log('error', error));
The error I get here is boundary parameter is not defined.
So I removed the content-type header as stated in the below thread:
Boundary Issue
But then it gives me connection timeout error(since the request format is incorrect).
So is there a way to create formData Similar to the below code without using FormData Object?
const FormData = require('form-data');
const fetch = require("node-fetch")
var formdata = new FormData();
var myHeaders = {"Authorization": "Basic Y2hhdEJvdDpJRkxjYkAxMjM="
//,"Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryyEmKNDsBKjB7QEqu"
};
formdata.append("", "{ \n \"data\":{ \n\n \"templateName\":\"Test_123\",\n \"fieldName\":\"Document_Purpose,Policy_Number,Document_Category\",\n \"fieldValue\":\"AttachDocument, G0000784, Customer_Requirement\",\n \"docList\":\"Test_Doc\" \n}\n}\n");
formdata.append("", "base 64 file data", "close.png");
var requestOptions = {
method: 'POST',
headers: myHeaders,
body: formdata,
redirect: 'follow'
};
fetch(API_URL, requestOptions)
.then(response => response.text())
.then(result => console.log(result))
.catch(error => console.log('error', error));
Instead of literally inputting the boundary have you tried using the form-data function getBoundary() and assigning it to a variable like so:
let boundary = formdata.getBoundary();
var myHeaders = {"Authorization": "Basic Y2hhdEJvdDpJRkxjYkAxMjM="
"Content-Type": `multipart/form-data; boundary=${boundary}`
};

Categories