A simple and easy to use file download manager for Electron applications.
Designed in response to the many issues around electron-dl
and provides
a more robust and reliable solution for downloading files in Electron.
Use cases:
Download files from a URL
Get an id associated with the download to track it
Optionally show a "Save As" dialog
Get progress updates on the download
Be able to cancel / pause / resume downloads
Support multiple downloads at once
Electron 26.0.0 or later is required.
// In main process// Not a working example, just a demonstration of the APIimport { ElectronDownloadManager } from 'electron-dl-manager';const manager = new ElectronDownloadManager();// Start a downloadconst id = await manager.download({ window: browserWindowInstance, url: 'https://example.com/file.zip', saveDialogOptions: {title: 'Save File', }, callbacks: {onDownloadStarted: async ({ id, item, webContents }) => { // Do something with the download id},onDownloadProgress: async (...) => {},onDownloadCompleted: async (...) => {},onDownloadCancelled: async (...) => {},onDownloadInterrupted: async (...) => {},onError: (err, data) => {}, }});manager.cancelDownload(id);manager.pauseDownload(id);manager.resumeDownload(id);
Electron File Download Manager
Installation
Getting started
API
Properties
isDownloadInProgress()
isDownloadPaused()
isDownloadResumable()
isDownloadCancelled()
isDownloadInterrupted()
isDownloadCompleted()
Formatting download progress
constructor()
download()
cancelDownload()
pauseDownload()
resumeDownload()
getActiveDownloadCount()
getDownloadData()
Interface: DownloadParams
Interface: DownloadManagerCallbacks
Class: ElectronDownloadManager
Class: DownloadData
Mock class
FAQ
Acknowledgments
$ npm install electron-dl-manager
You'll want to use electron-dl-manager
in the main process of your
Electron application where you will be handling the file downloads.
In this example, we use IPC handlers / invokers to communicate between the main and renderer processes, but you can use any IPC strategy you want.
// MainIpcHandlers.tsimport { ElectronDownloadManager } from 'electron-dl-manager';import { ipcMain } from 'electron';const manager = new ElectronDownloadManager();// Renderer would invoke this handler to start a downloadipcMain.handle('download-file', async (event, args) => { const { url } = args; let downloadId const browserWindow = BrowserWindow.fromId(event.sender.id) // You *must* call manager.download() with await or // you may get unexpected behavior downloadId = await manager.download({window: browserWindow,url,// If you want to download without a save as dialogsaveAsFilename: 'file.zip',directory: '/directory/where/to/save',// If you want to download with a save as dialogsaveDialogOptions: { title: 'Save File',},callbacks: { // item is an instance of Electron.DownloadItem onDownloadStarted: async ({ id, item, resolvedFilename }) => {// Send the download id back to the renderer along// with some other databrowserWindow.webContents.invoke('download-started', { id, // The filename that the file will be saved as filename: resolvedFilename, // Get the file size to be downloaded in bytes totalBytes: item.getTotalBytes(),}); }, onDownloadProgress: async ({ id, item, percentCompleted }) => {// Send the download progress back to the rendererbrowserWindow.webContents.invoke('download-progress', { id, percentCompleted, // Get the number of bytes received so far bytesReceived: item.getReceivedBytes(),}); }, onDownloadCompleted: async ({ id, item }) => {// Send the download completion back to the rendererbrowserWindow.webContents.invoke('download-completed', { id, // Get the path to the file that was downloaded filePath: item.getSavePath(),}); }, onError: (err, data) => {// ... handle any errors }} }); // Pause the download manager.pauseDownload(downloadId);});
ElectronDownloadManager
Manages file downloads in an Electron application.
constructor()
constructor(params: DownloadManagerConstructorParams)
interface DownloadManagerConstructorParams { /** * If defined, will log out internal debug messages. Useful for * troubleshooting downloads. Does not log out progress due to * how frequent it can be. */ debugLogger?: (message: string) => void}
download()
Starts a file download. Returns the id
of the download.
download(params: DownloadParams): Promise<string>
DownloadParams
interface DownloadParams { /** * The Electron.BrowserWindow instance */ window: BrowserWindow /** * The URL to download */ url: string /** * The callbacks to define to listen for download events */ callbacks: DownloadManagerCallbacks /** * Electron.DownloadURLOptions to pass to the downloadURL method * * @see https://www.electronjs.org/docs/latest/api/session#sesdownloadurlurl-options */ downloadURLOptions?: Electron.DownloadURLOptions /** * If defined, will show a save dialog when the user * downloads a file. * * @see https://www.electronjs.org/docs/latest/api/dialog#dialogshowsavedialogbrowserwindow-options */ saveDialogOptions?: SaveDialogOptions /** * The filename to save the file as. If not defined, the filename * from the server will be used. * * Only applies if saveDialogOptions is not defined. */ saveAsFilename?: string /** * The directory to save the file to. Must be an absolute path. * @default The user's downloads directory */ directory?: string /** * If true, will overwrite the file if it already exists * @default false */ overwrite?: boolean}
DownloadManagerCallbacks
interface DownloadManagerCallbacks { /** * When the download has started. When using a "save as" dialog, * this will be called after the user has selected a location. * * This will always be called first before the progress and completed events. */ onDownloadStarted: (data: DownloadData) => void /** * When there is a progress update on a download. Note: This * may be skipped entirely in some cases, where the download * completes immediately. In that case, onDownloadCompleted * will be called instead. */ onDownloadProgress: (data: DownloadData) => void /** * When the download has completed */ onDownloadCompleted: (data: DownloadData) => void /** * When the download has been cancelled. Also called if the user cancels * from the save as dialog. */ onDownloadCancelled: (data: DownloadData) => void /** * When the download has been interrupted. This could be due to a bad * connection, the server going down, etc. */ onDownloadInterrupted: (data: DownloadData) => void /** * When an error has been encountered. * Note: The signature is (error, <maybe some data>). */ onError: (error: Error, data?: DownloadData) => void}
cancelDownload()
Cancels a download.
cancelDownload(id: string): void
pauseDownload()
Pauses a download.
pauseDownload(id: string): void
resumeDownload()
Resumes a download.
resumeDownload(id: string): void
getActiveDownloadCount()
Returns the number of active downloads.
getActiveDownloadCount(): number
getDownloadData()
Returns the download data for a download.
getDownloadData(id: string): DownloadData
DownloadData
Data returned in the callbacks for a download.
class DownloadData { /** * Generated id for the download */ id: string /** * The Electron.DownloadItem. Use this to grab the filename, path, etc. * @see https://www.electronjs.org/docs/latest/api/download-item */ item: DownloadItem /** * The Electron.WebContents * @see https://www.electronjs.org/docs/latest/api/web-contents */ webContents: WebContents /** * The Electron.Event * @see https://www.electronjs.org/docs/latest/api/event */ event: Event /** * The name of the file that is being saved to the user's computer. * Recommended over Item.getFilename() as it may be inaccurate when using the save as dialog. */ resolvedFilename: string /** * If true, the download was cancelled from the save as dialog. This flag * will also be true if the download was cancelled by the application when * using the save as dialog. */ cancelledFromSaveAsDialog?: boolean /** * The percentage of the download that has been completed */ percentCompleted: number /** * The download rate in bytes per second. */ downloadRateBytesPerSecond: number /** * The estimated time remaining in seconds. */ estimatedTimeRemainingSeconds: number /** * If the download was interrupted, the state in which it was interrupted from */ interruptedVia?: 'in-progress' | 'completed'}
You can use the libraries bytes
and dayjs
to format the download progress.
$ npm install bytes dayjs $ npm install @types/bytes --save-dev
import bytes from 'bytes'import dayjs from 'dayjs'import relativeTime from 'dayjs/plugin/relativeTime';import duration from 'dayjs/plugin/duration';dayjs.extend(relativeTime);dayjs.extend(duration);const downloadData = manager.getDownloadData(id); // or DataItem from the callbacks// Will return something like 1.2 MB/sconst formattedDownloadRate = bytes(downloadData.downloadRateBytesPerSecond, { unitSeparator: ' ' }) + '/s'// Will return something like "in a few seconds"const formattedEstimatedTimeRemaining = dayjs.duration(downloadData.estimatedTimeRemainingSeconds, 'seconds').humanize(true)
isDownloadInProgress()
Returns true if the download is in progress.
isDownloadInProgress(): boolean
isDownloadPaused()
Returns true if the download is paused.
isDownloadPaused(): boolean
isDownloadResumable()
Returns true if the download is resumable.
isDownloadResumable(): boolean
isDownloadCancelled()
Returns true if the download is cancelled.
isDownloadCancelled(): boolean
isDownloadInterrupted()
Returns true if the download is interrupted.
isDownloadInterrupted(): boolean
isDownloadCompleted()
Returns true if the download is completed.
isDownloadCompleted(): boolean
If you need to mock out ElectronDownloadManager
in your tests, you can use the ElectronDownloadManagerMock
class.
import { ElectronDownloadManagerMock } from 'electron-dl-manager'
onError()
is not being called.Electron DownloadItem
doesn't provide an explicit way to capture errors for downloads in general:
https://www.electronjs.org/docs/latest/api/download-item#class-downloaditem
(It only has on('updated')
and on('done')
events, which this library uses for defining the callback handlers.)
What it does for invalid URLs, it will trigger the onDownloadCancelled()
callback.
const id = await manager.download({ window: mainWindow, url: 'https://alkjsdflksjdflk.com/file.zip', callbacks: {onDownloadCancelled: async (...) => { // Invalid download; this callback will be called}, }});
A better way to handle this is to check if the URL exists prior to the download yourself. I couldn't find a library that I felt was reliable to include into this package, so it's best you find a library that works for you:
https://www.npmjs.com/search?q=url%20exists&ranking=maintenance
GPT also suggests the following code (untested):
async function urlExists(url: string): Promise<boolean> { try {const response = await fetch(url, { method: 'HEAD' });return response.ok; } catch (error) {return false; }}const exists = await urlExists('https://example.com/file.jpg');
This code uses small portions from electron-dl
and is noted in the
code where it is used.
electron-dl
is licensed under the MIT License and is maintained by Sindre Sorhus [email protected] (https://sindresorhus.com).