'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'); function getFilenameFromMime(name, mime) { const exts = extName.mime(mime); if (exts.length !== 1) { return name; } return `${name}.${exts[0].ext}`; } function registerListener(session, options, cb = () => {}) { const downloadItems = new Set(); let receivedBytes = 0; let completedBytes = 0; let totalBytes = 0; const activeDownloadItems = () => downloadItems.size; const progressDownloadItems = () => receivedBytes / totalBytes; options = Object.assign({ showBadge: true }, options); const listener = (e, item, webContents) => { downloadItems.add(item); totalBytes += item.getTotalBytes(); let hostWebContents = webContents; if (webContents.getType() === 'webview') { ({hostWebContents} = webContents); } const win = BrowserWindow.fromWebContents(hostWebContents); const dir = options.directory || app.getPath('downloads'); let filePath; if (options.filename) { filePath = path.join(dir, options.filename); } else { const filename = item.getFilename(); const name = path.extname(filename) ? filename : getFilenameFromMime(filename, item.getMimeType()); filePath = unusedFilename.sync(path.join(dir, name)); } const errorMessage = options.errorMessage || 'The download of {filename} was interrupted'; const errorTitle = options.errorTitle || 'Download Error'; if (!options.saveAs) { item.setSavePath(filePath); } if (typeof options.onStarted === 'function') { options.onStarted(item); } item.on('updated', () => { receivedBytes = [...downloadItems].reduce((receivedBytes, item) => { receivedBytes += item.getReceivedBytes(); return receivedBytes; }, completedBytes); if (options.showBadge && ['darwin', 'linux'].includes(process.platform)) { app.setBadgeCount(activeDownloadItems()); } if (!win.isDestroyed()) { win.setProgressBar(progressDownloadItems()); } if (typeof options.onProgress === 'function') { options.onProgress(progressDownloadItems()); } }); item.on('done', (event, state) => { completedBytes += item.getTotalBytes(); downloadItems.delete(item); if (options.showBadge && ['darwin', 'linux'].includes(process.platform)) { app.setBadgeCount(activeDownloadItems()); } if (!win.isDestroyed() && !activeDownloadItems()) { win.setProgressBar(-1); receivedBytes = 0; completedBytes = 0; totalBytes = 0; } if (options.unregisterWhenDone) { session.removeListener('will-download', listener); } if (state === 'cancelled') { if (typeof options.onCancel === 'function') { options.onCancel(item); } } else if (state === 'interrupted') { const message = pupa(errorMessage, {filename: item.getFilename()}); dialog.showErrorBox(errorTitle, message); cb(new Error(message)); } else if (state === 'completed') { if (process.platform === 'darwin') { app.dock.downloadFinished(filePath); } if (options.openFolderWhenDone) { shell.showItemInFolder(path.join(dir, item.getFilename())); } cb(null, item); } }); }; session.on('will-download', listener); } module.exports = (options = {}) => { app.on('session-created', session => { registerListener(session, options); }); }; // TODO: Remove this for the next major release module.exports.default = module.exports; module.exports.download = (win, url, options) => new Promise((resolve, reject) => { options = Object.assign({}, options, {unregisterWhenDone: true}); registerListener(win.webContents.session, options, (err, item) => { if (err) { reject(err); } else { resolve(item); } }); win.webContents.downloadURL(url); });