Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,17 +123,17 @@ locations are:
* MacOS: `~/Library/Caches/electron/`
* Windows: `%LOCALAPPDATA%/electron/Cache` or `~/AppData/Local/electron/Cache/`

By default, the module uses [`got`](https://github.com/sindresorhus/got) as the
downloader. As a result, you can use the same [options](https://github.com/sindresorhus/got#options)
via `downloadOptions`.
By default, the module uses the built-in [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)
as the downloader. As a result, you can pass [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit)
options via `downloadOptions`.

### Progress Bar

By default, a progress bar is shown when downloading an artifact for more than 30 seconds. To
disable, set the `ELECTRON_GET_NO_PROGRESS` environment variable to any non-empty value, or set
`quiet` to `true` in `downloadOptions`. If you need to monitor progress yourself via the API, set
`getProgressCallback` in `downloadOptions`, which has the same function signature as `got`'s
[`downloadProgress` event callback](https://github.com/sindresorhus/got#ondownloadprogress-progress).
`getProgressCallback` in `downloadOptions`, which receives a `Progress` object with `transferred`,
`total`, and `percent` properties.

### Proxies

Expand Down
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
"dependencies": {
"debug": "^4.1.1",
"env-paths": "^3.0.0",
"got": "^14.4.5",
"graceful-fs": "^4.2.11",
"progress": "^2.0.3",
"semver": "^7.6.3",
Expand Down Expand Up @@ -79,7 +78,7 @@
"release"
],
"optionalDependencies": {
"global-agent": "^3.0.0"
"undici": "^7.24.4"
},
"packageManager": "yarn@4.10.3+sha512.c38cafb5c7bb273f3926d04e55e1d8c9dfa7d9c3ea1f36a4868fa028b9e5f72298f0b7f401ad5eb921749eb012eb1c3bb74bf7503df3ee43fd600d14a018266f"
}
2 changes: 1 addition & 1 deletion src/Downloader.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* Generic interface for the artifact downloader library.
* The default implementation is {@link GotDownloader},
* The default implementation is {@link FetchDownloader},
* but any custom downloader can be passed to `@electron/get` via
* the {@link ElectronDownloadRequestOptions.downloader} option.
*
Expand Down
129 changes: 129 additions & 0 deletions src/FetchDownloader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import fs from 'graceful-fs';

import path from 'node:path';
import ProgressBar from 'progress';

import { Downloader } from './Downloader.js';
import { pipeline } from 'node:stream/promises';
import { Readable } from 'node:stream';

const PROGRESS_BAR_DELAY_IN_SECONDS = 30;

/**
* @category Downloader
*/
export interface Progress {
/** Bytes downloaded so far. */
transferred: number;
/** Total bytes to download, or `null` if the response had no `Content-Length` header. */
total: number | null;
/**
* Ratio of `transferred` to `total` between 0 and 1.
* If `total` is unknown, this is 0 until the download completes, then 1.
*/
percent: number;
}

/**
* @category Downloader
*/
export class HTTPError extends Error {
constructor(public readonly response: Response) {
super(`Response code ${response.status} (${response.statusText}) for ${response.url}`);
this.name = 'HTTPError';
}
}

/**
* @category Downloader
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/RequestInit | `RequestInit`} for possible keys/values.
*/
export type FetchDownloaderOptions = RequestInit & {
/** Called on each chunk with the current download {@link Progress}. */
getProgressCallback?: (progress: Progress) => Promise<void>;
/**
* Disables the console progress bar. Setting the `ELECTRON_GET_NO_PROGRESS`
* environment variable to a non-empty value also does this.
*/
quiet?: boolean;
};

/**
* Default {@link Downloader} implemented with the built-in
* {@link https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API | Fetch API}.
* @category Downloader
*/
export class FetchDownloader implements Downloader<FetchDownloaderOptions> {
async download(
url: string,
targetFilePath: string,
options: FetchDownloaderOptions = {},
): Promise<void> {
const { quiet, getProgressCallback, ...fetchOptions } = options;
let downloadCompleted = false;
let bar: ProgressBar | undefined;
let progressPercent: number;
let timeout: NodeJS.Timeout | undefined = undefined;
await fs.promises.mkdir(path.dirname(targetFilePath), { recursive: true });

if (!quiet && !process.env.ELECTRON_GET_NO_PROGRESS) {
const start = new Date();
timeout = setTimeout(() => {
if (!downloadCompleted) {
bar = new ProgressBar(
`Downloading ${path.basename(url)}: [:bar] :percent ETA: :eta seconds `,
{
curr: progressPercent,
total: 100,
},
);
// https://github.com/visionmedia/node-progress/issues/159
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(bar as any).start = start;
}
}, PROGRESS_BAR_DELAY_IN_SECONDS * 1000);
}
try {
const response = await fetch(url, fetchOptions);
if (!response.ok) {
throw new HTTPError(response);
}
if (!response.body) {
throw new Error('Response body is empty');
}

const contentLength = response.headers.get('content-length');
const total = contentLength ? parseInt(contentLength, 10) : null;
let transferred = 0;

const onProgress = (percent: number): void => {
progressPercent = percent;
if (bar) {
bar.update(percent);
}
if (getProgressCallback) {
void getProgressCallback({ transferred, total, percent });
}
};

await pipeline(
Readable.fromWeb(response.body),
async function* (source) {
for await (const chunk of source) {
transferred += chunk.length;
onProgress(total ? transferred / total : 0);
yield chunk;
}
},
fs.createWriteStream(targetFilePath),
);

onProgress(1);
} finally {
downloadCompleted = true;
if (timeout) {
clearTimeout(timeout);
}
}
}
}
93 changes: 0 additions & 93 deletions src/GotDownloader.ts

This file was deleted.

9 changes: 2 additions & 7 deletions src/downloader-resolver.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import { DownloadOptions } from './types.js';
import { Downloader } from './Downloader.js';

// TODO: Resolve the downloader or default to GotDownloader
// Current thoughts are a dot-file traversal for something like
// ".electron.downloader" which would be a text file with the name of the
// npm module to import() and use as the downloader
import { GotDownloader } from './GotDownloader.js';
import { FetchDownloader } from './FetchDownloader.js';

export async function getDownloaderForSystem(): Promise<Downloader<DownloadOptions>> {
return new GotDownloader();
return new FetchDownloader();
}
26 changes: 6 additions & 20 deletions src/proxy.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { createRequire } from 'node:module';

import debug from 'debug';
import { getEnv, setEnv } from './utils.js';

const d = debug('@electron/get:proxy');
const require = createRequire(import.meta.url);
Expand All @@ -13,31 +12,18 @@ const require = createRequire(import.meta.url);
* If the `ELECTRON_GET_USE_PROXY` environment variable is set to `true`, this function will be
* called automatically for `@electron/get` requests.
*
* Supported environment variables are `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY`.
*
* @category Utility
* @see {@link https://github.com/gajus/global-agent?tab=readme-ov-file#environment-variables | `global-agent`}
* @see {@link https://undici.nodejs.org/#/docs/api/EnvHttpProxyAgent | `EnvHttpProxyAgent`}
* documentation for available environment variables.
*
* @example
* ```sh
* export GLOBAL_AGENT_HTTPS_PROXY="$HTTPS_PROXY"
* ```
*/
export function initializeProxy(): void {
try {
// See: https://github.com/electron/get/pull/214#discussion_r798845713
const env = getEnv('GLOBAL_AGENT_');

setEnv('GLOBAL_AGENT_HTTP_PROXY', env('HTTP_PROXY'));
setEnv('GLOBAL_AGENT_HTTPS_PROXY', env('HTTPS_PROXY'));
setEnv('GLOBAL_AGENT_NO_PROXY', env('NO_PROXY'));

/**
* TODO: replace global-agent with a hpagent. @BlackHole1
* https://github.com/sindresorhus/got/blob/HEAD/documentation/tips.md#proxying
*/
// eslint-disable-next-line @typescript-eslint/no-require-imports
require('global-agent').bootstrap();
const { EnvHttpProxyAgent, setGlobalDispatcher } = require('undici');
setGlobalDispatcher(new EnvHttpProxyAgent());
} catch (e) {
d('Could not load either proxy modules, built-in proxy support not available:', e);
d('Could not load undici, built-in proxy support not available:', e);
}
}
8 changes: 4 additions & 4 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Downloader } from './Downloader.js';
import { GotDownloader, GotDownloaderOptions } from './GotDownloader.js';
import { FetchDownloader, FetchDownloaderOptions, HTTPError, Progress } from './FetchDownloader.js';

export { Downloader, GotDownloader, GotDownloaderOptions };
export { Downloader, FetchDownloader, FetchDownloaderOptions, HTTPError, Progress };

/**
* Custom downloaders can implement any set of options.
Expand Down Expand Up @@ -140,7 +140,7 @@ export interface ElectronDownloadRequestOptions {
/**
* Options passed to the downloader module.
*
* @see {@link GotDownloaderOptions} for options for the default {@link GotDownloader}.
* @see {@link FetchDownloaderOptions} for options for the default {@link FetchDownloader}.
*/
downloadOptions?: DownloadOptions;
/**
Expand All @@ -149,7 +149,7 @@ export interface ElectronDownloadRequestOptions {
mirrorOptions?: MirrorOptions;
/**
* A custom {@link Downloader} class used to download artifacts. Defaults to the
* built-in {@link GotDownloader}.
* built-in {@link FetchDownloader}.
*/
downloader?: Downloader<DownloadOptions>;
/**
Expand Down
Loading