2

I'm building out a firebase function that uses the html-pdf package (which uses PhantomJS). The function works fine on my local machine, but whenever I deploy the function on Firebase, I get the following error:

Error: html-pdf: PDF generation timeout. Phantom.js script did not exit.

I've changed the timeout parameter for pdf.create() and keep getting the same result. Any idea on what might be creating this issue that is unique to only when a deploy this to Firebase? Code is below.

const pdf = require('html-pdf');
const runtimeOpts = {
    timeoutSeconds: 540,  // in seconds
    memory: '2GB'
  }

exports.sendToKindle = functions.runWith(runtimeOpts).https.onRequest(async (req, res) => {
   // REMOVED A BLOCK OF CODE FOR SIMPLICITY, BUT CAN PUT BACK IN IF NEEDED//
   var options = { 
       format: 'Letter',
       directory: "/tmp", 
       timeout: 540000,  // in milliseconds
   };
    
   const blookFileName = createFileName(blookData.title) + '.pdf';
    
   const tempFilePath = path.join(os.tmpdir(), `${blookFileName}`);

   const htmlFilePath = path.join(os.tmpdir(), 'book.html');

   const htmlFs = fs.openSync(htmlFilePath, 'w');

   await fs.promises.appendFile(htmlFilePath, bookHTML);

   const fd = fs.openSync(tempFilePath, 'w');

   var html = fs.readFileSync(htmlFilePath, 'utf8');

   let mailgunObject = null;

   pdf.create(html, options).toFile(tempFilePath, async (err, res) => {
           if (err) return console.error(err);
           mailgunObject = await sendEmail(tempFilePath, kindleEmail);
           return console.log(res);
       });

   fs.closeSync(fd);
   fs.closeSync(htmlFs);

   return cors(req, res, () => {
        res.status(200).type('application/json').send({'response': 'Success'})
    })
Deep
  • 223
  • 3
  • 12
  • Your code is not waiting for `create().toFile()` to complete. It's almost certainly asynchronous, given that it accepts a callback. – Doug Stevenson Oct 24 '20 at 17:44
  • @DougStevenson, wouldn't that issue then also be reflected on the local firebase emulator as well? This is only an issue when I deploy it to firebase. – Deep Oct 24 '20 at 17:53
  • No, the emulator will not shut down the request the moment it returns. It is not a 100% accurate emulation. – Doug Stevenson Oct 24 '20 at 17:58
  • @DougStevenson, okay that makes sense. What' s the best way to address that? I can't have the ```return cors(req, res, () => { res.status(200).type('application/json').send({'response': 'Success'}) })``` within the callback. – Deep Oct 24 '20 at 18:11
  • Return a promise that resolves only after all the async work is complete. That's how you tell Cloud Functions that it's safe to clean up. – Doug Stevenson Oct 24 '20 at 19:21

2 Answers2

3

I was able to solve this issue by modifying the code by having the pdf.create().toFile() placed within the return of the cloud function.

const pdf = require('html-pdf');
const runtimeOpts = {
    timeoutSeconds: 300,  // in seconds
    memory: '1GB'
  }

exports.sendToKindle = functions.runWith(runtimeOpts).https.onRequest(async (req, res) => {
   // REMOVED A BLOCK OF CODE FOR SIMPLICITY, BUT CAN PUT BACK IN IF NEEDED//
   var options = { 
       format: 'Letter',
       directory: "/tmp", 
       timeout: 540000,  // in milliseconds
   };
    
   const blookFileName = createFileName(blookData.title) + '.pdf';
    
   const tempFilePath = path.join(os.tmpdir(), `${blookFileName}`);

   const htmlFilePath = path.join(os.tmpdir(), 'book.html');

   const htmlFs = fs.openSync(htmlFilePath, 'w');

   await fs.promises.appendFile(htmlFilePath, bookHTML);

   const fd = fs.openSync(tempFilePath, 'w');

   var html = fs.readFileSync(htmlFilePath, 'utf8');

   return cors(req, res, () => {
        pdf.create(html, options).toFile(tempFilePath, async (err, res) => {
           if (err) return console.error(err);
           let mailgunObject = await sendEmail(tempFilePath, kindleEmail);
           fs.closeSync(fd);
           fs.closeSync(htmlFs);
           return console.log(res);
        });

        res.status(200).type('application/json').send({'response': 'Success'})
    })
Deep
  • 223
  • 3
  • 12
0

I got the same issue. Actualy I realized that when I called the function using html-pdf through Postman, or simply through a request by Google Chrome, the pdf used to generate within a 2 or 3 seconds, whereas it was more 2 or 3 minutes when calling it directly from my app. So this is what I did : putting html-pdf in a separate function that I deployed, and then calling it :

https = require('https'); 
https.get(https://us-central1-your-project-name.cloudfunctions.net/your-function-using-html-pdf)