electron-dl
Advanced tools
Comparing version 3.5.2 to 4.0.0
283
index.d.ts
@@ -1,132 +0,135 @@ | ||
import {BrowserView, BrowserWindow, DownloadItem, SaveDialogOptions} from 'electron'; | ||
import { | ||
type BrowserView, | ||
type BrowserWindow, | ||
type DownloadItem, | ||
type SaveDialogOptions, | ||
} from 'electron'; | ||
declare namespace electronDl { | ||
interface Progress { | ||
percent: number; | ||
transferredBytes: number; | ||
totalBytes: number; | ||
} | ||
export type Progress = { | ||
percent: number; | ||
transferredBytes: number; | ||
totalBytes: number; | ||
}; | ||
interface File { | ||
filename: string; | ||
path: string; | ||
fileSize: number; | ||
mimeType: string; | ||
url: string; | ||
} | ||
export type File = { | ||
filename: string; | ||
path: string; | ||
fileSize: number; | ||
mimeType: string; | ||
url: string; | ||
}; | ||
interface Options { | ||
/** | ||
Show a `Save As…` dialog instead of downloading immediately. | ||
export type Options = { | ||
/** | ||
Show a `Save As…` dialog instead of downloading immediately. | ||
Note: Only use this option when strictly necessary. Downloading directly without a prompt is a much better user experience. | ||
Note: Only use this option when strictly necessary. Downloading directly without a prompt is a much better user experience. | ||
@default false | ||
*/ | ||
readonly saveAs?: boolean; | ||
@default false | ||
*/ | ||
readonly saveAs?: boolean; | ||
/** | ||
The directory to save the file in. | ||
/** | ||
The directory to save the file in. | ||
Must be an absolute path. | ||
Must be an absolute path. | ||
Default: [User's downloads directory](https://electronjs.org/docs/api/app/#appgetpathname) | ||
*/ | ||
readonly directory?: string; | ||
Default: [User's downloads directory](https://electronjs.org/docs/api/app/#appgetpathname) | ||
*/ | ||
readonly directory?: string; | ||
/** | ||
Name of the saved file. | ||
This option only makes sense for `electronDl.download()`. | ||
/** | ||
Name of the saved file. | ||
This option only makes sense for `electronDl.download()`. | ||
Default: [`downloadItem.getFilename()`](https://electronjs.org/docs/api/download-item/#downloaditemgetfilename) | ||
*/ | ||
readonly filename?: string; | ||
Default: [`downloadItem.getFilename()`](https://electronjs.org/docs/api/download-item/#downloaditemgetfilename) | ||
*/ | ||
readonly filename?: string; | ||
/** | ||
Title of the error dialog. Can be customized for localization. | ||
/** | ||
Title of the error dialog. Can be customized for localization. | ||
Note: Error dialog will not be shown in `electronDl.download()`. Please handle error manually. | ||
Note: Error dialog will not be shown in `electronDl.download()`. Please handle error manually. | ||
@default 'Download Error' | ||
*/ | ||
readonly errorTitle?: string; | ||
@default 'Download Error' | ||
*/ | ||
readonly errorTitle?: string; | ||
/** | ||
Message of the error dialog. `{filename}` is replaced with the name of the actual file. Can be customized for localization. | ||
/** | ||
Message of the error dialog. `{filename}` is replaced with the name of the actual file. Can be customized for localization. | ||
Note: Error dialog will not be shown in `electronDl.download()`. Please handle error manually. | ||
Note: Error dialog will not be shown in `electronDl.download()`. Please handle error manually. | ||
@default 'The download of {filename} was interrupted' | ||
*/ | ||
readonly errorMessage?: string; | ||
@default 'The download of {filename} was interrupted' | ||
*/ | ||
readonly errorMessage?: string; | ||
/** | ||
Optional callback that receives the [download item](https://electronjs.org/docs/api/download-item). | ||
You can use this for advanced handling such as canceling the item like `item.cancel()`. | ||
*/ | ||
readonly onStarted?: (item: DownloadItem) => void; | ||
/** | ||
Optional callback that receives the [download item](https://electronjs.org/docs/api/download-item). | ||
You can use this for advanced handling such as canceling the item like `item.cancel()`. | ||
*/ | ||
readonly onStarted?: (item: DownloadItem) => void; | ||
/** | ||
Optional callback that receives an object containing information about the progress of the current download item. | ||
*/ | ||
readonly onProgress?: (progress: Progress) => void; | ||
/** | ||
Optional callback that receives an object containing information about the progress of the current download item. | ||
*/ | ||
readonly onProgress?: (progress: Progress) => void; | ||
/** | ||
Optional callback that receives an object containing information about the combined progress of all download items done within any registered window. | ||
/** | ||
Optional callback that receives an object containing information about the combined progress of all download items done within any registered window. | ||
Each time a new download is started, the next callback will include it. The progress percentage could therefore become smaller again. | ||
This callback provides the same data that is used for the progress bar on the app icon. | ||
*/ | ||
readonly onTotalProgress?: (progress: Progress) => void; | ||
Each time a new download is started, the next callback will include it. The progress percentage could therefore become smaller again. | ||
This callback provides the same data that is used for the progress bar on the app icon. | ||
*/ | ||
readonly onTotalProgress?: (progress: Progress) => void; | ||
/** | ||
Optional callback that receives the [download item](https://electronjs.org/docs/api/download-item) for which the download has been cancelled. | ||
*/ | ||
readonly onCancel?: (item: DownloadItem) => void; | ||
/** | ||
Optional callback that receives the [download item](https://electronjs.org/docs/api/download-item) for which the download has been cancelled. | ||
*/ | ||
readonly onCancel?: (item: DownloadItem) => void; | ||
/** | ||
Optional callback that receives an object with information about an item that has been completed. It is called for each completed item. | ||
*/ | ||
readonly onCompleted?: (file: File) => void; | ||
/** | ||
Optional callback that receives an object with information about an item that has been completed. It is called for each completed item. | ||
*/ | ||
readonly onCompleted?: (file: File) => void; | ||
/** | ||
Reveal the downloaded file in the system file manager, and if possible, select the file. | ||
/** | ||
Reveal the downloaded file in the system file manager, and if possible, select the file. | ||
@default false | ||
*/ | ||
readonly openFolderWhenDone?: boolean; | ||
@default false | ||
*/ | ||
readonly openFolderWhenDone?: boolean; | ||
/** | ||
Show a file count badge on the macOS/Linux dock/taskbar icon when a download is in progress. | ||
/** | ||
Show a file count badge on the macOS/Linux dock/taskbar icon when a download is in progress. | ||
@default true | ||
*/ | ||
readonly showBadge?: boolean; | ||
@default true | ||
*/ | ||
readonly showBadge?: boolean; | ||
/** | ||
Show a progress bar on the dock/taskbar icon when a download is in progress. | ||
/** | ||
Show a progress bar on the dock/taskbar icon when a download is in progress. | ||
@default true | ||
*/ | ||
readonly showProgressBar?: boolean; | ||
@default true | ||
*/ | ||
readonly showProgressBar?: boolean; | ||
/** | ||
Allow downloaded files to overwrite files with the same name in the directory they are saved to. | ||
/** | ||
Allow downloaded files to overwrite files with the same name in the directory they are saved to. | ||
The default behavior is to append a number to the filename. | ||
The default behavior is to append a number to the filename. | ||
@default false | ||
*/ | ||
readonly overwrite?: boolean; | ||
@default false | ||
*/ | ||
readonly overwrite?: boolean; | ||
/** | ||
Customize the save dialog. | ||
/** | ||
Customize the save dialog. | ||
If `defaultPath` is not explicity defined, a default value is assigned based on the file path. | ||
If `defaultPath` is not explicity defined, a default value is assigned based on the file path. | ||
@default {} | ||
*/ | ||
readonly dialogOptions?: SaveDialogOptions; | ||
} | ||
} | ||
@default {} | ||
*/ | ||
readonly dialogOptions?: SaveDialogOptions; | ||
}; | ||
@@ -136,57 +139,47 @@ /** | ||
*/ | ||
declare class CancelError extends Error {} | ||
export class CancelError extends Error {} | ||
// eslint-disable-next-line no-redeclare | ||
declare const electronDl: { | ||
/** | ||
Error thrown if `item.cancel()` was called. | ||
*/ | ||
CancelError: typeof CancelError; | ||
/** | ||
Register the helper for all windows. | ||
/** | ||
Register the helper for all windows. | ||
@example | ||
``` | ||
import {app, BrowserWindow} from 'electron'; | ||
import electronDl from 'electron-dl'; | ||
@example | ||
``` | ||
import {app, BrowserWindow} from 'electron'; | ||
import electronDl = require('electron-dl'); | ||
electronDl(); | ||
electronDl(); | ||
let mainWindow; | ||
(async () => { | ||
await app.whenReady(); | ||
mainWindow = new BrowserWindow(); | ||
})(); | ||
``` | ||
*/ | ||
export default function electronDl(options?: Options): void; | ||
let win; | ||
(async () => { | ||
await app.whenReady(); | ||
win = new BrowserWindow(); | ||
})(); | ||
``` | ||
*/ | ||
(options?: electronDl.Options): void; | ||
/** | ||
This can be useful if you need download functionality in a reusable module. | ||
/** | ||
This can be useful if you need download functionality in a reusable module. | ||
@param window - Window to register the behavior on. | ||
@param url - URL to download. | ||
@returns A promise for the downloaded file. | ||
@throws {CancelError} An error if the user calls `item.cancel()`. | ||
@throws {Error} An error if the download fails. | ||
@param window - Window to register the behavior on. | ||
@param url - URL to download. | ||
@returns A promise for the downloaded file. | ||
@throws {CancelError} An error if the user calls `item.cancel()`. | ||
@throws {Error} An error if the download fails. | ||
@example | ||
``` | ||
import {BrowserWindow, ipcMain} from 'electron'; | ||
import {download} from 'electron-dl'; | ||
@example | ||
``` | ||
import {BrowserWindow, ipcMain} from 'electron'; | ||
import electronDl = require('electron-dl'); | ||
ipcMain.on('download-button', async (event, {url}) => { | ||
const win = BrowserWindow.getFocusedWindow(); | ||
console.log(await electronDl.download(win, url)); | ||
}); | ||
``` | ||
*/ | ||
download( | ||
window: BrowserWindow | BrowserView, | ||
url: string, | ||
options?: electronDl.Options | ||
): Promise<DownloadItem>; | ||
}; | ||
export = electronDl; | ||
ipcMain.on('download-button', async (event, {url}) => { | ||
const win = BrowserWindow.getFocusedWindow(); | ||
console.log(await download(win, url)); | ||
}); | ||
``` | ||
*/ | ||
export function download( | ||
window: BrowserWindow | BrowserView, | ||
url: string, | ||
options?: Options | ||
): Promise<DownloadItem>; |
110
index.js
@@ -1,9 +0,14 @@ | ||
'use strict'; | ||
const path = require('path'); | ||
const {app, BrowserWindow, shell, dialog} = require('electron'); | ||
const unusedFilename = require('unused-filename'); | ||
const pupa = require('pupa'); | ||
const extName = require('ext-name'); | ||
import process from 'node:process'; | ||
import path from 'node:path'; | ||
import { | ||
app, | ||
BrowserWindow, | ||
shell, | ||
dialog, | ||
} from 'electron'; | ||
import {unusedFilenameSync} from 'unused-filename'; | ||
import pupa from 'pupa'; | ||
import extName from 'ext-name'; | ||
class CancelError extends Error {} | ||
export class CancelError extends Error {} | ||
@@ -20,35 +25,2 @@ const getFilenameFromMime = (name, mime) => { | ||
const majorElectronVersion = () => { | ||
const version = process.versions.electron.split('.'); | ||
return Number.parseInt(version[0], 10); | ||
}; | ||
const getWindowFromBrowserView = webContents => { | ||
for (const currentWindow of BrowserWindow.getAllWindows()) { | ||
for (const currentBrowserView of currentWindow.getBrowserViews()) { | ||
if (currentBrowserView.webContents.id === webContents.id) { | ||
return currentWindow; | ||
} | ||
} | ||
} | ||
}; | ||
const getWindowFromWebContents = webContents => { | ||
let window_; | ||
const webContentsType = webContents.getType(); | ||
switch (webContentsType) { | ||
case 'webview': | ||
window_ = BrowserWindow.fromWebContents(webContents.hostWebContents); | ||
break; | ||
case 'browserView': | ||
window_ = getWindowFromBrowserView(webContents); | ||
break; | ||
default: | ||
window_ = BrowserWindow.fromWebContents(webContents); | ||
break; | ||
} | ||
return window_; | ||
}; | ||
function registerListener(session, options, callback = () => {}) { | ||
@@ -65,3 +37,3 @@ const downloadItems = new Set(); | ||
showProgressBar: true, | ||
...options | ||
...options, | ||
}; | ||
@@ -73,3 +45,6 @@ | ||
const window_ = majorElectronVersion() >= 12 ? BrowserWindow.fromWebContents(webContents) : getWindowFromWebContents(webContents); | ||
const window_ = BrowserWindow.fromWebContents(webContents); | ||
if (!window_) { | ||
throw new Error('Failed to get window from web contents.'); | ||
} | ||
@@ -80,3 +55,3 @@ if (options.directory && !path.isAbsolute(options.directory)) { | ||
const directory = options.directory || app.getPath('downloads'); | ||
const directory = options.directory ?? app.getPath('downloads'); | ||
@@ -90,6 +65,6 @@ let filePath; | ||
filePath = options.overwrite ? path.join(directory, name) : unusedFilename.sync(path.join(directory, name)); | ||
filePath = options.overwrite ? path.join(directory, name) : unusedFilenameSync(path.join(directory, name)); | ||
} | ||
const errorMessage = options.errorMessage || 'The download of {filename} was interrupted'; | ||
const errorMessage = options.errorMessage ?? 'The download of {filename} was interrupted'; | ||
@@ -123,3 +98,3 @@ if (options.saveAs) { | ||
transferredBytes: itemTransferredBytes, | ||
totalBytes: itemTotalBytes | ||
totalBytes: itemTotalBytes, | ||
}); | ||
@@ -132,3 +107,3 @@ } | ||
transferredBytes: receivedBytes, | ||
totalBytes | ||
totalBytes, | ||
}); | ||
@@ -162,2 +137,3 @@ } | ||
} | ||
callback(new CancelError()); | ||
@@ -185,3 +161,3 @@ } else if (state === 'interrupted') { | ||
mimeType: item.getMimeType(), | ||
url: item.getURL() | ||
url: item.getURL(), | ||
}); | ||
@@ -202,7 +178,7 @@ } | ||
module.exports = (options = {}) => { | ||
export default function electronDl(options = {}) { | ||
app.on('session-created', session => { | ||
registerListener(session, options, (error, _) => { | ||
if (error && !(error instanceof CancelError)) { | ||
const errorTitle = options.errorTitle || 'Download Error'; | ||
const errorTitle = options.errorTitle ?? 'Download Error'; | ||
dialog.showErrorBox(errorTitle, error.message); | ||
@@ -212,21 +188,21 @@ } | ||
}); | ||
}; | ||
} | ||
module.exports.download = (window_, url, options) => new Promise((resolve, reject) => { | ||
options = { | ||
...options, | ||
unregisterWhenDone: true | ||
}; | ||
export async function download(window_, url, options) { | ||
return new Promise((resolve, reject) => { | ||
options = { | ||
...options, | ||
unregisterWhenDone: true, | ||
}; | ||
registerListener(window_.webContents.session, options, (error, item) => { | ||
if (error) { | ||
reject(error); | ||
} else { | ||
resolve(item); | ||
} | ||
registerListener(window_.webContents.session, options, (error, item) => { | ||
if (error) { | ||
reject(error); | ||
} else { | ||
resolve(item); | ||
} | ||
}); | ||
window_.webContents.downloadURL(url); | ||
}); | ||
window_.webContents.downloadURL(url); | ||
}); | ||
module.exports.CancelError = CancelError; | ||
} |
{ | ||
"name": "electron-dl", | ||
"version": "3.5.2", | ||
"version": "4.0.0", | ||
"description": "Simplified file downloads for your Electron app", | ||
@@ -13,9 +13,15 @@ "license": "MIT", | ||
}, | ||
"type": "module", | ||
"exports": { | ||
"types": "./index.d.ts", | ||
"default": "./index.js" | ||
}, | ||
"sideEffects": false, | ||
"engines": { | ||
"node": ">=12" | ||
"node": ">=18" | ||
}, | ||
"scripts": { | ||
"start": "electron run.js", | ||
"test": "xo && ava && tsd" | ||
"//test": "xo && ava && tsc index.d.ts", | ||
"test": "xo && tsc index.d.ts" | ||
}, | ||
@@ -36,18 +42,16 @@ "files": [ | ||
"ext-name": "^5.0.0", | ||
"pupa": "^2.0.1", | ||
"unused-filename": "^2.1.0" | ||
"pupa": "^3.1.0", | ||
"unused-filename": "^4.0.1" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "^13.1.4", | ||
"ava": "^2.4.0", | ||
"cp-file": "^7.0.0", | ||
"electron": "^7.1.7", | ||
"minimist": "^1.2.0", | ||
"@types/node": "^20.12.7", | ||
"ava": "^6.1.2", | ||
"copy-file": "^11.0.0", | ||
"electron": "^30.0.1", | ||
"minimist": "^1.2.8", | ||
"node-static": "^0.7.11", | ||
"pify": "^4.0.1", | ||
"spectron": "^9.0.0", | ||
"tsd": "^0.17.0", | ||
"typescript": "^4.4.3", | ||
"uuid": "^8.3.2", | ||
"xo": "^0.39.0" | ||
"pify": "^6.1.0", | ||
"spectron": "^19.0.0", | ||
"typescript": "^5.4.5", | ||
"xo": "^0.58.0" | ||
}, | ||
@@ -59,3 +63,6 @@ "xo": { | ||
] | ||
}, | ||
"ava": { | ||
"serial": true | ||
} | ||
} |
@@ -11,3 +11,2 @@ # electron-dl | ||
- Handles multiple downloads. | ||
- Support for `BrowserWindow` and `BrowserView`. | ||
- Shows badge count *(macOS & Linux only)* and download progress. Example on macOS: | ||
@@ -23,3 +22,3 @@ | ||
Requires Electron 7 or later. | ||
*Requires Electron 30 or later.* | ||
@@ -33,11 +32,11 @@ ## Usage | ||
```js | ||
const {app, BrowserWindow} = require('electron'); | ||
const electronDl = require('electron-dl'); | ||
import {app, BrowserWindow} from 'electron'; | ||
import electronDl from 'electron-dl'; | ||
electronDl(); | ||
let win; | ||
let mainWindow; | ||
(async () => { | ||
await app.whenReady(); | ||
win = new BrowserWindow(); | ||
mainWindow = new BrowserWindow(); | ||
})(); | ||
@@ -51,16 +50,16 @@ ``` | ||
```js | ||
const {BrowserWindow, ipcMain} = require('electron'); | ||
const {download} = require('electron-dl'); | ||
import {BrowserWindow, ipcMain} from 'electron'; | ||
import {download, CancelError} from 'electron-dl'; | ||
ipcMain.on('download-button', async (event, {url}) => { | ||
const win = BrowserWindow.getFocusedWindow(); | ||
try { | ||
console.log(await download(win, url)); | ||
} catch (error) { | ||
if (error instanceof electronDl.CancelError) { | ||
console.info('item.cancel() was called'); | ||
} else { | ||
console.error(error); | ||
} | ||
} | ||
const win = BrowserWindow.getFocusedWindow(); | ||
try { | ||
console.log(await download(win, url)); | ||
} catch (error) { | ||
if (error instanceof CancelError) { | ||
console.info('item.cancel() was called'); | ||
} else { | ||
console.error(error); | ||
} | ||
} | ||
}); | ||
@@ -75,9 +74,9 @@ ``` | ||
### electronDl.download(window, url, options?): Promise<[DownloadItem](https://electronjs.org/docs/api/download-item)> | ||
### download(window, url, options?): Promise<[DownloadItem](https://electronjs.org/docs/api/download-item)> | ||
### window | ||
Type: `BrowserWindow | BrowserView` | ||
Type: `BrowserWindow | WebContentsView` | ||
Window to register the behavior on. Alternatively, a `BrowserView` can be passed. | ||
The window to register the behavior on. Alternatively, a `WebContentsView` can be passed. | ||
@@ -88,3 +87,3 @@ ### url | ||
URL to download. | ||
The URL to download. | ||
@@ -243,9 +242,9 @@ ### options | ||
```sh | ||
npm test | ||
``` | ||
$ npm test | ||
``` | ||
And before submitting a pull request, run the manual tests to manually verify that everything works: | ||
``` | ||
```sh | ||
npm start | ||
@@ -252,0 +251,0 @@ ``` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
10
Yes
18550
302
252
+ Addedescape-goat@4.0.0(transitive)
+ Addedescape-string-regexp@5.0.0(transitive)
+ Addedpath-exists@5.0.0(transitive)
+ Addedpupa@3.1.0(transitive)
+ Addedunused-filename@4.0.1(transitive)
- Removedescape-goat@2.1.1(transitive)
- Removedmodify-filename@1.1.0(transitive)
- Removedpath-exists@4.0.0(transitive)
- Removedpupa@2.1.1(transitive)
- Removedunused-filename@2.1.0(transitive)
Updatedpupa@^3.1.0
Updatedunused-filename@^4.0.1