Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add note about handling invalid URLs #15

Merged
merged 1 commit into from
Oct 21, 2024
Merged
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
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ test
config
__mocks__
.github
test-app
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 3.2.1 (2024-10-21)

- Add an FAQ section to `README.md` on how to handle invalid URLs

# 3.2.0 (2024-08-04)

- Fix issues around starting multiple downloads at the same time.
Expand Down
45 changes: 44 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ manager.resumeDownload(id);
- [`isDownloadInterrupted()`](#isdownloadinterrupted)
- [`isDownloadCompleted()`](#isdownloadcompleted)
- [Mock class](#mock-class)
- [FAQ](#faq)
- [Acknowledgments](#acknowledgments)

# Installation
Expand Down Expand Up @@ -352,7 +353,9 @@ class DownloadData {
*/
resolvedFilename: string
/**
* If true, the download was cancelled from the save as dialog
* 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
/**
Expand Down Expand Up @@ -455,6 +458,46 @@ If you need to mock out `ElectronDownloadManager` in your tests, you can use the

`import { ElectronDownloadManagerMock } from 'electron-dl-manager'`

# FAQ

## How do I capture if the download is invalid? `onError()` is not being called.

Electron doesn't provide an explicit way to capture errors for downloads in general.
What it does for invalid URLs, it will trigger the `onDownloadCancelled()` callback.

```typescript
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):

```typescript
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');
```

# Acknowledgments

This code uses small portions from [`electron-dl`](https://github.com/sindresorhus/electron-dl) and is noted in the
Expand Down
4 changes: 3 additions & 1 deletion src/DownloadData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ export class DownloadData {
*/
resolvedFilename: string;
/**
* If true, the download was cancelled from the save as dialog
* 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;
/**
Expand Down
2 changes: 1 addition & 1 deletion src/DownloadInitiator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ export class DownloadInitiator {
}
} else if (this.downloadData.isDownloadCancelled()) {
clearInterval(interval);
this.log("Download was cancelled by user");
this.log("Download was cancelled");
this.downloadData.cancelledFromSaveAsDialog = true;
await this.callbackDispatcher.onDownloadCancelled(this.downloadData);
} else {
Expand Down
9 changes: 9 additions & 0 deletions test-app/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
4 changes: 4 additions & 0 deletions test-app/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
dist
out
.gitignore
7 changes: 7 additions & 0 deletions test-app/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
extends: [
'eslint:recommended',
'@electron-toolkit/eslint-config-ts/recommended',
'@electron-toolkit/eslint-config-prettier'
]
}
5 changes: 5 additions & 0 deletions test-app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
dist
out
.DS_Store
*.log*
6 changes: 6 additions & 0 deletions test-app/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
out
dist
pnpm-lock.yaml
LICENSE.md
tsconfig.json
tsconfig.*.json
4 changes: 4 additions & 0 deletions test-app/.prettierrc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
singleQuote: true
semi: false
printWidth: 100
trailingComma: none
3 changes: 3 additions & 0 deletions test-app/.vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"recommendations": ["dbaeumer.vscode-eslint"]
}
39 changes: 39 additions & 0 deletions test-app/.vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Main Process",
"type": "node",
"request": "launch",
"cwd": "${workspaceRoot}",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite",
"windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite.cmd"
},
"runtimeArgs": ["--sourcemap"],
"env": {
"REMOTE_DEBUGGING_PORT": "9222"
}
},
{
"name": "Debug Renderer Process",
"port": 9222,
"request": "attach",
"type": "chrome",
"webRoot": "${workspaceFolder}/src/renderer",
"timeout": 60000,
"presentation": {
"hidden": true
}
}
],
"compounds": [
{
"name": "Debug All",
"configurations": ["Debug Main Process", "Debug Renderer Process"],
"presentation": {
"order": 1
}
}
]
}
11 changes: 11 additions & 0 deletions test-app/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
34 changes: 34 additions & 0 deletions test-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# test-app

A minimal Electron application with TypeScript

## Recommended IDE Setup

- [VSCode](https://code.visualstudio.com/) + [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)

## Project Setup

### Install

```bash
$ npm install
```

### Development

```bash
$ npm run dev
```

### Build

```bash
# For windows
$ npm run build:win

# For macOS
$ npm run build:mac

# For Linux
$ npm run build:linux
```
12 changes: 12 additions & 0 deletions test-app/build/entitlements.mac.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
</dict>
</plist>
Binary file added test-app/build/icon.icns
Binary file not shown.
Binary file added test-app/build/icon.ico
Binary file not shown.
Binary file added test-app/build/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 43 additions & 0 deletions test-app/electron-builder.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
appId: com.electron.app
productName: test-app
directories:
buildResources: build
files:
- '!**/.vscode/*'
- '!src/*'
- '!electron.vite.config.{js,ts,mjs,cjs}'
- '!{.eslintignore,.eslintrc.js,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
- '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
- '!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}'
asarUnpack:
- resources/**
win:
executableName: test-app
nsis:
artifactName: ${name}-${version}-setup.${ext}
shortcutName: ${productName}
uninstallDisplayName: ${productName}
createDesktopShortcut: always
mac:
entitlementsInherit: build/entitlements.mac.plist
extendInfo:
- NSCameraUsageDescription: Application requests access to the device's camera.
- NSMicrophoneUsageDescription: Application requests access to the device's microphone.
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
notarize: false
dmg:
artifactName: ${name}-${version}.${ext}
linux:
target:
- AppImage
- snap
- deb
maintainer: electronjs.org
category: Utility
appImage:
artifactName: ${name}-${version}.${ext}
npmRebuild: false
publish:
provider: generic
url: https://example.com/auto-updates
11 changes: 11 additions & 0 deletions test-app/electron.vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'

export default defineConfig({
main: {
plugins: [externalizeDepsPlugin()]
},
preload: {
plugins: [externalizeDepsPlugin()]
},
renderer: {}
})
Loading
Loading