ionic-team / ionic-team/capacitor

bug: Blob downloads don't work on iOS & Android

Open
#5,478 12 comments 10 reactions 0 assignees View on GitHub
priority: low type: feature request
Dominant language
TypeScript
Stars
16.7k
Forks
1.3k
Avg merge
3d 15h
Merged PRs (30d)
10

Description

## Bug Report

### Capacitor Version

```
Latest Dependencies:

@capacitor/cli: 3.4.1
@capacitor/core: 3.4.1
@capacitor/android: 3.4.1
@capacitor/ios: 3.4.1

Installed Dependencies:

@capacitor/cli: 3.4.1
@capacitor/core: 3.4.1
@capacitor/android: 3.4.1
@capacitor/ios: 3.4.1

[success] iOS looking great! 👌
[success] Android looking great! 👌
```

### Platform(s)
- iOS (Tested on iOS 13.x, 14.x and 15.2 )
- Android (tested on Android 11, API 29)

### Current Behavior
**iOS 15>=** (tested on few 15.x different versions)
Whenever browser is asked to `navigate` to a URL such as `blob:capacitor://localhost/ee26ec26-911c-461e-b01e-dd715c99e54a` something like:
```
XX XXXX V1.1[59585:3775699] [Process] 0x15684b018 - [pageProxyID=5, webPageID=6, PID=59586] WebPageProxy::didFailProvisionalLoadForFrame: frameID = 3, domain = WebKitErrorDomain, code = 102
```
is printed out and nothing happens.

**iOS 13.2.2, 13.0, 14.0.1 & 14.5 - (safe to assume this for 14.5 =<)**
Error is different:
```
Failed to open URL blob:https://jimmywarting.github.io/29dbf607-c3d4-49a3-baa2-977d71cb3b80: Error Domain=NSOSStatusErrorDomain Code=-10814 "(null)" UserInfo={_LSLine=247, _LSFunction=-[_LSDOpenClient openURL:options:completionHandler:]}
```

**Android**
Nothing happens, more investigation is needed to see technical details of the implementation.

### Expected Behavior
Allow that blob to be downloaded without errors into a standard download directory (as Safari does).
Maybe, an even better solution would be asking the user where to save it. (optionally)
By RFC these URLs should be handled and downloaded by the browser. More details at: https://w3c.github.io/FileAPI/#url-intro

### Code Reproduction
**iOS**
- Download from repo and install deps: https://github.com/ikon-integration/Capacitor-Blob-Download-Issue
- Open on Xcode `npx capacitor open ios`, run from there
- After the app is loaded, click on the start button. **(careful, button on this sample seems pretty small on the simulator, so better to zoom on the webview)**
- Error should be printed on the console

_Alternatively: (with any Capacitor app you might have around)_
- Set your `capacitor.config.json` **server.url** to `https://jimmywarting.github.io/StreamSaver.js/examples/saving-multiple-files.html` (example)
- Zoom in to be able to see the `start` button, and click on it.
- Error should be available on the console

**Android**
Same from iOS, however, no error is displayed and the file is not visible anywhere else on the system.

### Other Technical Details
**iOS**
I believe this is not related to any versioning-related problem as it seems iOS 15 significantly improved the support of download/blob on the `WKWebViewDelegate`.
With some tweaks on the `WebViewDelegationHandler`, I was able to make it work and download to the `downloads` or `documents` folder seamlessly.

**Android**
Still, more investigation is needed on the Android side, but RFC described behaviour should also be

### Additional Context

By RCF `blob:` URL schema should trigger the download of the URL being provided. I believe this will enhance Capacitor support to already existing apps as developers expect the same behaviour to be followed.

### Possible fix
Changes on the`WebViewDelegationHandler` like the one below will handle it for `iOS > 14.5`, however still unsure where it should be saved as a default and how to allow developers to overwrite this behaviour. Another important factor is the attempt of making it to work with previous iOS versions for sure.
Creating a PR soon and linking into this issue. (Changes will be certainly be needed)

```
...minor changes above...
public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {

// post a notification for any listeners
NotificationCenter.default.post(name: .capacitorDecidePolicyForNavigationAction, object: navigationAction)

if #available(iOS 14.5, *) {
if (navigationAction.shouldPerformDownload) {
print("Is download");
decisionHandler(.download)
return;
}
}
.....
}
@available(iOS 14.5, *)
func webView(_ webView: WKWebView, navigationAction: WKNavigationAction, didBecome download: WKDownload) {
download.delegate = self;
}
@available(iOS 14.5, *)
func webView(_ webView: WKWebView, navigationResponse: WKNavigationResponse, didBecome download: WKDownload) {
download.delegate = self;
}

public func download(_ download: WKDownload, decideDestinationUsing response: URLResponse, suggestedFilename: String, completionHandler: @escaping (URL?) -> Void) {
print("decide destination", suggestedFilename);

let documentsURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let documentURL = documentsURL.appendingPathComponent(suggestedFilename, isDirectory: false)
print(documentURL.absoluteString)
completionHandler(documentURL);
}
....minor changes below....
```

-----
**Edited to** Improve issue with reproducible code, android behaviour and other small improvements

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.