Compare commits
12 Commits
master
...
feature/pu
| Author | SHA1 | Date |
|---|---|---|
|
|
39b6e95275 | |
|
|
9b8de8ac89 | |
|
|
db50d16c84 | |
|
|
7d6d9c4cf9 | |
|
|
8037abb918 | |
|
|
b63114df4b | |
|
|
f8d0686174 | |
|
|
e6f131ca70 | |
|
|
1151d3bc96 | |
|
|
e96aadfaf8 | |
|
|
636fa64bfa | |
|
|
9f3b102e5e |
54
README.md
54
README.md
|
|
@ -11,7 +11,7 @@
|
||||||
[sparticuz/chrome-aws-lambda](https://github.com/sparticuz/chrome-aws-lambda) was originally forked from [alixaxel/chrome-aws-lambda#264](https://github.com/alixaxel/chrome-aws-lambda/pull/264).
|
[sparticuz/chrome-aws-lambda](https://github.com/sparticuz/chrome-aws-lambda) was originally forked from [alixaxel/chrome-aws-lambda#264](https://github.com/alixaxel/chrome-aws-lambda/pull/264).
|
||||||
The biggest difference, besides the chromium version, is the inclusion of some code from https://github.com/alixaxel/lambdafs, as well as dropping that as a dependency. Due to some changes in WebGL, the files in bin/swiftshader.tar.br need to be extracted to `/tmp` instead of `/tmp/swiftshader`. This necessitated changes in lambdafs.
|
The biggest difference, besides the chromium version, is the inclusion of some code from https://github.com/alixaxel/lambdafs, as well as dropping that as a dependency. Due to some changes in WebGL, the files in bin/swiftshader.tar.br need to be extracted to `/tmp` instead of `/tmp/swiftshader`. This necessitated changes in lambdafs.
|
||||||
|
|
||||||
However, it quickly became difficult to maintain because of the pace of `puppeteer` updates. This package, `@sparticuz/chromium`, is not chained to `puppeteer` versions, but also does not include the overrides and hooks that the original package contained. It is only `chromium`, as well as the special code needed to decompress the brotli package, and a set of predefined arguments tailored to serverless usage.
|
However, it quickly became difficult to maintain because of the pace of `puppeteer` updates. This package, `@sparticuz/chromium`, is not chained to `puppeteer` versions, but also does not include the overrides and hooks that the original package contained. It is only `chromium`, as well as the special code needed to decompress the brotli package, and an additional set of predefined arguments tailored to serverless usage.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
|
|
@ -50,14 +50,6 @@ const test = require("node:test");
|
||||||
const puppeteer = require("puppeteer-core");
|
const puppeteer = require("puppeteer-core");
|
||||||
const chromium = require("@sparticuz/chromium");
|
const chromium = require("@sparticuz/chromium");
|
||||||
|
|
||||||
// Optional: If you'd like to use the new headless mode. "shell" is the default.
|
|
||||||
// NOTE: Because we build the shell binary, this option does not work.
|
|
||||||
// However, this option will stay so when we migrate to full chromium it will work.
|
|
||||||
chromium.setHeadlessMode = true;
|
|
||||||
|
|
||||||
// Optional: If you'd like to disable webgl, true is the default.
|
|
||||||
chromium.setGraphicsMode = false;
|
|
||||||
|
|
||||||
// Optional: Load any fonts you need. Open Sans is included by default in AWS Lambda instances
|
// Optional: Load any fonts you need. Open Sans is included by default in AWS Lambda instances
|
||||||
await chromium.font(
|
await chromium.font(
|
||||||
"https://raw.githack.com/googlei18n/noto-emoji/master/fonts/NotoColorEmoji.ttf"
|
"https://raw.githack.com/googlei18n/noto-emoji/master/fonts/NotoColorEmoji.ttf"
|
||||||
|
|
@ -65,10 +57,20 @@ await chromium.font(
|
||||||
|
|
||||||
test("Check the page title of example.com", async (t) => {
|
test("Check the page title of example.com", async (t) => {
|
||||||
const browser = await puppeteer.launch({
|
const browser = await puppeteer.launch({
|
||||||
|
args: puppeteer.defaultArgs({
|
||||||
args: chromium.args,
|
args: chromium.args,
|
||||||
defaultViewport: chromium.defaultViewport,
|
headless: "shell",
|
||||||
|
}),
|
||||||
|
defaultViewport: {
|
||||||
|
deviceScaleFactor: 1,
|
||||||
|
hasTouch: false,
|
||||||
|
height: 1080,
|
||||||
|
isLandscape: true,
|
||||||
|
isMobile: false,
|
||||||
|
width: 1920,
|
||||||
|
},
|
||||||
executablePath: await chromium.executablePath(),
|
executablePath: await chromium.executablePath(),
|
||||||
headless: chromium.headless,
|
headless: "shell",
|
||||||
});
|
});
|
||||||
|
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
|
|
@ -92,7 +94,7 @@ test("Check the page title of example.com", async (t) => {
|
||||||
const browser = await playwright.launch({
|
const browser = await playwright.launch({
|
||||||
args: chromium.args,
|
args: chromium.args,
|
||||||
executablePath: await chromium.executablePath(),
|
executablePath: await chromium.executablePath(),
|
||||||
headless: chromium.headless,
|
headless: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const context = await browser.newContext();
|
const context = await browser.newContext();
|
||||||
|
|
@ -127,10 +129,13 @@ In this example, /opt/chromium contains all the brotli files
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const browser = await puppeteer.launch({
|
const browser = await puppeteer.launch({
|
||||||
|
args: puppeteer.defaultArgs({
|
||||||
args: chromium.args,
|
args: chromium.args,
|
||||||
|
headless: "shell",
|
||||||
|
}),
|
||||||
defaultViewport: chromium.defaultViewport,
|
defaultViewport: chromium.defaultViewport,
|
||||||
executablePath: await chromium.executablePath("/opt/chromium"),
|
executablePath: await chromium.executablePath("/opt/chromium"),
|
||||||
headless: chromium.headless,
|
headless: "shell",
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -142,12 +147,15 @@ The latest chromium-pack.tar file will be on the latest [release](https://github
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const browser = await puppeteer.launch({
|
const browser = await puppeteer.launch({
|
||||||
|
args: puppeteer.defaultArgs({
|
||||||
args: chromium.args,
|
args: chromium.args,
|
||||||
|
headless: "shell",
|
||||||
|
}),
|
||||||
defaultViewport: chromium.defaultViewport,
|
defaultViewport: chromium.defaultViewport,
|
||||||
executablePath: await chromium.executablePath(
|
executablePath: await chromium.executablePath(
|
||||||
"https://www.example.com/chromiumPack.tar"
|
"https://www.example.com/chromiumPack.tar"
|
||||||
),
|
),
|
||||||
headless: chromium.headless,
|
headless: "shell",
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -177,12 +185,12 @@ For example, you can set your code to use an ENV variable such as `IS_LOCAL`, th
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const browser = await puppeteer.launch({
|
const browser = await puppeteer.launch({
|
||||||
args: process.env.IS_LOCAL ? puppeteer.defaultArgs() : chromium.args,
|
args: process.env.IS_LOCAL ? puppeteer.defaultArgs() : puppeteer.defaultArgs({args:chromium.args, process.env.IS_LOCAL ? false : "shell"}),
|
||||||
defaultViewport: chromium.defaultViewport,
|
defaultViewport: chromium.defaultViewport,
|
||||||
executablePath: process.env.IS_LOCAL
|
executablePath: process.env.IS_LOCAL
|
||||||
? "/tmp/localChromium/chromium/linux-1122391/chrome-linux/chrome"
|
? "/tmp/localChromium/chromium/linux-1122391/chrome-linux/chrome"
|
||||||
: await chromium.executablePath(),
|
: await chromium.executablePath(),
|
||||||
headless: process.env.IS_LOCAL ? false : chromium.headless,
|
headless: process.env.IS_LOCAL ? false : "shell",
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -294,18 +302,18 @@ zip -9 --filesync --move --recurse-paths fonts.zip fonts/
|
||||||
|
|
||||||
## Graphics
|
## Graphics
|
||||||
|
|
||||||
|
NOTE: Disabling the Graphics stack is currently broken, causing Chromium to crash repeadly. For now, even if you `setGraphicsMode=false`, this package will set it to `true`.
|
||||||
|
|
||||||
By default, this package uses `swiftshader`/`angle` to do CPU acceleration for WebGL. This is the only known way to enable WebGL on a serverless platform. You can disable WebGL by setting `chromium.setGraphiceMode = false;` _before_ launching Chromium. Disabling this will also skip the extract of the `bin/swiftshader.tar.br` file, which saves about a second of initial execution time. Disabling graphics is recommended if you know you are not using any WebGL.
|
By default, this package uses `swiftshader`/`angle` to do CPU acceleration for WebGL. This is the only known way to enable WebGL on a serverless platform. You can disable WebGL by setting `chromium.setGraphiceMode = false;` _before_ launching Chromium. Disabling this will also skip the extract of the `bin/swiftshader.tar.br` file, which saves about a second of initial execution time. Disabling graphics is recommended if you know you are not using any WebGL.
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
| Method / Property | Returns | Description |
|
| Method / Property | Returns | Description |
|
||||||
| ----------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ----------------------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `font(url)` | `Promise<string>` | Provisions a custom font and returns its basename. |
|
| `font(url)` | `Promise<string>` | Provisions a custom font and returns its basename. |
|
||||||
| `args` | `Array<string>` | Provides a list of recommended additional [Chromium flags](https://github.com/GoogleChrome/chrome-launcher/blob/master/docs/chrome-flags-for-tools.md). |
|
| `args` | `Array<string>` | Provides a list of recommended additional [Chromium flags](https://github.com/GoogleChrome/chrome-launcher/blob/master/docs/chrome-flags-for-tools.md) that are specific to running in Lambda. |
|
||||||
| `defaultViewport` | `Object` | Returns a sensible default viewport for serverless. |
|
| `defaultViewport` | `Object` | Returns a sensible default viewport for serverless. |
|
||||||
| `executablePath(location?: string)` | `Promise<string>` | Returns the path the Chromium binary was extracted to. |
|
| `executablePath(location?: string)` | `Promise<string>` | Returns the path the Chromium binary was extracted to. |
|
||||||
| `setHeadlessMode` | `void` | Sets the headless mode to either `true` or `"shell"` |
|
|
||||||
| `headless` | `true \| "shell"` | Returns `true` or `"shell"` depending on what version of chrome's headless you are running |
|
|
||||||
| `setGraphicsMode` | `void` | Sets the graphics mode to either `true` or `false` |
|
| `setGraphicsMode` | `void` | Sets the graphics mode to either `true` or `false` |
|
||||||
| `graphics` | `boolean` | Returns a boolean depending on whether webgl is enabled or disabled |
|
| `graphics` | `boolean` | Returns a boolean depending on whether webgl is enabled or disabled |
|
||||||
|
|
||||||
|
|
@ -357,11 +365,13 @@ exports.handler = async (event, context, callback) => {
|
||||||
try {
|
try {
|
||||||
- browser = await chromium.puppeteer.launch({
|
- browser = await chromium.puppeteer.launch({
|
||||||
+ browser = await puppeteer.launch({
|
+ browser = await puppeteer.launch({
|
||||||
args: chromium.args,
|
- args: chromium.args,
|
||||||
|
+ args: puppeteer.defaultArgs({ args: chromium.args, headless: "shell" }),
|
||||||
defaultViewport: chromium.defaultViewport,
|
defaultViewport: chromium.defaultViewport,
|
||||||
- executablePath: await chromium.executablePath,
|
- executablePath: await chromium.executablePath,
|
||||||
+ executablePath: await chromium.executablePath(),
|
+ executablePath: await chromium.executablePath(),
|
||||||
headless: chromium.headless,
|
- headless: chromium.headless,
|
||||||
|
+ headless: "shell",
|
||||||
ignoreHTTPSErrors: true,
|
ignoreHTTPSErrors: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,14 @@
|
||||||
"url": "https://example.com",
|
"url": "https://example.com",
|
||||||
"expected": {
|
"expected": {
|
||||||
"title": "Example Domain",
|
"title": "Example Domain",
|
||||||
"screenshot": "e610a8be5568f23c453b08928460aae3ae0b4b0a"
|
"screenshot": "6b6bfde6d0cd0035255ac81eb4c6a8823fc74f02"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "https://get.webgl.org",
|
"url": "https://get.webgl.org",
|
||||||
"expected": {
|
"expected": {
|
||||||
"remove": "logo-container",
|
"remove": "logo-container",
|
||||||
"screenshot": "ec6c79a571b4cb5727c6fc23f9da30de3868138c"
|
"screenshot": "1e4d1ce70b48ca14c1cee2a8e5f458f6ea1b987d"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,18 @@ 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 {
|
||||||
browser = await puppeteer.launch({
|
browser = await puppeteer.launch({
|
||||||
|
args: puppeteer.defaultArgs({
|
||||||
args: chromium.args,
|
args: chromium.args,
|
||||||
defaultViewport: chromium.defaultViewport,
|
headless: "shell",
|
||||||
|
}),
|
||||||
dumpio: true,
|
dumpio: true,
|
||||||
executablePath: await chromium.executablePath(),
|
executablePath: await chromium.executablePath(),
|
||||||
headless: chromium.headless,
|
headless: "shell",
|
||||||
ignoreHTTPSErrors: true,
|
ignoreHTTPSErrors: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
import chromium from '@sparticuz/chromium';
|
import chromium from "@sparticuz/chromium";
|
||||||
import puppeteer from 'puppeteer-core';
|
import puppeteer from "puppeteer-core";
|
||||||
|
|
||||||
export const lambdaHandler = async (event, context) => {
|
export const lambdaHandler = async (event, context) => {
|
||||||
const browser = await puppeteer.launch({
|
const browser = await puppeteer.launch({
|
||||||
|
args: puppeteer.defaultArgs({
|
||||||
args: chromium.args,
|
args: chromium.args,
|
||||||
defaultViewport: chromium.defaultViewport,
|
headless: "shell",
|
||||||
|
}),
|
||||||
executablePath: await chromium.executablePath(),
|
executablePath: await chromium.executablePath(),
|
||||||
headless: chromium.headless,
|
headless: "shell",
|
||||||
});
|
});
|
||||||
|
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
|
|
@ -20,5 +22,5 @@ export const lambdaHandler = async (event, context) => {
|
||||||
|
|
||||||
await browser.close();
|
await browser.close();
|
||||||
|
|
||||||
return { result: 'success', browserVersion, pageTitle };
|
return { result: "success", browserVersion, pageTitle };
|
||||||
}
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,12 @@ const chromium = require("@sparticuz/chromium");
|
||||||
const handler = async () => {
|
const handler = async () => {
|
||||||
try {
|
try {
|
||||||
const browser = await puppeteer.launch({
|
const browser = await puppeteer.launch({
|
||||||
|
args: puppeteer.defaultArgs({
|
||||||
args: chromium.args,
|
args: chromium.args,
|
||||||
defaultViewport: chromium.defaultViewport,
|
headless: "shell",
|
||||||
|
}),
|
||||||
executablePath: await chromium.executablePath(),
|
executablePath: await chromium.executablePath(),
|
||||||
headless: chromium.headless,
|
headless: "shell",
|
||||||
ignoreHTTPSErrors: true,
|
ignoreHTTPSErrors: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,10 @@ const chromium = require("@sparticuz/chromium-min");
|
||||||
const handler = async () => {
|
const handler = async () => {
|
||||||
try {
|
try {
|
||||||
const browser = await puppeteer.launch({
|
const browser = await puppeteer.launch({
|
||||||
|
args: puppeteer.defaultArgs({
|
||||||
args: chromium.args,
|
args: chromium.args,
|
||||||
defaultViewport: chromium.defaultViewport,
|
headless: "shell",
|
||||||
|
}),
|
||||||
executablePath: await chromium.executablePath(
|
executablePath: await chromium.executablePath(
|
||||||
"https://github.com/Sparticuz/chromium/releases/download/v110.0.1/chromium-v110.0.1-pack.tar"
|
"https://github.com/Sparticuz/chromium/releases/download/v110.0.1/chromium-v110.0.1-pack.tar"
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,12 @@ module.exports = {
|
||||||
handler: async () => {
|
handler: async () => {
|
||||||
try {
|
try {
|
||||||
const browser = await puppeteer.launch({
|
const browser = await puppeteer.launch({
|
||||||
|
args: puppeteer.defaultArgs({
|
||||||
args: chromium.args,
|
args: chromium.args,
|
||||||
defaultViewport: chromium.defaultViewport,
|
headless: "shell",
|
||||||
|
}),
|
||||||
executablePath: await chromium.executablePath(),
|
executablePath: await chromium.executablePath(),
|
||||||
headless: chromium.headless,
|
headless: "shell",
|
||||||
ignoreHTTPSErrors: true,
|
ignoreHTTPSErrors: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
"@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.14.10",
|
"@types/node": "^20.14.11",
|
||||||
"@types/tar-fs": "^2.0.4",
|
"@types/tar-fs": "^2.0.4",
|
||||||
"clean-modules": "^3.0.5",
|
"clean-modules": "^3.0.5",
|
||||||
"typescript": "^5.5.3"
|
"typescript": "^5.5.3"
|
||||||
|
|
@ -47,9 +47,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.14.10",
|
"version": "20.14.11",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.11.tgz",
|
||||||
"integrity": "sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==",
|
"integrity": "sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~5.26.4"
|
"undici-types": "~5.26.4"
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@
|
||||||
"@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.14.10",
|
"@types/node": "^20.14.11",
|
||||||
"@types/tar-fs": "^2.0.4",
|
"@types/tar-fs": "^2.0.4",
|
||||||
"clean-modules": "^3.0.5",
|
"clean-modules": "^3.0.5",
|
||||||
"typescript": "^5.5.3"
|
"typescript": "^5.5.3"
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,16 @@
|
||||||
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 { 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 URL {
|
||||||
maxBodyLength: number;
|
maxBodyLength: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -57,20 +55,20 @@ export const isRunningInAwsLambdaNode20 = () => {
|
||||||
|
|
||||||
export const downloadAndExtract = async (url: string) =>
|
export const downloadAndExtract = async (url: string) =>
|
||||||
new Promise<string>((resolve, reject) => {
|
new Promise<string>((resolve, reject) => {
|
||||||
const getOptions = parse(url) as FollowRedirOptions;
|
const getOptions = new URL(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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
322
source/index.ts
322
source/index.ts
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { https } from "follow-redirects";
|
||||||
import {
|
import {
|
||||||
access,
|
access,
|
||||||
createWriteStream,
|
createWriteStream,
|
||||||
|
|
@ -5,50 +6,18 @@ 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 */
|
// Set up the environmental variables
|
||||||
interface Viewport {
|
|
||||||
/**
|
|
||||||
* The page width in pixels.
|
|
||||||
*/
|
|
||||||
width: number;
|
|
||||||
/**
|
|
||||||
* The page height in pixels.
|
|
||||||
*/
|
|
||||||
height: number;
|
|
||||||
/**
|
|
||||||
* Specify device scale factor.
|
|
||||||
* See {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio | devicePixelRatio} for more info.
|
|
||||||
* @default 1
|
|
||||||
*/
|
|
||||||
deviceScaleFactor?: number;
|
|
||||||
/**
|
|
||||||
* Whether the `meta viewport` tag is taken into account.
|
|
||||||
* @default false
|
|
||||||
*/
|
|
||||||
isMobile?: boolean;
|
|
||||||
/**
|
|
||||||
* Specifies if the viewport is in landscape mode.
|
|
||||||
* @default false
|
|
||||||
*/
|
|
||||||
isLandscape?: boolean;
|
|
||||||
/**
|
|
||||||
* Specify if the viewport supports touch events.
|
|
||||||
* @default false
|
|
||||||
*/
|
|
||||||
hasTouch?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isRunningInAwsLambda()) {
|
if (isRunningInAwsLambda()) {
|
||||||
if (process.env["FONTCONFIG_PATH"] === undefined) {
|
if (process.env["FONTCONFIG_PATH"] === undefined) {
|
||||||
process.env["FONTCONFIG_PATH"] = "/tmp/fonts";
|
process.env["FONTCONFIG_PATH"] = "/tmp/fonts";
|
||||||
|
|
@ -88,208 +57,73 @@ if (isRunningInAwsLambdaNode20()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
class Chromium {
|
class Chromium {
|
||||||
/**
|
|
||||||
* Determines the headless mode that chromium will run at
|
|
||||||
* https://developer.chrome.com/articles/new-headless/#try-out-the-new-headless
|
|
||||||
* @values true or "new"
|
|
||||||
*/
|
|
||||||
private static headlessMode: true | "shell" = "shell";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If true, the graphics stack and webgl is enabled,
|
* If true, the graphics stack and webgl is enabled,
|
||||||
* 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.
|
||||||
* The canonical list of flags can be found on https://peter.sh/experiments/chromium-command-line-switches/.
|
* The canonical list of flags can be found on https://peter.sh/experiments/chromium-command-line-switches/.
|
||||||
|
* Most of below can be found here: https://github.com/GoogleChrome/chrome-launcher/blob/main/docs/chrome-flags-for-tools.md
|
||||||
*/
|
*/
|
||||||
static get args(): string[] {
|
static get args(): string[] {
|
||||||
/**
|
|
||||||
* These are the default args in puppeteer.
|
|
||||||
* https://github.com/puppeteer/puppeteer/blob/3a31070d054fa3cd8116ca31c578807ed8d6f987/packages/puppeteer-core/src/node/ChromeLauncher.ts#L185
|
|
||||||
*/
|
|
||||||
const puppeteerFlags = [
|
|
||||||
"--allow-pre-commit-input",
|
|
||||||
"--disable-background-networking",
|
|
||||||
"--disable-background-timer-throttling",
|
|
||||||
"--disable-backgrounding-occluded-windows",
|
|
||||||
"--disable-breakpad",
|
|
||||||
"--disable-client-side-phishing-detection",
|
|
||||||
"--disable-component-extensions-with-background-pages",
|
|
||||||
"--disable-component-update",
|
|
||||||
"--disable-default-apps",
|
|
||||||
"--disable-dev-shm-usage",
|
|
||||||
"--disable-extensions",
|
|
||||||
"--disable-hang-monitor",
|
|
||||||
"--disable-ipc-flooding-protection",
|
|
||||||
"--disable-popup-blocking",
|
|
||||||
"--disable-prompt-on-repost",
|
|
||||||
"--disable-renderer-backgrounding",
|
|
||||||
"--disable-sync",
|
|
||||||
"--enable-automation",
|
|
||||||
// TODO(sadym): remove '--enable-blink-features=IdleDetection' once
|
|
||||||
// IdleDetection is turned on by default.
|
|
||||||
"--enable-blink-features=IdleDetection",
|
|
||||||
"--export-tagged-pdf",
|
|
||||||
"--force-color-profile=srgb",
|
|
||||||
"--metrics-recording-only",
|
|
||||||
"--no-first-run",
|
|
||||||
"--password-store=basic",
|
|
||||||
"--use-mock-keychain",
|
|
||||||
];
|
|
||||||
const puppeteerDisableFeatures = [
|
|
||||||
"Translate",
|
|
||||||
"BackForwardCache",
|
|
||||||
// AcceptCHFrame disabled because of crbug.com/1348106.
|
|
||||||
"AcceptCHFrame",
|
|
||||||
"MediaRouter",
|
|
||||||
"OptimizationHints",
|
|
||||||
];
|
|
||||||
const puppeteerEnableFeatures = ["NetworkServiceInProcess2"];
|
|
||||||
|
|
||||||
const chromiumFlags = [
|
const chromiumFlags = [
|
||||||
"--disable-domain-reliability", // https://github.com/GoogleChrome/chrome-launcher/blob/main/docs/chrome-flags-for-tools.md#background-networking
|
"--ash-no-nudges", // Avoids blue bubble "user education" nudges (eg., "… give your browser a new look", Memory Saver)
|
||||||
|
"--disable-domain-reliability", // Disables Domain Reliability Monitoring, which tracks whether the browser has difficulty contacting Google-owned sites and uploads reports to Google.
|
||||||
"--disable-print-preview", // https://source.chromium.org/search?q=lang:cpp+symbol:kDisablePrintPreview&ss=chromium
|
"--disable-print-preview", // https://source.chromium.org/search?q=lang:cpp+symbol:kDisablePrintPreview&ss=chromium
|
||||||
"--disable-speech-api", // https://source.chromium.org/search?q=lang:cpp+symbol:kDisableSpeechAPI&ss=chromium
|
// "--disable-speech-api", // https://source.chromium.org/search?q=lang:cpp+symbol:kDisableSpeechAPI&ss=chromium
|
||||||
"--disk-cache-size=33554432", // https://source.chromium.org/search?q=lang:cpp+symbol:kDiskCacheSize&ss=chromium
|
"--disk-cache-size=33554432", // https://source.chromium.org/search?q=lang:cpp+symbol:kDiskCacheSize&ss=chromium
|
||||||
"--mute-audio", // https://source.chromium.org/search?q=lang:cpp+symbol:kMuteAudio&ss=chromium
|
"--no-default-browser-check", // Disable the default browser check, do not prompt to set it as such
|
||||||
"--no-default-browser-check", // https://source.chromium.org/search?q=lang:cpp+symbol:kNoDefaultBrowserCheck&ss=chromium
|
"--no-pings", // Don't send hyperlink auditing pings
|
||||||
"--no-pings", // https://source.chromium.org/search?q=lang:cpp+symbol:kNoPings&ss=chromium
|
"--single-process", // Runs the renderer and plugins in the same process as the browser. NOTES: Needs to be single-process to avoid `prctl(PR_SET_NO_NEW_PRIVS) failed` error
|
||||||
"--single-process", // Needs to be single-process to avoid `prctl(PR_SET_NO_NEW_PRIVS) failed` error
|
|
||||||
"--font-render-hinting=none", // https://github.com/puppeteer/puppeteer/issues/2410#issuecomment-560573612
|
"--font-render-hinting=none", // https://github.com/puppeteer/puppeteer/issues/2410#issuecomment-560573612
|
||||||
];
|
];
|
||||||
const chromiumDisableFeatures = [
|
const chromiumDisableFeatures = [
|
||||||
|
// "AutofillServerCommunication", // Disables autofill server communication. This feature isn't disabled via other 'parent' flags.
|
||||||
"AudioServiceOutOfProcess",
|
"AudioServiceOutOfProcess",
|
||||||
|
// "DestroyProfileOnBrowserClose", // Disable the feature of: Destroy profiles when their last browser window is closed, instead of when the browser exits.
|
||||||
|
// "InterestFeedContentSuggestions", // Disables the Discover feed on NTP
|
||||||
"IsolateOrigins",
|
"IsolateOrigins",
|
||||||
"site-per-process",
|
"site-per-process", // Disables OOPIF. https://www.chromium.org/Home/chromium-security/site-isolation
|
||||||
];
|
];
|
||||||
const chromiumEnableFeatures = ["SharedArrayBuffer"];
|
const chromiumEnableFeatures = ["SharedArrayBuffer"];
|
||||||
|
|
||||||
const graphicsFlags = [
|
const graphicsFlags = [
|
||||||
"--hide-scrollbars", // https://source.chromium.org/search?q=lang:cpp+symbol:kHideScrollbars&ss=chromium
|
|
||||||
"--ignore-gpu-blocklist", // https://source.chromium.org/search?q=lang:cpp+symbol:kIgnoreGpuBlocklist&ss=chromium
|
"--ignore-gpu-blocklist", // https://source.chromium.org/search?q=lang:cpp+symbol:kIgnoreGpuBlocklist&ss=chromium
|
||||||
"--in-process-gpu", // https://source.chromium.org/search?q=lang:cpp+symbol:kInProcessGPU&ss=chromium
|
"--in-process-gpu", // Saves some memory by moving GPU process into a browser process thread
|
||||||
"--window-size=1920,1080", // https://source.chromium.org/search?q=lang:cpp+symbol:kWindowSize&ss=chromium
|
"--window-size=1920,1080", // Sets the initial window size. Provided as string in the format "800,600".
|
||||||
];
|
];
|
||||||
|
|
||||||
// https://chromium.googlesource.com/chromium/src/+/main/docs/gpu/swiftshader.md
|
// https://chromium.googlesource.com/chromium/src/+/main/docs/gpu/swiftshader.md
|
||||||
// Blocked by https://github.com/Sparticuz/chromium/issues/247
|
// https://developer.chrome.com/blog/supercharge-web-ai-testing
|
||||||
//this.graphics
|
// https://www.browserless.io/blog/2023/08/31/browserless-gpu-instances/
|
||||||
// ? graphicsFlags.push("--use-gl=angle", "--use-angle=swiftshader")
|
this.graphics &&
|
||||||
// : graphicsFlags.push("--disable-webgl");
|
graphicsFlags.push(
|
||||||
graphicsFlags.push("--use-gl=angle", "--use-angle=swiftshader");
|
"--enable-gpu",
|
||||||
|
"--use-gl=angle",
|
||||||
|
"--use-angle=swiftshader"
|
||||||
|
);
|
||||||
|
|
||||||
const insecureFlags = [
|
const insecureFlags = [
|
||||||
"--allow-running-insecure-content", // https://source.chromium.org/search?q=lang:cpp+symbol:kAllowRunningInsecureContent&ss=chromium
|
"--allow-running-insecure-content", // https://source.chromium.org/search?q=lang:cpp+symbol:kAllowRunningInsecureContent&ss=chromium
|
||||||
"--disable-setuid-sandbox", // https://source.chromium.org/search?q=lang:cpp+symbol:kDisableSetuidSandbox&ss=chromium
|
"--disable-setuid-sandbox", // Lambda runs as root, so this is required to allow Chromium to run as root
|
||||||
"--disable-site-isolation-trials", // https://source.chromium.org/search?q=lang:cpp+symbol:kDisableSiteIsolation&ss=chromium
|
"--disable-site-isolation-trials", // https://source.chromium.org/search?q=lang:cpp+symbol:kDisableSiteIsolation&ss=chromium
|
||||||
"--disable-web-security", // https://source.chromium.org/search?q=lang:cpp+symbol:kDisableWebSecurity&ss=chromium
|
"--disable-web-security", // https://source.chromium.org/search?q=lang:cpp+symbol:kDisableWebSecurity&ss=chromium
|
||||||
"--no-sandbox", // https://source.chromium.org/search?q=lang:cpp+symbol:kNoSandbox&ss=chromium
|
"--no-sandbox", // Lambda runs as root, so this is required to allow Chromium to run as root
|
||||||
"--no-zygote", // https://source.chromium.org/search?q=lang:cpp+symbol:kNoZygote&ss=chromium
|
"--no-zygote", // https://codereview.chromium.org/2384163002
|
||||||
];
|
|
||||||
|
|
||||||
const headlessFlags = [
|
|
||||||
this.headless === "shell" ? "--headless='shell'" : "--headless",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
return [
|
return [
|
||||||
...puppeteerFlags,
|
|
||||||
...chromiumFlags,
|
...chromiumFlags,
|
||||||
`--disable-features=${[
|
`--disable-features=${[...chromiumDisableFeatures].join(",")}`,
|
||||||
...puppeteerDisableFeatures,
|
`--enable-features=${[...chromiumEnableFeatures].join(",")}`,
|
||||||
...chromiumDisableFeatures,
|
|
||||||
].join(",")}`,
|
|
||||||
`--enable-features=${[
|
|
||||||
...puppeteerEnableFeatures,
|
|
||||||
...chromiumEnableFeatures,
|
|
||||||
].join(",")}`,
|
|
||||||
...graphicsFlags,
|
...graphicsFlags,
|
||||||
...insecureFlags,
|
...insecureFlags,
|
||||||
...headlessFlags,
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns sensible default viewport settings for serverless environments.
|
|
||||||
*/
|
|
||||||
static get defaultViewport(): Required<Viewport> {
|
|
||||||
return {
|
|
||||||
deviceScaleFactor: 1,
|
|
||||||
hasTouch: false,
|
|
||||||
height: 1080,
|
|
||||||
isLandscape: true,
|
|
||||||
isMobile: false,
|
|
||||||
width: 1920,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inflates the included version of Chromium
|
* Inflates the included version of Chromium
|
||||||
* @param input The location of the `bin` folder
|
* @param input The location of the `bin` folder
|
||||||
|
|
@ -328,19 +162,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
|
||||||
|
|
@ -350,33 +184,67 @@ class Chromium {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the headless mode.
|
* Downloads or symlinks a custom font and returns its basename, patching the environment so that Chromium can find it.
|
||||||
* "shell" means the 'old' (legacy, chromium < 112) headless mode.
|
|
||||||
* `true` means the 'new' headless mode.
|
|
||||||
* https://developer.chrome.com/articles/new-headless/#try-out-the-new-headless
|
|
||||||
* @returns true | "shell"
|
|
||||||
*/
|
*/
|
||||||
public static get headless() {
|
static font(input: string): Promise<string> {
|
||||||
return this.headlessMode;
|
if (process.env["HOME"] === undefined) {
|
||||||
|
process.env["HOME"] = "/tmp";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
if (existsSync(`${process.env["HOME"]}/.fonts`) !== true) {
|
||||||
* Sets the headless mode.
|
mkdirSync(`${process.env["HOME"]}/.fonts`);
|
||||||
* "shell" means the 'old' (legacy, chromium < 112) headless mode.
|
|
||||||
* `true` means the 'new' headless mode.
|
|
||||||
* https://developer.chrome.com/articles/new-headless/#try-out-the-new-headless
|
|
||||||
* @default "shell"
|
|
||||||
*/
|
|
||||||
public static set setHeadlessMode(value: true | "shell") {
|
|
||||||
if (
|
|
||||||
(typeof value === "string" && value !== "shell") ||
|
|
||||||
(typeof value === "boolean" && value !== true)
|
|
||||||
) {
|
|
||||||
throw new Error(
|
|
||||||
`Headless mode must be either \`true\` or 'shell', you entered '${value}'`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
this.headlessMode = value;
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -396,7 +264,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}'`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue