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 API
import { ElectronDownloadManager } from 'electron-dl-manager';
const manager = new ElectronDownloadManager();
// Start a download
const 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
- Mock class
- 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.ts
import { ElectronDownloadManager } from 'electron-dl-manager';
import { ipcMain } from 'electron';
const manager = new ElectronDownloadManager();
// Renderer would invoke this handler to start a download
ipcMain.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 dialog
saveAsFilename: 'file.zip',
directory: '/directory/where/to/save',
// If you want to download with a save as dialog
saveDialogOptions: {
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 data
browserWindow.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 renderer
browserWindow.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 renderer
browserWindow.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);
});
Manages file downloads in an Electron application.
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
}
Starts a file download. Returns the id
of the download.
download(params: DownloadParams): Promise<string>
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
}
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
}
Cancels a download.
cancelDownload(id: string): void
Pauses a download.
pauseDownload(id: string): void
Resumes a download.
resumeDownload(id: string): void
Returns the number of active downloads.
getActiveDownloadCount(): number
Returns the download data for a download.
getDownloadData(id: string): 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
*/
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/s
const formattedDownloadRate = bytes(downloadData.downloadRateBytesPerSecond, { unitSeparator: ' ' }) + '/s'
// Will return something like "in a few seconds"
const formattedEstimatedTimeRemaining = dayjs.duration(downloadData.estimatedTimeRemainingSeconds, 'seconds').humanize(true)
Returns true if the download is in progress.
isDownloadInProgress(): boolean
Returns true if the download is paused.
isDownloadPaused(): boolean
Returns true if the download is resumable.
isDownloadResumable(): boolean
Returns true if the download is cancelled.
isDownloadCancelled(): boolean
Returns true if the download is interrupted.
isDownloadInterrupted(): boolean
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'
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 sindresorhus@gmail.com (https://sindresorhus.com).