Node JS / Generate PDF server side - node.js

I need to create PDF files via server side. I have a pug file with the HTML and it's working as expected (the format and the content is perfect). Also, I have a service that gets the HTML from the pug file in a string. The problem is that I'm facing issues using the node-html-pdf library to generate a PDF.
let config = {
format: 'Letter',
orientation: 'portrait',
type: 'pdf'
};
pdf.create(htmlConverted, config).toBuffer((errConvertingHtml, buffer) => {
if (errConvertingHtml) {
this.logError(errConvertingHtml, null, `${this.className} createPDF`);
return this.returnResult(errConvertingHtml, false, null);
} else {
const filename = 'Invoice' + '.pdf';
const base64PDF = buffer.toString('base64');
const email = new models.EmailModel();
email.to.email = order.email;
email.to.name = order.full_name;
email.template_id = subscriptionTemplate.value;
email.dynamic_template_data = dynamic_data;
email.attachments = [
{
filename: filename,
content: base64PDF,
type: 'application/pdf',
disposition: 'attachment'
}
];
let mailIntegration = new integrations.MailIntegration();
mailIntegration.SendNow(email);
}
});
This code works, but the PDF attach in the email doesn't have the right format, it's really small. I can use another library, or another implementation, I don't have any issue with that. Any suggestions or recommendations for this code? I have been searching for another library, but honestly, I haven't found something to convert the HTML string to PDF and then the PDF to a buffer and then to base64. Any help is well received. Thanks, I really appreciate your responses.

Related

Unable to send pdf attachment with more than 2 photos using sendgrid

I have one weird problem. I created API, that allow you to: Upload photos to server, then save data to database, generate pdf with these datas and photos and then send email with that pdf attachment. It was working well for me, while I was uploading/sending only two photos. I was able to open that generated pdf file using finder, also I was able to open it from received email.
Then I uploaded more files and after sending API request, I received email with that pdf file but I was not able to open it. It shows error that finder was not able to open that file. When I checked manually inside server folder, it was working well.
So there must be problem while sending that email. 0 to 2 photos works well, more than 2 is not working. But when I open that pdf file on server, it is working well even with 8 photos.
Sending picture of that pdf attachment.
Here is code, that I am using to generate pdf with pictures:
const photosToPdf = [];
let positionX = 30;
let positionY = 240;
for(let j=0;j<uploadsPath.length;j++){
photosToPdf.push(uploadsFileNames[j]);
}
photosToPdf.forEach(img => {
if(positionX == 570){
positionX = 30
positionY = positionY + 160
}
doc.image(`uploads/${img}`, positionX, positionY, {width: 80});
positionX = positionX + 90
});
And here is code that generate email:
function highPriorityEmail(){
return new Promise((resolve, reject) => {
setTimeout(() => {
let filePath = path.join(__dirname, `../../${problemName}_${creationDate}.pdf`);
let attachment = fs.readFileSync(filePath).toString("base64");
let fileName = `${problemName}_${creationDate}.pdf`
if (problemPriority == "vysoká") {
global.msg = {
//to: ['marekzacik23#gmail.com', 'zacik.mareek#gmail.com'], // Change to your recipient
//to: "miroslav.hanzen#skolboz.sk", // Change to your recipient
to: "zacik.mareek#gmail.com", // Change to your recipient
from: "marekzacik23#gmail.com", // Change to your verified sender
subject: "Vytvorenie nového nedostatku: " + problemName,
html: `
Dobrý deň, <br>
<br>
dňa ${creationDate} bol vykonaný dozor koordinátora ${koordinatorName}. <br>
Boli zistené nedostatky s vysokou prioritou, ktoré sú priložené v prílohe tohoto emailu.
<br>
`,
attachments: [
{
content: attachment,
filename: fileName,
contentType:'application/pdf'
},
],
};
// sgMail.sendMultiple(msg) to prevent seeing
sgMail
.send(msg)
.then(() => {
console.log("Email sent");
})
.catch((error) => {
console.error(error);
});
Case.update(
{ wasSended: 1 },
{ returning: true, where: { caseName: problemName } }
);
}
resolve();
}, 1000)
});
};
I am using:
pdfkit-table
and sendgrid/mail
Thanks for help
Ok, I think we got to an answer here.
The issue is in the line
global.msg = {
Setting a variable that is local to the function as a global variable makes it possible that other code can change this data while it is being used. Change this to a const instead and you avoid that issue:
const msg = {
One other thing to point out, but the attachments object content type property should be type not contentType (though in my testing that didn't change anything).
attachments: [
{
content: attachment,
filename: fileName,
type:'application/pdf'
},
],

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

How to decode base64 PDF string in Flutter?

I know there is a package called dart:convert which let me decode base64 image. But apparently, it doesn't work with pdf files. How can I decode the base64 PDF file in Flutter?
I want to store it in Firebase Storage (I know how to do it) but I need the File variable to do it.
I have a web service written in node js where I have a POST route. There, I create a pdf file and encode it to base 64. The response is a base64 string, look at the code.
router.post('/pdf', (req, res, next) => {
//res.send('PDF');
const fname = req.body.fname;
const lname = req.body.lname;
var documentDefinition = {
content: [ write your pdf with pdfMake.org ],
styles: { write your style };
const pdfDoc = pdfMake.createPdf(documentDefinition);
pdfDoc.getBase64((data) => {
res.send({ "base64": data });
});
});
As you can see, it returns the pdf as a base64 string.
Now, in Flutter, I have written this:
http.post("https://mypostaddreess.com",body: json.encode({"data1":"data"}))
.then((response) {
print("Response status: ${response.statusCode}");
print("Response body: ${response.body}");
var data = json.decode(response.body);
var pdf = base64.decode(data["base64"]);
});
}
I have the PDF in the variable 'pdf' as you see. But I don't know how to decode it to download the pdf or show it in my Flutter app.
#SwiftingDuster
a little added, maybe besides decoding, it's also necessary to create a pdf file and open it.
createPdf() async {
var bytes = base64Decode(widget.base64String.replaceAll('\n', ''));
final output = await getTemporaryDirectory();
final file = File("${output.path}/example.pdf");
await file.writeAsBytes(bytes.buffer.asUint8List());
print("${output.path}/example.pdf");
await OpenFile.open("${output.path}/example.pdf");
setState(() {});
}
library needed:
1. open_file
2. path_provider
3. pdf
I think it's better to get the BufferArray and convert it into a pdf file.
Check out my answer from here : Get pdf from blob data
This should convert base64 encoded pdf data into a byte array.
import 'packages:dart/convert.dart';
List<int> pdfDataBytes = base64.decode(pdfBase64)
.map((number) => int.parse(number));
The pdf and the image plugins seems to suit your needs for displaying pdf.
The code should be roughly like so:
import 'package:pdf/pdf.dart';
import 'package:image/image.dart';
...
Image img = decodeImage(pdfDataBytes);
PdfImage image = PdfImage(
pdf,
image: img.data.buffer.asUint8List(),
width: img.width,
height: img.height);
// Display it somehow
...

How to save pdf to android file system and then view PDF - react-native

I am using the react-native-fs and I am trying to save a base64 of a pdf file to my android emulators file system.
I receive base64 encoded pdf from the server.
I then decode the base64 string with the line:
var pdfBase64 = 'data:application/pdf;base64,'+base64Str;
saveFile() function
saveFile(filename, pdfBase64){
// create a path you want to write to
var path = RNFS.DocumentDirectoryPath + '/' + filename;
// write the file
RNFS.writeFile(path, base64Image, 'base64').then((success) => {
console.log('FILE WRITTEN!');
})
.catch((err) => {
console.log("SaveFile()", err.message);
});
}
Error
When I try saving the pdfBase64 the saveFile() function catches the following error:
bad base-64
Question
Can anyone tell where or what I am doing wrong?
Thanks.
For anyone having the same problem, here is the solution.
Solution
react-nativive-pdf-view must take the file path to the pdf_base64.
Firstly, I used the react-native-fetch-blob to request the pdf base64 from the server.(Because RN fetch API does not yet support BLOBs).
Also I discovered that react-native-fetch-blob also has a FileSystem API which is way better documented and easier to understand than the 'react-native-fs' library. (Check out its FileSystem API documentation)
Receiving base64 pdf and saving it to a file path:
var RNFetchBlob = require('react-native-fetch-blob').default;
const DocumentDir = RNFetchBlob.fs.dirs.DocumentDir;
getPdfFromServer: function(uri_attachment, filename_attachment) {
return new Promise((RESOLVE, REJECT) => {
// Fetch attachment
RNFetchBlob.fetch('GET', config.apiRoot+'/app/'+uri_attachment)
.then((res) => {
let base64Str = res.data;
let pdfLocation = DocumentDir + '/' + filename_attachment;
RNFetchBlob.fs.writeFile(pdfLocation, pdf_base64Str, 'base64');
RESOLVE(pdfLocation);
})
}).catch((error) => {
// error handling
console.log("Error", error)
});
}
What I was doing wrong was instead of saving the pdf_base64Str to the file location like I did in the example above. I was saving it like this:
var pdf_base64= 'data:application/pdf;base64,'+pdf_base64Str;
which was wrong.
Populate PDF view with file path:
<PDFView
ref={(pdf)=>{this.pdfView = pdf;}}
src={pdfLocation}
style={styles.pdf}
/>
There is a new package to handle the fetching (based on react-native-fetch-blob) and displaying of the PDF via URL: react-native-pdf.
Remove application type in base64 string and it's working for me
var pdfBase64 = 'data:application/pdf;base64,'+base64Str;
To
var pdfBase64 = base64Str;

Send PDF, docx, etc. files from node.js

I'm trying to send file's content requested by user using AngularJS on clientside.
This is how I send file from node:
res.download(filePath, req.param('fileName'));
And this is how I read and save file in Angular:
$http.get('/attachment/' + req.fileName, {responseType: 'arraybuffer'}).success(function (data, status, headers, config) {
var element = angular.element('<a/>');
var file = '';
var bytes = new Uint8Array(data);
for (var i = 0; i < bytes.byteLength; i++) {
file += String.fromCharCode(bytes[i]);
}
element.attr({
href: 'data:application/force-download,' + encodeURI(file),
target: '_blank',
download: fileName
})[0].click();
});
It seems to work but only for trivial files (TXT, CSV etc.) but after requesting PDF client get file with proper page count but all pages are blank.
I suppose it must be something wrong with content encoding.
Have anyone had similar issue and already fixed it?

Resources