Converting HTML to PDF buffer in Nodejs - node.js

I am trying to convert an HTML code that is returned by the "returnDefaultOfferLetter" function here into PDF buffer(that I will use for sending attachments in a mail) using html-pdf package. So, the problem is it works on localhost but on AWS elastic beanstalk server it throws me ASSERTION ERROR. So after some research, I got to know I need to specify phantomPath. I tried everything I could, but I haven't got any solution.
BTW one week before it was working on AWS, so don't know what's wrong now. Help me in finding some solution or suggest me any method or package to convert HTML into pdf BUFFER. (Please, don't ignore buffer)
const htmlToBase64Pdf = (req, res) => {
const promise = new Promise((resolve, reject) => {
const offerLetterHTML = returnDefaultOfferLetter(req.body).toString(
"utf8"
);
const pdfOptions = {
format: "A3",
phantomPath: "../../node_modules/phantomjs-prebuilt/bin/phantomjs",
};
pdf.create(offerLetterHTML, pdfOptions).toBuffer(function (
err,
buffer
) {
if (err) {
// console.log("err", err);
reject(err);
} else {
// console.log("buffer", buffer);
const base64Attachment = buffer.toString("base64");
resolve(base64Attachment);
}
});
});
promise
.then((resp) => res.send(resp))
.catch((e) => {
res.send(e);
});
};

Related

Using fs.read inside promise does not work

I am trying to do a fs.read after the promise job is done by using the .then()
Here is how my code looks like
(async () => {
const feed = await parser.parseURL('https://www.nasa.gov/rss/dyn/breaking_news.rss');
console.log(feed.title);
const items = [];
await Promise.all(feed.items.map(async (currentItem) => {
// some code here to create data
items.push(data);
})).then(
items.forEach((element) => {
const file = downloadFile(element.url);
let checksumValue;
try {
fs.readFileSync(file, (_err, data) => {
checksumValue = generateChecksum(data);
console.log(`The checksum is: ${checksumValue}`);
// Delete the downloaded file
deleteFile(file);
});
} catch (error) {
console.error(error);
// expected output: ReferenceError: nonExistentFunction is not defined
// Note - error messages will vary depending on browse
}
})(),
);
})();
But it doesn't operate this piece of code :
fs.readFileSync(file, (_err, data) => {
checksumValue = generateChecksum(data);
console.log(`The checksum is: ${checksumValue}`);
// Delete the downloaded file
deleteFile(file);
});
How should I read the file?
fs.readFileSync is sync, so it doesn't take a callback.
Either use the non-sync version:
fs.readFile(file, (_err, data) => {
checksumValue = generateChecksum(data);
console.log(`The checksum is: ${checksumValue}`);
// Delete the downloaded file
deleteFile(file);
});
or use it as intended:
const data = fs.readFileSync(file);
checksumValue = generateChecksum(data);
console.log(`The checksum is: ${checksumValue}`);
// Delete the downloaded file
deleteFile(file);

How to write unit tests for PDF Kit for node js?

I'm writing a pdf generator using https://github.com/foliojs/pdfkit inside a hapi server route, I have successfully implemented it but I'm struggling on writing the unit tests for it.
I've googled this but I have not had found any answer yet and package's documentation doesn't mention anything about testing.
Anyone have written unit test for his library?
Thanks for your help y'all!
Edit: The focus of the test is how 'doc', an instance of pdfkit, works here is the function i want to test, the functions that are not added are just separation of some repetitive pdf code
const generatePdf = async (request, h) => {
return new Promise(async (resolve, reject) => {
try {
const doc = initPdf();
const {curated, address, photo} = await dataHandler(request, h);
// Title page
doc.font('Lato-Semibold')
.fontSize(16)
.text(
`${address.address1}, ${address.city} ${address.stateProvince} ${address.postalCode}`,
{align: 'center'}
);
doc.fontSize(14).text(' ');
if (photo.base64Photo) {
handleImage(doc, photo.base64Photo);
}
// Others page(s)
doc.addPage();
renderSections(doc, curated.others);
// Amenities page(s)
doc.addPage();
doc.font('Lato-Semibold')
.fontSize(26)
.text(
await getTranslatedString(
locale,
`px-hospitality.amenity.category.AMENITIES`
),
{align: 'center'}
);
// Hack to add a space between text and next header
doc.fontSize(14).text(' ');
renderSections(doc, curated.amenities);
generatePageNumbers(doc);
// write to PDF
const chunks = []; // contains the base64 string
doc.on('data', (chunk) => {
chunks.push(chunk);
});
doc.on('end', () => {
// the stream is at its end, so push the resulting base64 string to the response
const result = Buffer.concat(chunks);
resolve({code: 200, result});
});
doc.on('error', (err) => {
reject({code: 500, error});
});
doc.end(); // will trigger the stream to end
} catch (error) {
reject({code: 500, error});
}
});
};

Nodejs download binary octet-stream

I am trying to download (meaning create an instance of the file on the server) a .pdf file from a server that returns it to me in binary format, with:
Content-Type = application / octet-stream.
After a bit of online research I came to write:
http.get(url.parse(pdfURL), res => {
let data = [];
console.log(res.statusCode);
res.on('data', chunk => {
data.push(chunk);
}).on('end', () => {
let buffer = Buffer.concat(data);
console.log(buffer.toString('base64'));
fs.open(path, 'w', (e, fd) => {
if (e) throw e;
fs.write(fd, buffer, 0, buffer.length, null, e => {
if (e) throw e;
fs.close(fd, () => console.log('Wrote successfully'));
});
});
});
});
Everything works properly, but when I try to open the generated pdf, it tells me that the file is corrupt and not readable.
Any idea what might have been wrong?
Thanks
Edit:
I noticed that with postman everything works as it should, so I think the way I treat the binary is wrong
Ok, i got it,
I wasn't de-gzipping the response, now works properly
This didn't work for me, tried so many different ways until I found got, an npm library that handles http requests, here's what worked for me:
const stream = require('stream');
const { promisify } = require('util');
const fs = require('fs');
const got = require('got');
const pipeline = promisify(stream.pipeline);
async function downloadImage(url, name) {
await pipeline(
got.stream(url),
fs.createWriteStream(name)
);
}
More info here: https://bleext.com/post/downloading-images-with-nodejs

Upload synthesized speech from firebase function node.js server's tmp directory

I am trying to upload the audio returned by Google's Text-to-Speech API in a Firebase Function and having trouble writing the audio file to the Node.js server's temp directory. I receive the following error in my functions log:
Write ERROR: { Error: ENOENT: no such file or directory, open '/tmp/synthesized/output.mp3' at Error (native) errno: -2, code: 'ENOENT', syscall: 'open', path: '/tmp/synthesized/output.mp3' }
Here's my imports:
// Cloud Storage
import * as Storage from '#google-cloud/storage';
const gcs = new Storage();
import { tmpdir } from 'os';
import { join, dirname } from 'path';
import * as fs from 'fs';
import * as fse from 'fs-extra';
// Cloud Text to Speech
import * as textToSpeech from '#google-cloud/text-to-speech';
const client = new textToSpeech.TextToSpeechClient();
...and the part of my function I'm having trouble with:
// Construct the text-to-speech request
const request = {
input: { text: text },
voice: { languageCode: 'en-US', ssmlGender: 'NEUTRAL' },
audioConfig: { audioEncoding: 'MP3' },
};
// Creat temp directory
const workingDir = join(tmpdir(), 'synthesized');
const tmpFilePath = join(workingDir, 'output.mp3');
// Ensure temp directory exists
await fse.ensureDir(workingDir);
// Performs the Text-to-Speech request
client.synthesizeSpeech(request)
.then(responses => {
const response = responses[0];
// Write the binary audio content to a local file in temp directory
fs.writeFile(tmpFilePath, response.audioContent, 'binary', writeErr => {
if (writeErr) {
console.error('Write ERROR:', writeErr);
return;
}
// Upload audio to Firebase Storage
gcs.bucket(fileBucket).upload(tmpFilePath, {
destination: join(bucketDir, pageName)
})
.then(() => { console.log('audio uploaded successfully') })
.catch((error) => { console.log(error) });
});
})
.catch(err => {
console.error('Synthesize ERROR:', err);
});
What is wrong with my temp directory creation or fs.writeFile() function?
(Answer edited in response to question edit...)
In your original question, you invoked
client.synthesizeSpeech(request, (err, response) => {...})
following Node's http callback pattern, in which the callback function may initiate before the response is complete. Your subsequent code calls methods that assume response content; if the response is still empty, fs.writeFile() writes nothing initially, and subsequent methods cannot find the non-existent file. (Because fs.writeFile() follows the same callback pattern, you might even discover that output.mp3 file after the program exits, because fs will stream the input. But I bet your Firebase methods aren't waiting.)
The solution is to use Promises or async/await. Looking at the Google TextToSpeechClient class docs, it looks like the synthesizeSpeech method supports this:
Returns: Promise -> Array. The first element of the array is an object representing SynthesizeSpeechResponse.
Example:
client.synthesizeSpeech(request)
.then(responses => {
var response = responses[0];
// doThingsWith(response)
})
.catch(err => {
console.error(err);
});
That should solve the problem with client.synthesizeSpeech, but unfortunately fs.writeFile is still synchronous. If you were using Node >10 you could use a native fsPromise.writeFile method, and if you were using Node >8 you could use util.promisify() to convert fs.writeFile to promises. But you've indicated in comments that you are using Node 6, so we'll have to do things manually. Thieving from this reference:
const writeFilePromise = (file, data, option) => {
return new Promise((resolve, reject) => {
fs.writeFile(file, data, option, error => {
if (error) reject(error);
resolve("File created! Time for the next step!");
});
});
};
client.synthesizeSpeech(request)
.then(responses => {
const response = responses[0];
return writeFilePromise(tmpFilePath, response.audioContent, 'binary');
})
.then(() => {
return gcs.bucket(fileBucket).upload(tmpFilePath, {
destination: join(bucketDir, pageName)
});
})
.then(() => {
console.log('audio uploaded successfully');
return null;
})
.catch((error) => { console.log(error) });
I've written all of this using .then constructs, but naturally, you could also use async/await if you would rather do that. I hope this fixes things--it will force your Firebase code to wait until fs.writeFile has completed its job. I have also, unfortunately, smooshed all of the error checking into one final .catch block. And made things a bit verbose for clarity. I'm sure you can do better.

Firebase + Google cloud storage: Handling files of 1MB+ without stalling

I've run into a curious problem:
Basically I'm making a Firebase Cloud function where when someone uploads a file then that file is sent via email or API to somewhere else.
Everything works for tiny files (100K-ish), if slow, but anything above 1MB (haven't tested the exact size) stalls. Doesn't give any errors in the Firebase log, the function just never completes.
Here's the relevant code:
const Storage = require('#google-cloud/storage')({
projectId: 'bilagskortet',
keyFilename: './service-account-key.json'
});
const returnBase64 = (fileFullPathAndName) => {
console.log("Fetching file...")
// Downloads the file
return Storage
.bucket(bucketName)
.file(fileFullPathAndName)
.download()
.then((data) => {
const file = {};
file.file = new Buffer(data[0]).toString('base64');
return file;
})
.catch((error) => {
console.error("Didn't get file:", error);
});
}
This is used together with two other Promises to get everything about the file needed for the email:
Promise
.all([
StorageFile.returnDownloadURL(attachementRef)
.then(url => {
console.log("AttachmenetLink is: ")
console.log(typeof url);
console.log(url);
email.attachementLink = url
})
.catch(error => {
console.error("Error, didn't get link: ")
console.error(error)
}),
StorageFile.returnMetaData(attachementRef)
.then(metadata => {
console.log("Content type:")
console.log(metadata.contentType);
file.contentType = metadata.contentType;
})
.catch(error => {
console.error("Didn't get the metadata")
console.error(error);
}),
StorageFile.returnBase64(attachementRef)
.then(data => {
console.log("File is: ")
console.log(typeof data);
console.log(data);
file.data = data.file;
})
.catch(error => {
console.error("Error, didn't get file: ")
console.error(error)
})
])
.then(allData => {
// Define and send email with attachement (cut for brevity)
}).catch(error =>
console.error(error)
)
As I've said, the code works well if the file is tiny. Times out and dies if the file is for example a 1.7MB image (.png)
Anyone know what might be going on?
Last thing that's logged is the "AttachmentLink" and "Content Type" ones, and last thing in the StorageFile.returnBase64 function is "Fetching file..."

Resources