Using headless-chrome / Chromeless with Meteor for PDF generation

Hey, hard reset of the project helped i.e., meteor reset. Also, make sure you install puppeteer, like so meteor npm install --save puppeteer. Then, simply use it in an async method on your server.

I’m trying to use Puppeteer / headless Chrome in a METEOR@1.6.1 project.
I’ve installed puppeteer using:
meteor npm install --save puppeteer
When I run my app, after 30 seconds it crashes with this error:
(node:13927) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Navigation Timeout Exceeded: 30000ms exceeded
I have not run any Puppeteer script. All I have done is install Puppeteer and run my app as usual. So it seems Puppeteer has completely broken my app!
Has anybody seen a similar problem or got any ideas? I have googled for this error and it seems to be common but always after you have run a .js script, which I have not done.

Ah, I think I’ve figured out what I did wrong.

I created a file example.js as per the Puppeteer documentation, with some code to run in Puppeteer. And I saved it in the root of my Meteor project. So of course Meteor automatically ran it - before the app had launched and the page rendered.

Moving my example.js into the /tests folder solved the problem: the file now does not execute until I run it manually.

Leaving this up here in case anybody else makes the same simple mistake and this post can save them some time.

1 Like

Hi,

I am able to generate the PDF, but want to know is there anyway i can get the link of the created pdf file.

const browser = await puppeteer.launch();
const page = await browser.newPage()

        // We set the page content as the generated html by handlebars
        await page.setContent(html_string)

        // we Use pdf function to generate the pdf in the same folder as this file.
        await page.pdf({ path: '/usr/Data/Meteor/project/one2/public/allFiles/'+fileName +  '.pdf', format: 'A4' })

        await browser.close();
        console.log("PDF Generated")

Please help its urgent

Getting Error as

built_app/programs/server/npm/node_modules/puppeteer/.local-chromium/linux-768783/chrome-linux/chrome: error while loading shared libraries: libgobject-2.0.so.0: cannot open shared object file: No such file or directory

Using Mup JS for Deployment. Please Suggest if anyone experienced the same

Meteor Version: METEOR@1.10.2

@msavin @klabauter @tomnewmann @CarlosMC @cheerful12 @zodern

Hi,

You need to install the dependencies by including them inside buildInstructions in mup.js.

I’m running an older version than you so the packages might not be the same.

Inside the buildInstructions array:

‘RUN apt-get update && apt-get install -y libpangocairo-1.0-0 libx11-xcb1 libxcomposite1 libxcursor1 libxdamage1 libxi6 libxtst6 libnss3 libcups2 libxss1 libxrandr2 libgconf2-4 libasound2 libatk1.0-0 libgtk-3-0’,

I’m also running puppeteer with the following arguments:

puppeteer.launch({ args: [’–no-sandbox’, ‘–disable-setuid-sandbox’], headless: true });

I’ve been using puppeteer for a while now. When using puppeteer with Meteor and deploying with MUP you’ll need to install necessary shared library dependencies as desicribed here.

Alternatively you can use the custom imageI’ve created in your mup.js config file:

    docker: {
      // change to 'abernix/meteord:base' if your app is using Meteor 1.4 - 1.5
      image: 'fedescoinc/meteord-node-with-chromium_dumb-init:node-12.16.1-base',
    },

The link to the images working with Meteor 1.10.2 (node 12.16.1):
https://hub.docker.com/repository/docker/fedescoinc/meteord-node-with-chromium_dumb-init/tags

1 Like

Thanks @johannel, Its working. Welcome to Meteor Community also

Just one more thought, Can I also download the file created on the client side after pdf generation also?

This method serves the PDF as a download directly without first saving it on the server, not sure if that fits your case but maby it’s usefull.

I use a package named fileSaver to serve the file as a download (save as) in the brower.

import fileSaver from 'file-saver';

Method on the client side that calls the server method to generate the PDF and then serves it as a download in the browser.

Meteor.call('generatePDF', { }, (error, response) => {
  if (error) {
    console.log(error.reason);
  } else {
    const blob = base64ToBlob(response.base64);
    fileSaver.saveAs(blob, `${response.fileName}.pdf`);
  }
});

In your puppeteer code instead of generating the pdf in the folder create it as a const and return it as base64.

  const pdfContent = await page.pdf({format: 'A4'});

  const base = await Buffer.from(pdfContent).toString('base64');

  await browser.close();

  return { "your_file_name", base64: base };

It’s a little cut and paste because I have left out the part of the method that generates the HTML as I understand you got that working already.

1 Like

Like @zayco mentioned you can use a Meteor.call to get the base64 string to serve.

I haven’t served static files like this before but I’m not sure if bigger files will become an issue when going the Meteor.call route. (Maybe someone can clarify or correct me)

When not specifying a path property like page.pdf({path: 'path_to_save_file_to'}) the file is saved in memory (as described here).

What I’ve found to work is to install the meteorhacks:picker package and body-parser npm package. Then serve an endpoint to the file like so:

import puppeteer from 'puppeteer';
import bodyParser from 'body-parser';

Picker.middleware( bodyParser.raw() );
Picker.middleware( bodyParser.urlencoded( { extended: false } ) );

Picker.route('/viewpdf', async function(params, req, res, next) {
  const { url } = params.query;
  const browser = await puppeteer.launch({args: ['--no-sandbox']});
  const page = await browser.newPage();
  await page.goto(url);
  const pdf = await page.pdf();
  await browser.close();
  res.writeHead(200, {
    'Content-Type': 'application/pdf'
  });
  res.write(pdf);
  res.end('OK');
});

This will allow you to go to a url like http://localhost:3000/viewpdf?url=https://example.com and including the url in the params to view the file in memory. You’ll need extra error handling etc but I hope you get the point.

This is what I’ve found works and I’m not saying it’s the best option. If anyone has a better solution please share it.

Hope this helps.

1 Like

Aside note from all good suggestions, For my project I have been using jsreport as it makes generating reports and PDF documents a lot easier.

1 Like

@johannel Thanks a lot!

Thanks @zayco. It was really helpful

I would personally do it on another self hosted instance running prerender.com (the other service from prerender.io) or with a lambda function but in all cases, not on my Meteor server(s).

2 Likes

Were you able to get puppeteer to run on Galaxy?

1 Like

Push :slight_smile:
I successfully run an meteor app with puppeteer in heroku using this buildpack ( I have to use an old puppeteer version (3.0.0) because the size of the newer versions exeeds the maximum size (500mb) for heroku apps, but for PDF creation this is fairly sufficient)
Like @zayco stated meteor-up (mup) has the ability to attach buildInstructions, so the missing shared libraries can be installed that way. So the question is:
Is there a way to attach buildInstructions for shared libraries to a meteor deploy at Galaxy?

1 Like

Also interested in deploying an app on galaxy that uses puppeteer.

1 Like

Hey everyone, I think the Meteor Galaxy team just made it VERY easy to now run a Docker base image with puppeteer :tada:. I need to try this out still, so if anyone beats me to it, please share how it worked for you here.

Just add the “baseImage” spec to you settings.json file :raised_hands::grinning:

{
  "galaxy.meteor.com": {
    "env": { "MONGO_URL": "mongodb://..." },
    "baseImage": {
      "repository": "meteor/galaxy-puppeteer",
      "tag": "latest"
    }
  }
}

Meteor Galaxy Docs:

Freshly Released meteor-puppeteer Docker Image on GitHub:

3 Likes

I followed the instructions in the galaxy docs, but I’m still getting the following errors:

(node:8) UnhandledPromiseRejectionWarning: Error: Failed to launch the browser process!

[0513/175101.669695:ERROR:zygote_host_impl_linux.cc(90)] Running as root without --no-sandbox is not supported. See https://crbug.com/638180.
    at Interface.<anonymous> (/app/bundle/programs/server/npm/node_modules/puppeteer/lib/cjs/puppeteer/node/BrowserRunner.js:184:68)
    at onClose (/app/bundle/programs/server/npm/node_modules/puppeteer/lib/cjs/puppeteer/node/BrowserRunner.js:194:20)

TROUBLESHOOTING: https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md

    at Socket.onend (readline.js:194:10)
    at Interface.close (readline.js:416:8)
    at Interface.EventEmitter.emit (domain.js:483:12)
    at Interface.emit (events.js:326:22)
    at endReadableNT (_stream_readable.js:1241:12)
    at Socket.EventEmitter.emit (domain.js:483:12)
    at Socket.emit (events.js:326:22)
    at Function.Promise.await (/app/bundle/programs/server/npm/node_modules/meteor/promise/node_modules/meteor-promise/promise_server.js:56:12)
 => awaited here:
    at processTicksAndRejections (internal/process/task_queues.js:84:21)
(node:8) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 27)
    at /app/bundle/programs/server/npm/node_modules/meteor/promise/node_modules/meteor-promise/fiber_pool.js:43:40
    at imports/api/puppeteer.js:24:17
(node:8) UnhandledPromiseRejectionWarning: Error: Failed to launch the browser process!
(node:8) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 28)
    at /app/bundle/programs/server/npm/node_modules/meteor/promise/node_modules/meteor-promise/fiber_pool.js:43:40
    at imports/api/puppeteer.js:98:17
    at Function.Promise.await (/app/bundle/programs/server/npm/node_modules/meteor/promise/node_modules/meteor-promise/promise_server.js:56:12)
 => awaited here:
    at processTicksAndRejections (internal/process/task_queues.js:84:21)
    at endReadableNT (_stream_readable.js:1241:12)
    at Socket.EventEmitter.emit (domain.js:483:12)
    at Socket.emit (events.js:326:22)
    at Socket.onend (readline.js:194:10)
    at Interface.close (readline.js:416:8)
    at Interface.EventEmitter.emit (domain.js:483:12)
    at Interface.emit (events.js:326:22)
    at Interface.<anonymous> (/app/bundle/programs/server/npm/node_modules/puppeteer/lib/cjs/puppeteer/node/BrowserRunner.js:184:68)
    at onClose (/app/bundle/programs/server/npm/node_modules/puppeteer/lib/cjs/puppeteer/node/BrowserRunner.js:194:20)

Anyone else running into this issue or able to get Puppeteer working on Galaxy?

Looks like you just might need to add the Meteor Galaxy Docker args when launching the puppeteer browser.

const browser = await puppeteer.launch({
    args: [
    '--no-sandbox',
    '--disable-setuid-sandbox',
    '--disable-dev-shm-usage'
    ]
});
const page = await browser.newPage();
await page.goto("https://www.google.com");

I had to read a bit about why --no-sandbox is required and I found this Github page helpful.

This is a common approach used when running puppeteer on a Docker container.

1 Like