#[SOLVED] Using Puppeteer in a Function?

65 messages · Page 1 of 1 (latest)

uncut kernel
#

I'm trying to create PDF's from HTML in a Function, and right now I am getting this error

Error: Could not find Chrome (ver. 119.0.6045.105). This can occur if either
 1. you did not perform an installation before running the script (e.g. `npx puppeteer browsers install chrome`) or
 2. your cache path is incorrectly configured (which is: /root/.cache/puppeteer).
For (2), check out our guide on configuring puppeteer at https://pptr.dev/guides/configuration.
    at ChromeLauncher.resolveExecutablePath (file:///usr/local/server/src/function/node_modules/puppeteer-core/lib/esm/puppeteer/node/ProductLauncher.js:262:27)
    at ChromeLauncher.executablePath (file:///usr/local/server/src/function/node_modules/puppeteer-core/lib/esm/puppeteer/node/ChromeLauncher.js:213:25)
    at ChromeLauncher.computeLaunchArguments (file:///usr/local/server/src/function/node_modules/puppeteer-core/lib/esm/puppeteer/node/ChromeLauncher.js:107:37)
    at async ChromeLauncher.launch (file:///usr/local/server/src/function/node_modules/puppeteer-core/lib/esm/puppeteer/node/ProductLauncher.js:53:28)
    at async Module.default (file:///usr/local/server/src/function/src/main.js:15:19)
    at async execute (/usr/local/server/src/server.js:141:22)
    at async /usr/local/server/src/server.js:158:13

which makes sense, but I changed the command

npm install && npx puppeteer browsers install chrome and that ran, but it still doesn't know, so do I need to set the location or cache location? Anyone done this yet?

uncut kernel
#

so I found out it's installing

#

it's just it's installing to executablePath: '/root/.cache/puppeteer/chrome/linux-119.0.6045.105/chrome-linux64/chrome',

inner stratus
#

package.json:

{
  "name": "starter-template",
  "version": "1.0.0",
  "description": "",
  "main": "src/main.js",
  "type": "module",
  "scripts": {
    "format": "prettier --write ."
  },
  "dependencies": {
    "node-appwrite": "^9.0.0",
    "puppeteer": "^21.3.6"
  },
  "devDependencies": {
    "prettier": "^3.0.0"
  }
}
#

function variables:

PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
uncut kernel
#

interesting

#

so I can't use default okay one sec

inner stratus
#

main.js

import { execSync } from 'node:child_process';
import puppeteer from 'puppeteer';

let installed = false

export default async (context) => {
  try {

    if (installed) {
      context.log('already installed chromium');
    } else {
      execSync('apk add /usr/local/server/src/function/*.apk');
      context.log('installed chromium');
      installed = true;
    }


    const browser = await puppeteer.launch({
      headless: true,
      args: [
        "--no-sandbox",
        "--headless",
        "--disable-gpu",
        "--disable-dev-shm-usage",
      ],
    });
    return context.res.send("loaded puppeteer");
  } catch (e) {
    return context.res.send(e.message);
  }
};
uncut kernel
#

wow thank you

inner stratus
#

1st execution:

uncut kernel
#

interesting

#

and that stays in that docker?

#

so each deployment isn't a fresh install but more of a container overall and then updating it?

inner stratus
# uncut kernel and that stays in that docker?

so the idea is the build command downloads the OS level dependencies and so it gets added to the build output. When your function executes for the first time, the OS level dependencies get installed.

uncut kernel
#

so I did

uncut kernel
#

so the build auto-fetches the latest version cause what if new features or idk but I wanted a way for that to work

#

then I did

export default async ({ req, res, log, error }) => {
  try {
    log(`Starting Puppeteer timestamp (seconds): ${Date.now() / 1000}`);
    
    // Add the executablePath to the launch options
    const browser = await Puppeteer.launch({
      headless: "new",
      executablePath: '/usr/bin/chromium-browser', // this is the path to the docker container's chromium-browser executable
      args: [
        '--no-sandbox', 
        '--disable-setuid-sandbox',
        '--disable-gpu',
        '--headless',
        '--disable-dev-shm-usage',
      ] // these args are often necessary in a Docker environment
    });

    const page = await browser.newPage();
    await page.setContent(req.body.html);
    const pdf = await page.pdf({ format: 'letter' });
    await browser.close();

    log(`Ending Puppeteer timestamp (seconds): ${Date.now() / 1000}`);

    res.setHeader('Content-Type', 'application/pdf');
    res.setHeader('Content-Disposition', 'attachment; filename=document.pdf');
    return res.send(pdf);
  } catch (e) {
    error('Error generating PDF', e);
    return res.send('Failed to generate PDF');
  }
};

and that fails after 2s

#

which

#

can I send a PDF like that?

#

lol

uncut kernel
#

or do I need to base64 encode it

uncut kernel
inner stratus
uncut kernel
#

rip so must send base64 if I don't auto-upload to storage?

inner stratus
inner stratus
uncut kernel
#

yeah that's what I do oh I see what you mean

#

yeah that's what I was gonna do was just testing to see if it worked

uncut kernel
#

still getting an unknown error

#

need to add more logging to know where one sec

inner stratus
uncut kernel
#
export default async ({ req, res, log, error }) => {
  try {
    log(`Starting Puppeteer timestamp (seconds): ${Date.now() / 1000}`);
    const client = new Client()
       .setEndpoint('https://appwrite.pva.ai/v1')
       .setProject(process.env["APPWRITE_FUNCTION_PROJECT_ID"])
       .setKey(process.env["APPWRITE_API_KEY"]);
    const storage = new Storage(client);

    const userId = req.body.userId;
    
    // Add the executablePath to the launch options
    const browser = await Puppeteer.launch({
      headless: "new",
      executablePath: '/usr/bin/chromium-browser', // this is the path to the docker container's chromium-browser executable
      args: [
        '--no-sandbox', 
        '--disable-setuid-sandbox',
        '--disable-gpu',
        '--headless',
        '--disable-dev-shm-usage',
      ] // these args are often necessary in a Docker environment
    });
    log(`Browser launched`);
    const page = await browser.newPage();
    log(`Page created`);
    await page.setContent(req.body.html);
    log(`Content set`);
    const pdf = await page.pdf({ format: 'letter' });
    log(`PDF generated`);
    await browser.close();

    log(`Ending Puppeteer timestamp (seconds): ${Date.now() / 1000}`);
    const file = await storage.createFile(
      "mystorageid",
      ID.unique(),
      InputFile.fromBuffer(pdf, `document_${new Date.now()}.pdf`),
    )

    log(`File ID: ${file.$id}`);
    log(`File Location: ${storage.getFileView("mystorageid", file.$id)}`)

    return res.send(file.$id);
  } catch (e) {
    error('Error generating PDF', e);
    return res.send('Failed to generate PDF');
  }
};
inner stratus
uncut kernel
#

the file.sync?

inner stratus
uncut kernel
#

I don't understand, that runs the install command no?

#

Mine is in the build process so it's always there is the thought, why would that matter here?

inner stratus
#

and it needs to be done in the function

uncut kernel
#

huh, oky

inner stratus
#

the build container is not the same container where your function gets exeucted. Also, the container can be removed and the nrecreated. That's why you always need to make sure to install the dependencies at runtime

uncut kernel
#

interesting, lemme try this rq

#

see my issue with the one executor is a perfect example here

#

a PDF generation thus far has taken 2s, if 1000 people want one, I can't have it as an Appwrite Function no?

#

oof 9s to successfully generate one

inner stratus
inner stratus
uncut kernel
#

yeah, oh true

#

the actual execution took 7 seconds I believe

#

including install

#

so lemme try again

#

oh yeah

uncut kernel
#

fails here

log(`Ending Puppeteer timestamp (seconds): ${Date.now() / 1000}`);
    const fileToUpload = InputFile.fromBuffer(pdf, `document_${new Date.now()}_upload.pdf`);
    log(`File to upload created, trying to create file in Storage`);
    const file = await storage.createFile(
      "my_storage_id",
      ID.unique(),
      fileToUpload,
    )
#

could it be that it's getting the wrong mimetype? it has no error that it's actually logging

inner stratus
inner stratus
#

hmm how about:

import { Readable } from "stream";

// ...

    const stream = Readable.from(b);
    const size = Buffer.byteLength(b);
    const inputFile = InputFile.fromStream(stream, `document_${new Date.now()}_upload.pdf`, size);
uncut kernel
#

[SOLVED] Using Puppeteer in a Function?

lusty lynx