Node buffer doesn't properly download file? - node.js

I'm successfully sending a get request that generates a pdf on the server, which I now want to send back to client and download it on their browser. The npm pdf generating library I'm using is called html-pdf and has the following options:
pdf.create(html).toFile([filepath, ]function(err, res){
console.log(res.filename);
});
pdf.create('<h1>Hi</h1>').toBuffer(function(err, buffer){
console.log('This is a buffer:', Buffer.isBuffer(buffer));
});
When I use the toFile option, the file gets correctly generated, however, when I use the toBuffer option and send that back to the user, the resulting pdf is blank.
I send the buffer to the user from my ajax handler like this:
module.exports = function(req, res) {
pdf.create(html).toBuffer(function(err, buffer){
res.setHeader('Content-Disposition', 'attachment; filename=panda.pdf');
res.setHeader('Content-Type', 'application/pdf');
res.send(buffer)
});
};
which gets received on the client here:
$.get('generatePdf', function(data, status) {
const url = window.URL.createObjectURL(new Blob([data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'file.pdf');
document.body.appendChild(link);
link.click();
})
For some reason though the pdf that is downloaded is blank. Does anyone know what I might be doing wrong?
My downloaded file is corrupt according to this online pdf validator with the following errors:
Result Document does not conform to PDF/A. Details Validating file
"file (8).pdf" for conformance level pdf1.4
The 'xref' keyword was not found or the xref table is malformed. The
file trailer dictionary is missing or invalid. The "Length" key of the
stream object is wrong. Error in Flate stream: data error. The
document does not conform to the requested standard. The file format
(header, trailer, objects, xref, streams) is corrupted. The document
does not conform to the PDF 1.4 standard.

Related

How can I temporarily store a pdf in Firebase filesystem?

I am creating a pdf using JSPDF on server-side, in NodeJS. Once done, I want to create a new folder for the user in Google Drive, upload the pdf to said folder, and also send it to the client-side (browser) for the user to view.
There are two problems that I'm encountering. Firstly, if I send the pdf in the response -via pdf.output()- the images don't display correctly. They are distorted, as though each row of pixels is offset by some amount. A vertical line "|" instead renders as a diagonal "\". An example is shown below.
Before
After
My workaround for this was to instead save it to the filesystem using doc.save() and then send it to the browser using fs.readFileSync(filepath).
However, I've discovered that when running remotely, I don't have file permissions to be saving the pdf and reading it. And after some research and tinkering, I'm thinking that I cannot change these permissions. This is the error I get:
Error: EROFS: read-only file system, open './temp/output.pdf'
at Object.openSync (fs.js:443:3)
at Object.writeFileSync (fs.js:1194:35)
at Object.v.save (/workspace/node_modules/jspdf/dist/jspdf.node.min.js:86:50626)
etc...
So I have this JSPDF object, and I believe I need to either, alter the permissions to allow writing/reading or take the jspdf object or, I guess, change it's format to one accepted by Google drive, such as a stream or buffer object?
The link below leads me to think these permissions can't be altered since it states: "These files are available in a read-only directory".
https://cloud.google.com/functions/docs/concepts/exec#file_system
I also have no idea 'where' the server filesystem is, or how to access it. Thus, I think the best course of action is to look at sending the pdf in different formats.
I've checked jsPDF documentation for types that pdf.output() can return. These include string, arraybuffer, window, blob, jsPDF.
https://rawgit.com/MrRio/jsPDF/master/docs/jsPDF.html#output
My simplified code is as follows:
const express = require('express');
const fs = require('fs');
const app = express();
const { jsPDF } = require('jspdf');
const credentials = require(credentialsFilepath);
const scopes = [scopes in here];
const auth = new google.auth.JWT(
credentials.client_email, null,
credentials.private_key, scopes
);
const drive = google.drive({version: 'v3', auth});
//=========================================================================
app.post('/submit', (req, res) => {
var pdf = new jsPDF();
// Set font, fontsize. Added some text, etc.
pdf.text('blah blah', 10, 10);
// Add image (signature) from canvas, which is passed as a dataURL
pdf.addImage(img, 'JPEG', 10, 10, 50, 20);
pdf.save('./temp/output.pdf');
drive.files.create({
resource: folderMetaData,
fields: 'id'
})
.then(response => {
// Store pdf in newly created folder
var fileMetaData = {
'name': 'filename.pdf',
'parents': [response.data.id],
};
var media = {
mimeType: 'application/pdf',
body: fs.createReadStream('./temp/output.pdf'),
};
drive.files.create({
resource: fileMetaData,
media: media,
fields: 'id'
}, function(err, file) {
if(err){
console.error('Error:', err);
}else{
// I have considered piping 'file' back in the response here but can't figure out how
console.log('File uploaded');
}
});
})
.catch(error => {
console.error('Error:', error);
});
// Finally, I attempt to send the pdf to client/browser
res.setHeader('Content-Type', 'application/pdf');
res.send(fs.readFileSync('./temp/output.pdf'));
})
Edit: After some more searching, I've found a similar question which explains that the fs module is for reading/writing to local filestore.
EROFS error when executing a File Write function in Firebase
I eventually came to a solution after some further reading. I'm not sure who this will be useful for, but...
Turns out the Firebase filesystem only has 1 directory which allows you to write to (the rest are read-only). This directory is named tmp and I accessed it using the tmp node module [installed with: npm i tmp], since trying to manually reference the path with pdf.save('./tmp/output.pdf') didn't work.
So the only changes to my code were to add in the lines:
var tmp = require('tmp');
var tmpPath = tmp.tmpNameSync();
and then replacing all the instances of './temp/output.pdf' with tmpPath

Node + Express + PDF getting downloaded instead of opening

Although it may be a duplicate question but I have done everything I can without getting the solution.
I'm using Node + Express.
We use to send link of PDF file as CDN url in SMS
The link looks like this:
/api/v1/cdn/pdf/24
To get PDf and return, I have this code:
let parameter = await this.parameterDB.readRecord(where);
if (parameter) {
let fileName = parameter.id + '_' + parameter.patient_id + '.pdf';
// METHOD 1
let data = fs.readFileSync(constants.PATH.PDFS + fileName);
res.setHeader('Content-disposition', 'inline; filename="' + fileName + '"');
res.contentType('application/pdf');
res.send(data);
// METHOD 2
res.writeHead(200, {
'Content-Type': 'application/pdf',
'Content-Disposition': 'inline; filename=report_' + fileName,
'Content-Length': data.length
});
res.end(data, 'binary');
// METHOD 3
let file = fs.createReadStream(constants.PATH.PDFS + fileName);
var stat = fs.statSync(constants.PATH.PDFS + fileName);
res.setHeader('Content-Length', stat.size);
res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', 'inline; filename=' + fileName);
file.pipe(res);
} else {
return this.responseUtil.sendReadResponse(req, res, {
message: 'not found'
}, 200);
}
I all of the above 3 methods, when I click on the link in SMS, it downloads the PDF file instead of opening in web browser (which is annoying for the customers).
File is returned properly.
What can I do here?
I assume, by sending the API link in SMS means you want your customers to view the PDF in mobile browser.
Browsers can view the PDF file due to the plugins they support or have installed by default, but this is not the case with phone browsers, atleast
not all browsers on smartphones support PDF viewer, especially android. I can view PDF files in safari and chrome browser on iPhone though, because they have support for PDF view.
source: https://www.quora.com/Why-does-Chrome-on-Android-not-open-PDF-files-like-Chrome-on-Windows-Linux-can
Any one of the method in your code can show the PDF file on desktop browser with no modification, however when you try to call the API URI on phone browser,
you'll see the PDF file getting downloaded as there is no support for that in the browser, instead browser looks for the native application on phone,
downloads the file and opens in it.
Although there is a way/work-around for this if you want to see the PDF files in the browser using google docs.
You can simply embed your URL location of the PDF in the google docs link itself and redirect to it when customer hits your API URI.
source: How to display a PDF via Android web browser without "downloading" first
So you can modify your code something like this:
let parameter = await this.parameterDB.readRecord(where);
if (parameter) {
res.redirect("https://docs.google.com/gview?embedded=true&url=http://host...your-pdf-path.pdf")
} else {
return this.responseUtil.sendReadResponse(req, res, {
message: 'not found'
}, 200);
}
There is an another alternative for this, although it would require more efforts, but it is worth if you have lots of documents in your application that need to be seen on phone browser.
You can make use of PDF.js by Mozilla and create your own custom pdf viewer, this viewer is completely created in vanilla JavaScript and thus, it is supported by almost all browsers.
Once you have your own viewer implemented, all you have to do is :
res.sendfFile('show an html file eg index.html', { pdfURL: <path of the pdf file> });
and inside this index.html file you need to pass the path of the pdf file, make use of PDF.js functions to get the document data and create custom viewer.
You can use ejs to achieve this in Express JS.

Converting adobe inDesign to pptx (is it even possible?)

I'm struggling to find a solution. I have a bulk of Adobe inDesign files I'm trying to convert over as PDFs
I know you can export to inDesign -> PDF then from Acrobat PDF -> PPTX. This would work well if it was just one or two files. I don't want to keep doing this over and over. I've tried using pdf-powerpoint the only issue with that is it exports each slide as a PNG. I would still like to be able to edit them afterward. I've seen that it is possible to use javascript to automate Adobe products but, after combing through their documentation I'm not sure if it's possible to pipe data into other Adobe products. Any suggestions?
You want to convert a PDF file to a Microsoft Powerpoint file (pptx).
You want to achieve this using Node.js.
If my understanding is correct, how about this workaround? In this workaround, it uses an external API which is ConvertAPI. The pptx file converted by this API can be edited by Microsoft Powerpoint. When you try this, for example, you can also test this using "Free Package". When you try using "Free Package", please Sign Up at "Free Package" and retrieve your Secret key.
Sample script:
const fs = require('fs');
const request = require('request');
const pdfFile = "### PDF file ###"; // Please set PDF filename including the path.
const url = "https://v2.convertapi.com/convert/pdf/to/pptx?Secret=#####"; // Please set your Secret key.
const options = {
url: url,
method: 'POST',
formData: {File: fs.createReadStream(pdfFile)},
};
request(options, function(err, res, body) {
if (err) {
console.log(err);
return;
}
const obj = JSON.parse(body);
obj.Files.forEach(function(e) {
const file = new Buffer(e.FileData, "base64");
fs.writeFile(e.FileName, file, function(err) {
if (err) {
console.log(err);
return;
}
console.log("Done.");
});
});
});
Note:
Before you run ths script, please retrieve your secret key.
In this script, a PDF file is uploaded and converted to pptx file, and download it. Then, it is saved as a pptx file.
This is a simple sample script. So please modify it for your situation.
Reference:
PDF to PPTX API of ConvertAPI
If this workaround was not what you want, I'm sorry.

NodeJS server Write response and start a file download

I'm creating a simple file conversion tool for a in-company service.
I'd created a http server where any user is able to upload a file through a form and them the server responds the request with the converted file download.
I Would like to write the conversion summary on the screen and them start the download. not just start the file download.
My code is:
createServer(function(req, res) {
var form = new IncomingForm();
form.parse(req, function(err, fields, files) {
var path = files.filetoupload.path;
var result = dataConverter.parse(path); // File data conversion
res.writeHead(200);
res.write(result.summary);
res.writeHead(200, { "Content-Type": "application/force-download", "Content-disposition": "attachment; filename=outputfile.csv" });
res.write(result.data);
res.end();
});
but i'm getting the fallowing response
ERR_INVALID_CHUNKED_ENCODING
How can I write those two responses to the user?
This is not possible without some client Javascript or something like this.
The content should be a file or write to the "page".
Te best solution was to create a temporary file to download and them send response to the page.

How to attach a file to a response in REST call node js

I have a file in my local drive (say test.txt) and I am trying to attach this file in a response to a REST service. I have tried resp.writeHead, and rest.attachment('./test.txt'); etc. I am sure I am missing something here. I am getting status 200 in my response on Poster window but not getting the file. Can any one help where I am missing?
app.get('/getFile', function (req, resp) {
res.setHeader('Content-disposition', 'attachment; filename=' + '/MyDisk/test5.txt');
filestream = fs.createReadStream('/MyDisk/test5.txt');
filestream.pipe(resp);
resp.status(200);
resp.end();
});
Try adding this before sending out the response:
var filename = "test5.txt";
resp.setHeader('Content-disposition', 'attachment; filename=' + filename);
resp.setHeader('Content-type', 'text/plain');
Content Disposition Header lets the user save the file to their computer and then decide how to use it, instead of the browser trying to use the file.
Although you are adding the Content Disposition Header, you are adding it to a different object. Make sure you add the headers to the resp object and not the res object.

Resources