UNPKG

@sparticuz/chromium

Version:

Chromium Binary for Serverless Platforms

127 lines (126 loc) 4.51 kB
import { createWriteStream, rm } from "node:fs"; import { access, symlink } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { Readable } from "node:stream"; import { pipeline } from "node:stream/promises"; import { extract } from "tar-fs"; /** * Creates a symlink to a file */ export const createSymlink = async (source, target) => { await access(source); await symlink(source, target); }; /** * Downloads a file from a URL */ export const downloadFile = async (url, outputPath) => { const response = await fetch(url, { redirect: "follow" }); if (!response.ok) { throw new Error(`Unexpected status code: ${String(response.status)}.`); } if (!response.body) { throw new Error("Response body is empty."); } await pipeline(Readable.fromWeb(response.body), createWriteStream(outputPath)); }; /** * Adds the proper folders to the environment * @param baseLibPath the path to this packages lib folder */ export const setupLambdaEnvironment = (baseLibPath) => { // If the FONTCONFIG_PATH is not set, set it to /tmp/fonts process.env["FONTCONFIG_PATH"] ??= join(tmpdir(), "fonts"); // Set up Home folder if not already set process.env["HOME"] ??= tmpdir(); // If LD_LIBRARY_PATH is undefined, set it to baseLibPath, otherwise, add it if (process.env["LD_LIBRARY_PATH"] === undefined) { process.env["LD_LIBRARY_PATH"] = baseLibPath; } else if (!process.env["LD_LIBRARY_PATH"].startsWith(baseLibPath)) { process.env["LD_LIBRARY_PATH"] = [ baseLibPath, ...new Set(process.env["LD_LIBRARY_PATH"].split(":")), ].join(":"); } }; /** * Determines if the input is a valid URL * @param input the input to check * @returns boolean indicating if the input is a valid URL */ export const isValidUrl = (input) => { try { const url = new URL(input); if (url.protocol === "https:") return true; // Allow http:// only for localhost/127.0.0.1 (development/testing) if (url.protocol === "http:" && (url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]")) { return true; } return false; } catch { return false; } }; /** * Determines if the running instance is inside an Amazon Linux 2023 container, * AWS_EXECUTION_ENV is for native Lambda instances * AWS_LAMBDA_JS_RUNTIME is for netlify instances * CODEBUILD_BUILD_IMAGE is for CodeBuild instances * VERCEL is for Vercel Functions (Node 20 or later enables an AL2023-compatible environment). * @returns boolean indicating if the running instance is inside a Lambda container with nodejs20 */ export const isRunningInAmazonLinux2023 = (nodeMajorVersion) => { const awsExecEnv = process.env["AWS_EXECUTION_ENV"] ?? ""; const awsLambdaJsRuntime = process.env["AWS_LAMBDA_JS_RUNTIME"] ?? ""; const codebuildImage = process.env["CODEBUILD_BUILD_IMAGE"] ?? ""; // Check for explicit version substrings, returns on first match if (awsExecEnv.includes("20.x") || awsExecEnv.includes("22.x") || awsExecEnv.includes("24.x") || awsLambdaJsRuntime.includes("20.x") || awsLambdaJsRuntime.includes("22.x") || awsLambdaJsRuntime.includes("24.x") || codebuildImage.includes("nodejs20") || codebuildImage.includes("nodejs22") || codebuildImage.includes("nodejs24")) { return true; } // Vercel: Node 20+ is AL2023 compatible if (process.env["VERCEL"] && nodeMajorVersion >= 20) { return true; } return false; }; export const downloadAndExtract = async (url) => { const destDir = join(tmpdir(), "chromium-pack"); const response = await fetch(url, { redirect: "follow", signal: AbortSignal.timeout(300_000), }); if (!response.ok) { throw new Error(`Unexpected status code: ${String(response.status)}.`); } if (!response.body) { throw new Error("Response body is empty."); } try { await pipeline(Readable.fromWeb(response.body), extract(destDir)); } catch (error) { // Clean up partial extraction on failure await new Promise((resolve) => { rm(destDir, { force: true, recursive: true }, () => { resolve(); }); }); throw error; } return destDir; };