Puppeteer incompatible with Vercel serverless functions? (Next 13) - node.js

I deployed an API route handler with Next.js 13 that uses Puppeteer. When I call this api route in production, I get this error message in 'Function Logs':
[POST] /api/getLinkedin
00:00:21:62
2023-02-02T00:00:23.103Z 7545c99c-ea8f-41d3-8771-97eb786502cb ERROR Error: Could not find Chromium (rev. 1083080). This can occur if either
1. you did not perform an installation before running the script (e.g. `npm install`) or
2. your cache path is incorrectly configured (which is: /home/sbx_user1051/.cache/puppeteer).
For (2), check out our guide on configuring puppeteer at https://pptr.dev/guides/configuration.
at ChromeLauncher.resolveExecutablePath (/var/task/node_modules/puppeteer-core/lib/cjs/puppeteer/node/ProductLauncher.js:127:27)
at ChromeLauncher.executablePath (/var/task/node_modules/puppeteer-core/lib/cjs/puppeteer/node/ChromeLauncher.js:205:25)
at ChromeLauncher.launch (/var/task/node_modules/puppeteer-core/lib/cjs/puppeteer/node/ChromeLauncher.js:93:37)
at async handler (/var/task/.next/server/pages/api/getLinkedin.js:27:21)
at async Object.apiResolver (/var/task/node_modules/next/dist/server/api-utils/node.js:372:9)
at async NextNodeServer.runApi (/var/task/node_modules/next/dist/server/next-server.js:488:9)
at async Object.fn (/var/task/node_modules/next/dist/server/next-server.js:751:37)
at async Router.execute (/var/task/node_modules/next/dist/server/router.js:253:36)
at async NextNodeServer.run (/var/task/node_modules/next/dist/server/base-server.js:384:29)
at async NextNodeServer.handleRequest (/var/task/node_modules/next/dist/server/base-server.js:322:20)
2023-02-02T00:00:23.124Z 7545c99c-ea8f-41d3-8771-97eb786502cb ERROR Error: Could not find Chromium (rev. 1083080). This can occur if either
1. you did not perform an installation before running the script (e.g. `npm install`) or
2. your cache path is incorrectly configured (which is: /home/sbx_user1051/.cache/puppeteer).
For (2), check out our guide on configuring puppeteer at https://pptr.dev/guides/configuration.
at ChromeLauncher.resolveExecutablePath (/var/task/node_modules/puppeteer-core/lib/cjs/puppeteer/node/ProductLauncher.js:127:27)
at ChromeLauncher.executablePath (/var/task/node_modules/puppeteer-core/lib/cjs/puppeteer/node/ChromeLauncher.js:205:25)
at ChromeLauncher.launch (/var/task/node_modules/puppeteer-core/lib/cjs/puppeteer/node/ChromeLauncher.js:93:37)
at async handler (/var/task/.next/server/pages/api/getLinkedin.js:27:21)
at async Object.apiResolver (/var/task/node_modules/next/dist/server/api-utils/node.js:372:9)
at async NextNodeServer.runApi (/var/task/node_modules/next/dist/server/next-server.js:488:9)
at async Object.fn (/var/task/node_modules/next/dist/server/next-server.js:751:37)
at async Router.execute (/var/task/node_modules/next/dist/server/router.js:253:36)
at async NextNodeServer.run (/var/task/node_modules/next/dist/server/base-server.js:384:29)
at async NextNodeServer.handleRequest (/var/task/node_modules/next/dist/server/base-server.js:322:20)
RequestId: 7545c99c-ea8f-41d3-8771-97eb786502cb Error: Runtime exited with error: exit status 1
Runtime.ExitError
I went to the Puppeteer docs and I changed the config file as shown:
const {join} = require('path');
/**
* #type {import("puppeteer").Configuration}
*/
module.exports = {
// Changes the cache location for Puppeteer.
cacheDirectory: join(__dirname, '.cache', 'puppeteer'),
};
That didn't fix it.

Related

#aws-sdk/client-sts - TypeError: (0 , smithy_client_1.parseRfc3339DateTimeWithOffset) is not a function

I'm facing issue using sts client on lambdas.
The current code was working two days ago.
const {
STSClient,
AssumeRoleCommand,
} = require('#aws-sdk/client-sts')
const stsClient = new STSClient({
region: process.env.REGION || 'eu-west-1',
})
const params = new AssumeRoleCommand({
RoleArn: process.env.MARKETPLACE_RESOLVE_CUSTOMER_ROLE_ARN,
RoleSessionName: `${
process.env.AWS_LAMBDA_FUNCTION_NAME
}-${new Date().getTime()}`,
})
const assumedRoleOutput = await stsClient.send(params)
Now it always throws an exception as follow:
2023-02-08T08:07:18.684Z 1a7dd68d-da00-4b07-935c-2f6bc95f996f ERROR TypeError: (0 , smithy_client_1.parseRfc3339DateTimeWithOffset) is not a function
at deserializeAws_queryCredentials (/opt/nodejs/node_modules/#aws-sdk/client-sts/dist-cjs/protocols/Aws_query.js:860:117)
at deserializeAws_queryAssumeRoleResponse (/opt/nodejs/node_modules/#aws-sdk/client-sts/dist-cjs/protocols/Aws_query.js:756:32)
at deserializeAws_queryAssumeRoleCommand (/opt/nodejs/node_modules/#aws-sdk/client-sts/dist-cjs/protocols/Aws_query.js:119:16)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async /opt/nodejs/node_modules/#aws-sdk/client-sts/node_modules/#aws-sdk/middleware-serde/dist-cjs/deserializerMiddleware.js:7:24
at async /opt/nodejs/node_modules/#aws-sdk/client-sts/node_modules/#aws-sdk/middleware-signing/dist-cjs/middleware.js:14:20
at async StandardRetryStrategy.retry (/opt/nodejs/node_modules/#aws-sdk/client-sts/node_modules/#aws-sdk/middleware-retry/dist-cjs/StandardRetryStrategy.js:51:46)
at async /opt/nodejs/node_modules/#aws-sdk/client-sts/node_modules/#aws-sdk/middleware-logger/dist-cjs/loggerMiddleware.js:5:22
at async getMarketplaceResolveCustomerRoleCredentials (/var/task/utils/marketplaceUtils.js:27:29)
at async Object.resolveMarketplaceCustomer (/var/task/utils/marketplaceUtils.js:50:5) {
'$metadata': { attempts: 1, totalRetryDelay: 0 }
I've tried it with the #aws-sdk/client-sts at versions 3.266.0 and 3.224.0
The problem was the incorrect aws-sdk version installed during the creation of the layer.
I use a docker file to install all the dependencies used by my lambdas and was using the command like:
RUN npm i #aws-sdk/client-sts#3.224.0
RUN npm i #aws-sdk/client-marketplace-entitlement-service#3.266.0
So there was inconsistency between sdk versions probably?
So I tried to create the layer without including the aws sdk modules (removed by hand) and it worked (the sdk is included in lambda execution enviroment)
But I faced another error with aws-sdk/client-marketplace-entitlement-service that where fixed some times ago git issue
So I've changed the commands on the Dockerfile to install latest major release I wanted, as follow:
RUN npm i #aws-sdk/client-sts#3
RUN npm i #aws-sdk/client-marketplace-entitlement-service#3
and now it works!

Jest test fail: "● default root route"

I'm trying to write Jest tests for a Fastify project. But I'm stuck with the example code failing with an ambiguous error: "● default root route".
// root.test.ts
import { build } from '../helper'
const app = build()
test('default root route', async () => {
const res = await app.inject({
url: '/'
})
expect(res.json()).toEqual({ root: true })
})
// helper.ts
import Fastify from "fastify"
import fp from "fastify-plugin"
import App from "../src/app"
export function build() {
const app = Fastify()
beforeAll(async () => {
void app.register(fp(App))
await app.ready()
})
afterAll(() => app.close())
return app
}
// console error:
FAIL test/routes/root.test.ts (8.547 s)
● default root route
A worker process has failed to exit gracefully and has been force exited. This is likely caused by tests leaking due to improper teardown. Try running with --detectOpenHandles to find leaks. Active timers can also cause this, ensure that .unref() was called on them.
What am I doing wrong?
After running --detectOpenHandles, Jest reported that open ioredis connections were timing out.
I hooked up ioredis instances to Fastify lifecycle with fastify-redis and the test passed.

Using serverless deployment to lambda with ES6 /Node.js v16

Newbie question....
I have a locally working node.js application which I am now trying to deploy express to AWS lambda. I have used this guide to deploy a test version (which worked).
I now am trying to implement my application which uses ES6 (and has type: module in package.json).
In my application I have added
import serverless from 'serverless-http'
but I cannot figure out the appropriate syntax for the export - the original was...
module.exports.handler = serverless(app);
I have tried:
const handler = async (app) =\> {
return serverless(app)
}
export default handler
Error message received:
2022-11-05T15:50:25.962Z undefined ERROR Uncaught Exception
"errorType": "Runtime.HandlerNotFound",
"errorMessage": "app.handler is undefined or not exported",
"stack": [
"Runtime.HandlerNotFound: app.handler is undefined or not exported",
" at Object.UserFunction.js.module.exports.load (file:///var/runtime/index.mjs:979:15)",
" at async start (file:///var/runtime/index.mjs:1137:23)",
" at async file:///var/runtime/index.mjs:1143:1"
]
I strongly suspect I am missing some fundamental understanding - truly appreciate some guidance.
The reason for the error is that you are sending a default export when AWS Lambda is expecting a named export.
The issue is the same as with all ES6 imports/exports:
// export.js
export default const defaultExport = "foo"
export const namedExport = "bar"
// import.js
import { defaultExport } from "./export.js" // error, cannot find defaultExport
import { namedExport } from "./export.js" // success, found namedExport
import defaultExport from "./export.js" // success, found defaultExport
So, it's like the case above where you're sending the defaultExport, but AWS Lambda wants the { namedExport }. You just need to remove default from your export, and make sure you're building the handler properly. Here is a suggestion to do that:
const lambda = serverless(app)
export async function handler(event, context) {
return lambda(event, context)
}
I've tested and it's working with serverless-offline using Node18.x. You can read more about exports on MDN.

Puppeteer: TypeError: Readable is not a constructor

I have been trying to use Puppeteer#15.5.0 to generate a PDF on the server side in Node.js.
import { launch } from 'puppeteer';
...
const browser = await launch();
const page = await browser.newPage();
await page.setContent('COME ON!');
console.log(await page.content());
const pdfBuffer = await page.pdf();
The console.log statement gives me the expected output of <html><head></head><body>COME ON!</body></html>
It then runs into the following error:
Error:
TypeError: Readable is not a constructor
at getReadableFromProtocolStream (/Users/kaziehsanaziz/Work/DocSpace/repos/docspace-pay/.webpack/service/src/public-lambda.js:405775:12)
at runMicrotasks (<anonymous>)
at processTicksAndRejections (internal/process/task_queues.js:95:5)
at async Page.pdf (/Users/kaziehsanaziz/Work/DocSpace/repos/docspace-pay/.webpack/service/src/public-lambda.js:403129:26)
at async /Users/kaziehsanaziz/Work/DocSpace/repos/docspace-pay/.webpack/service/src/public-lambda.js:329729:31
Puppeteer cannot be bundled using Webpack. The issue was that I was trying to do just that. In my case, since I was using Serverless, the solution was to tell the serverless-bundle plugin to not bundle the Puppeteer.
bundle:
packager: yarn
esbuild: true
forceExclude:
- aws-sdk
- puppeteer
externals:
- puppeteer-core
- '#sparticuz/chrome-aws-lambda'
The forceExclude is doing the trick here for the local environment. The external is what's helping the Production environment.
I have also run into this issue. It occurs when webpack (v5 on my end) bundles puppeteer. I have solved it by explicitly declaring webpack ignore directive when importing a file which uses puppeteer. I did this via dynamic es import, but a static one could be done in a very similar way:
const loadModule = async (modulePath) => {
try {
return await import(/* webpackIgnore: true */ modulePath)
} catch (e) {
throw new ImportError(`Unable to import module ${modulePath}`)
}
}
const renderPdf = (await loadModule('../../renderPdf/index.js')).default
use require puppeteer instead of import puppeteer statement

Error:"Failed to get the current sub/segment from the context" when use AWS X-ray in Lambda with node.js

I am trying to use implement the AWS X-ray into my current project (using Node.js and Serverless framework). I am trying to wire the X-ray to one of my lambda function, I got the problem of
Error: Failed to get the current sub/segment from the context.
at Object.contextMissingRuntimeError [as contextMissing] (/.../node_modules/aws-xray-sdk-core/lib/context_utils.js:21:15)
at Object.getSegment (/.../node_modules/aws-xray-sdk-core/lib/context_utils.js:92:45)
at Object.resolveSegment (/.../node_modules/aws-xray-sdk-core/lib/context_utils.js:73:19).....
code below:
import { DynamoDB } from "aws-sdk";
import AWSXRay from 'aws-xray-sdk';
export const handler = async (event, context, callback) => {
const dynamo = new DynamoDB.DocumentClient({
service: new DynamoDB({ region })
});
AWSXRay.captureAWSClient(dynamo.service);
try {
// call dynamoDB function
} catch(err) {
//...
}
}
for this problem, I use the solution from
https://forums.aws.amazon.com/thread.jspa?messageID=821510&#821510
the other solution I tried is from https://forums.aws.amazon.com/thread.jspa?messageID=829923&#829923
code is like
import AWSXRay from 'aws-xray-sdk';
const AWS = AWSXRay.captureAWS(require('aws-sdk'));
export const handler = async (event, context, callback) => {
const dynamo = new AWS.DynamoDB.DocumentClient({region});
//....
}
Still not working...
Appreciated to the help of any kind.
As you mention, that happened because you're running locally (using serverless-offline plugin) and the serverless-offline plugin doesn't provide a valid XRAY context.
One possible way to pass this error and still be able to call your function locally is setting AWS_XRAY_CONTEXT_MISSING environment variable to LOG_ERROR instead of RUNTIME_ERROR (default).
Something like:
serverless invoke local -f functionName -e AWS_XRAY_CONTEXT_MISSING=LOG_ERROR
I didn't test this using serverless framework but it worked when the same error occurred calling an amplify function locally:
amplify function invoke <function-name>
I encountered this error also. To fix it, I disabled XRay when running locally. XRay isn't needed when running locally because I can just set up debug log statements at that time.
This is what the code would look like
let AWS = require('aws-sdk');
if (!process.env.IS_OFFLINE) {
const AWSXRay = require('aws-xray-sdk');
AWS = AWSXRay.captureAWS(require('aws-sdk'));
}
If you don't like this approach, you can set up a contextStrategy to not error out when the context is missing.
Link here
AWSXRay.setContextMissingStrategy("LOG_ERROR");
If you don't want the error clogging up your output you can add a helper that ignores only that error.
// Removes noisy Error: Failed to get the current sub/segment from the context due to Xray
export async function disableXrayError() {
console.error = jest.fn((err) => {
if (err.message.includes("Failed to get the current sub/segment from the context")) {
return;
} else {
console.error(err);
}
});
}

Resources