From a9b40bd6a8a3eb1fa80058dbae9500a987e3b5ea Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Mon, 16 Dec 2024 14:52:41 +0100 Subject: [PATCH] add second branch --- .../server/registry/oopDownloadBrowserMain.ts | 73 ++++++++++++++++++- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts b/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts index 247581277e..2454f63c72 100644 --- a/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts +++ b/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts @@ -19,6 +19,10 @@ import path from 'path'; import { httpRequest } from '../../utils/network'; import { ManualPromise } from '../../utils/manualPromise'; import { extract } from '../../zipBundle'; +import type http from 'http'; +import { pipeline } from 'stream/promises'; +import { createBrotliDecompress } from 'zlib'; +import { TarExtractor } from 'playwright-core/lib/utils/tar'; export type DownloadParams = { title: string; @@ -104,11 +108,72 @@ function downloadFile(options: DownloadParams): Promise { } } +async function throwUnexpectedResponseError(response: http.IncomingMessage) { + let body = ''; + try { + await new Promise((resolve, reject) => { + response + .on('data', chunk => body += chunk) + .on('end', resolve) + .on('error', reject); + }); + } catch (error) { + body += error; + } + + response.resume(); // consume response data to free up memory + + throw new Error(`server returned code ${response.statusCode} body '${body}'`); +} + +async function downloadAndExtractBrotli(options: DownloadParams) { + const response = await new Promise((resolve, reject) => httpRequest({ + url: options.url, + headers: { + 'User-Agent': options.userAgent, + }, + timeout: options.connectionTimeout, + }, resolve, reject)); + + log(`-- response status code: ${response.statusCode}`); + if (response.statusCode !== 200) + await throwUnexpectedResponseError(response); + + const totalBytes = parseInt(response.headers['content-length'] || '0', 10); + log(`-- total bytes: ${totalBytes}`); + + let downloadedBytes = 0; + response.on('data', chunk => { + downloadedBytes += chunk.length; + progress(downloadedBytes, totalBytes); + }); + + await pipeline( + response, + createBrotliDecompress(), + new TarExtractor(file => path.join(options.browserDirectory, file)), + ); + + if (downloadedBytes !== totalBytes) + throw new Error(`size mismatch, file size: ${downloadedBytes}, expected size: ${totalBytes}`); + + log(`-- download complete, size: ${downloadedBytes}`); +} + async function main(options: DownloadParams) { - await downloadFile(options); - log(`SUCCESS downloading ${options.title}`); - log(`extracting archive`); - await extract(options.zipPath, { dir: options.browserDirectory }); + if (options.url.endsWith('.tar.br')) { + try { + await downloadAndExtractBrotli(options); + } catch (error) { + throw new Error(`Download failed. URL: ${options.url}`, { cause: error }); + } + log(`SUCCESS downloading and extracting ${options.title}`); + } else { + await downloadFile(options); + log(`SUCCESS downloading ${options.title}`); + log(`extracting archive`); + await extract(options.zipPath, { dir: options.browserDirectory }); + } if (options.executablePath) { log(`fixing permissions at ${options.executablePath}`); await fs.promises.chmod(options.executablePath, 0o755);