lint and cleanup

This commit is contained in:
Kyle McNally 2024-04-15 11:56:37 -04:00
parent 1151d3bc96
commit e6f131ca70
6 changed files with 166 additions and 175 deletions

View File

@ -3,7 +3,7 @@ const { createHash } = require("node:crypto");
const puppeteer = require("puppeteer-core"); const puppeteer = require("puppeteer-core");
const chromium = require("@sparticuz/chromium"); const chromium = require("@sparticuz/chromium");
exports.handler = async (event, context) => { exports.handler = async (event) => {
let browser = null; let browser = null;
try { try {

23
package-lock.json generated
View File

@ -13,25 +13,18 @@
"tar-fs": "^3.0.5" "tar-fs": "^3.0.5"
}, },
"devDependencies": { "devDependencies": {
"@tsconfig/node16": "^16.1.3",
"@tsconfig/node20": "^20.1.4", "@tsconfig/node20": "^20.1.4",
"@tsconfig/strictest": "^2.0.5", "@tsconfig/strictest": "^2.0.5",
"@types/follow-redirects": "^1.14.4", "@types/follow-redirects": "^1.14.4",
"@types/node": "^20.12.3", "@types/node": "^20.12.7",
"@types/tar-fs": "^2.0.4", "@types/tar-fs": "^2.0.4",
"clean-modules": "^3.0.5", "clean-modules": "^3.0.5",
"typescript": "^5.4.3" "typescript": "^5.4.5"
}, },
"engines": { "engines": {
"node": ">= 16" "node": ">= 16"
} }
}, },
"node_modules/@tsconfig/node16": {
"version": "16.1.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-16.1.3.tgz",
"integrity": "sha512-9nTOUBn+EMKO6rtSZJk+DcqsfgtlERGT9XPJ5PRj/HNENPCBY1yu/JEj5wT6GLtbCLBO2k46SeXDaY0pjMqypw==",
"dev": true
},
"node_modules/@tsconfig/node20": { "node_modules/@tsconfig/node20": {
"version": "20.1.4", "version": "20.1.4",
"resolved": "https://registry.npmjs.org/@tsconfig/node20/-/node20-20.1.4.tgz", "resolved": "https://registry.npmjs.org/@tsconfig/node20/-/node20-20.1.4.tgz",
@ -54,9 +47,9 @@
} }
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "20.12.3", "version": "20.12.7",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.3.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz",
"integrity": "sha512-sD+ia2ubTeWrOu+YMF+MTAB7E+O7qsMqAbMfW7DG3K1URwhZ5hN1pLlRVGbf4wDFzSfikL05M17EyorS86jShw==", "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"undici-types": "~5.26.4" "undici-types": "~5.26.4"
@ -306,9 +299,9 @@
"dev": true "dev": true
}, },
"node_modules/typescript": { "node_modules/typescript": {
"version": "5.4.3", "version": "5.4.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
"integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
"dev": true, "dev": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",

View File

@ -43,10 +43,10 @@
"@tsconfig/node20": "^20.1.4", "@tsconfig/node20": "^20.1.4",
"@tsconfig/strictest": "^2.0.5", "@tsconfig/strictest": "^2.0.5",
"@types/follow-redirects": "^1.14.4", "@types/follow-redirects": "^1.14.4",
"@types/node": "^20.12.3", "@types/node": "^20.12.7",
"@types/tar-fs": "^2.0.4", "@types/tar-fs": "^2.0.4",
"clean-modules": "^3.0.5", "clean-modules": "^3.0.5",
"typescript": "^5.4.3" "typescript": "^5.4.5"
}, },
"engines": { "engines": {
"node": ">= 16" "node": ">= 16"

View File

@ -1,9 +1,8 @@
import { unlink } from "node:fs";
import { https } from "follow-redirects"; import { https } from "follow-redirects";
import { unlink } from "node:fs";
import { tmpdir } from "node:os"; import { tmpdir } from "node:os";
import { type UrlWithStringQuery, parse } from "node:url";
import { extract } from "tar-fs"; import { extract } from "tar-fs";
import { parse } from "node:url";
import type { UrlWithStringQuery } from "node:url";
interface FollowRedirOptions extends UrlWithStringQuery { interface FollowRedirOptions extends UrlWithStringQuery {
maxBodyLength: number; maxBodyLength: number;
@ -12,7 +11,7 @@ interface FollowRedirOptions extends UrlWithStringQuery {
export const isValidUrl = (input: string) => { export const isValidUrl = (input: string) => {
try { try {
return !!new URL(input); return !!new URL(input);
} catch (err) { } catch {
return false; return false;
} }
}; };
@ -59,18 +58,18 @@ export const downloadAndExtract = async (url: string) =>
new Promise<string>((resolve, reject) => { new Promise<string>((resolve, reject) => {
const getOptions = parse(url) as FollowRedirOptions; const getOptions = parse(url) as FollowRedirOptions;
getOptions.maxBodyLength = 60 * 1024 * 1024; // 60mb getOptions.maxBodyLength = 60 * 1024 * 1024; // 60mb
const destDir = `${tmpdir()}/chromium-pack`; const destinationDirectory = `${tmpdir()}/chromium-pack`;
const extractObj = extract(destDir); const extractObject = extract(destinationDirectory);
https https
.get(url, (response) => { .get(url, (response) => {
response.pipe(extractObj); response.pipe(extractObject);
extractObj.on("finish", () => { extractObject.on("finish", () => {
resolve(destDir); resolve(destinationDirectory);
}); });
}) })
.on("error", (err) => { .on("error", (error) => {
unlink(destDir, (_) => { unlink(destinationDirectory, () => {
reject(err); reject(error);
}); });
}); });
}); });

View File

@ -1,3 +1,4 @@
import { https } from "follow-redirects";
import { import {
access, access,
createWriteStream, createWriteStream,
@ -5,27 +6,19 @@ import {
mkdirSync, mkdirSync,
symlink, symlink,
} from "node:fs"; } from "node:fs";
import { https } from "follow-redirects";
import LambdaFS from "./lambdafs";
import { join } from "node:path"; import { join } from "node:path";
import { URL } from "node:url"; import { URL } from "node:url";
import { import {
downloadAndExtract, downloadAndExtract,
isRunningInAwsLambda, isRunningInAwsLambda,
isValidUrl,
isRunningInAwsLambdaNode20, isRunningInAwsLambdaNode20,
isValidUrl,
} from "./helper"; } from "./helper";
import { inflate } from "./lambdafs";
/** Viewport taken from https://github.com/puppeteer/puppeteer/blob/main/docs/api/puppeteer.viewport.md */ /** Viewport taken from https://github.com/puppeteer/puppeteer/blob/main/docs/api/puppeteer.viewport.md */
interface Viewport { interface Viewport {
/**
* The page width in pixels.
*/
width: number;
/**
* The page height in pixels.
*/
height: number;
/** /**
* Specify device scale factor. * Specify device scale factor.
* See {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio | devicePixelRatio} for more info. * See {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio | devicePixelRatio} for more info.
@ -33,20 +26,28 @@ interface Viewport {
*/ */
deviceScaleFactor?: number; deviceScaleFactor?: number;
/** /**
* Whether the `meta viewport` tag is taken into account. * Specify if the viewport supports touch events.
* @default false * @default false
*/ */
isMobile?: boolean; hasTouch?: boolean;
/**
* The page height in pixels.
*/
height: number;
/** /**
* Specifies if the viewport is in landscape mode. * Specifies if the viewport is in landscape mode.
* @default false * @default false
*/ */
isLandscape?: boolean; isLandscape?: boolean;
/** /**
* Specify if the viewport supports touch events. * Whether the `meta viewport` tag is taken into account.
* @default false * @default false
*/ */
hasTouch?: boolean; isMobile?: boolean;
/**
* The page width in pixels.
*/
width: number;
} }
if (isRunningInAwsLambda()) { if (isRunningInAwsLambda()) {
@ -93,71 +94,7 @@ class Chromium {
* If false, webgl will be disabled. * If false, webgl will be disabled.
* (If false, the swiftshader.tar.br file will also not extract) * (If false, the swiftshader.tar.br file will also not extract)
*/ */
private static graphicsMode: boolean = true; private static graphicsMode = true;
/**
* Downloads or symlinks a custom font and returns its basename, patching the environment so that Chromium can find it.
*/
static font(input: string): Promise<string> {
if (process.env["HOME"] === undefined) {
process.env["HOME"] = "/tmp";
}
if (existsSync(`${process.env["HOME"]}/.fonts`) !== true) {
mkdirSync(`${process.env["HOME"]}/.fonts`);
}
return new Promise((resolve, reject) => {
if (/^https?:[/][/]/i.test(input) !== true) {
input = `file://${input}`;
}
const url = new URL(input);
const output = `${process.env["HOME"]}/.fonts/${url.pathname
.split("/")
.pop()}`;
if (existsSync(output) === true) {
return resolve(output.split("/").pop() as string);
}
if (url.protocol === "file:") {
access(url.pathname, (error) => {
if (error != null) {
return reject(error);
}
symlink(url.pathname, output, (error) => {
return error != null
? reject(error)
: resolve(url.pathname.split("/").pop() as string);
});
});
} else {
https.get(input, (response) => {
if (response.statusCode !== 200) {
return reject(`Unexpected status code: ${response.statusCode}.`);
}
const stream = createWriteStream(output);
stream.once("error", (error) => {
return reject(error);
});
response.on("data", (chunk) => {
stream.write(chunk);
});
response.once("end", () => {
stream.end(() => {
return resolve(url.pathname.split("/").pop() as string);
});
});
});
}
});
}
/** /**
* Returns a list of additional Chromium flags recommended for serverless environments. * Returns a list of additional Chromium flags recommended for serverless environments.
@ -271,19 +208,19 @@ class Chromium {
// Extract the required files // Extract the required files
const promises = [ const promises = [
LambdaFS.inflate(`${input}/chromium.br`), inflate(`${input}/chromium.br`),
LambdaFS.inflate(`${input}/fonts.tar.br`), inflate(`${input}/fonts.tar.br`),
]; ];
if (this.graphics) { if (this.graphics) {
// Only inflate graphics stack if needed // Only inflate graphics stack if needed
promises.push(LambdaFS.inflate(`${input}/swiftshader.tar.br`)); promises.push(inflate(`${input}/swiftshader.tar.br`));
} }
if (isRunningInAwsLambda()) { if (isRunningInAwsLambda()) {
// If running in AWS Lambda, extract more required files // If running in AWS Lambda, extract more required files
promises.push(LambdaFS.inflate(`${input}/al2.tar.br`)); promises.push(inflate(`${input}/al2.tar.br`));
} }
if (isRunningInAwsLambdaNode20()) { if (isRunningInAwsLambdaNode20()) {
promises.push(LambdaFS.inflate(`${input}/al2023.tar.br`)); promises.push(inflate(`${input}/al2023.tar.br`));
} }
// Await all extractions // Await all extractions
@ -292,6 +229,70 @@ class Chromium {
return result.shift() as string; return result.shift() as string;
} }
/**
* Downloads or symlinks a custom font and returns its basename, patching the environment so that Chromium can find it.
*/
static font(input: string): Promise<string> {
if (process.env["HOME"] === undefined) {
process.env["HOME"] = "/tmp";
}
if (existsSync(`${process.env["HOME"]}/.fonts`) !== true) {
mkdirSync(`${process.env["HOME"]}/.fonts`);
}
return new Promise((resolve, reject) => {
if (/^https?:\/\//i.test(input) !== true) {
input = `file://${input}`;
}
const url = new URL(input);
const output = `${process.env["HOME"]}/.fonts/${url.pathname
.split("/")
.pop()}`;
if (existsSync(output) === true) {
return resolve(output.split("/").pop() as string);
}
if (url.protocol === "file:") {
access(url.pathname, (error) => {
if (error != null) {
return reject(error);
}
symlink(url.pathname, output, (error) => {
return error == null
? resolve(url.pathname.split("/").pop() as string)
: reject(error);
});
});
} else {
https.get(input, (response) => {
if (response.statusCode !== 200) {
return reject(`Unexpected status code: ${response.statusCode}.`);
}
const stream = createWriteStream(output);
stream.once("error", (error) => {
return reject(error);
});
response.on("data", (chunk) => {
stream.write(chunk);
});
response.once("end", () => {
stream.end(() => {
return resolve(url.pathname.split("/").pop() as string);
});
});
});
}
});
}
/** /**
* Returns whether the graphics stack is enabled or disabled * Returns whether the graphics stack is enabled or disabled
* @returns boolean * @returns boolean
@ -309,7 +310,7 @@ class Chromium {
*/ */
public static set setGraphicsMode(value: boolean) { public static set setGraphicsMode(value: boolean) {
if (typeof value !== "boolean") { if (typeof value !== "boolean") {
throw new Error( throw new TypeError(
`Graphics mode must be a boolean, you entered '${value}'` `Graphics mode must be a boolean, you entered '${value}'`
); );
} }

View File

@ -1,22 +1,21 @@
import { createReadStream, createWriteStream, existsSync } from "node:fs"; import { createReadStream, createWriteStream, existsSync } from "node:fs";
import { tmpdir } from "node:os"; import { tmpdir } from "node:os";
import { basename, join } from "node:path"; import { basename, join } from "node:path";
import { extract } from "tar-fs";
import { createBrotliDecompress, createUnzip } from "node:zlib"; import { createBrotliDecompress, createUnzip } from "node:zlib";
import { extract } from "tar-fs";
class LambdaFS {
/** /**
* Decompresses a (tarballed) Brotli or Gzip compressed file and returns the path to the decompressed file/folder. * Decompresses a (tarballed) Brotli or Gzip compressed file and returns the path to the decompressed file/folder.
* *
* @param filePath Path of the file to decompress. * @param filePath Path of the file to decompress.
*/ */
static inflate(filePath: string): Promise<string> { export const inflate = (filePath: string): Promise<string> => {
const output = filePath.includes("swiftshader") const output = filePath.includes("swiftshader")
? tmpdir() ? tmpdir()
: join( : join(
tmpdir(), tmpdir(),
basename(filePath).replace( basename(filePath).replace(
/[.](?:t(?:ar(?:[.](?:br|gz))?|br|gz)|br|gz)$/i, /\.(?:t(?:ar(?:\.(?:br|gz))?|br|gz)|br|gz)$/i,
"" ""
) )
); );
@ -32,10 +31,10 @@ class LambdaFS {
} }
} }
let source = createReadStream(filePath, { highWaterMark: 2 ** 23 }); const source = createReadStream(filePath, { highWaterMark: 2 ** 23 });
let target = null; let target = null;
if (/[.](?:t(?:ar(?:[.](?:br|gz))?|br|gz))$/i.test(filePath) === true) { if (/\.t(?:ar(?:\.(?:br|gz))?|br|gz)$/i.test(filePath) === true) {
target = extract(output); target = extract(output);
target.once("finish", () => { target.once("finish", () => {
@ -69,7 +68,6 @@ class LambdaFS {
source.pipe(target); source.pipe(target);
} }
}); });
} };
}
export default LambdaFS; export default inflate;