Skip to content

Commit f88b66c

Browse files
author
Tyschenko
committed
For iOS add support for downloading files which have blob: url or base64 url
1 parent d0cccc5 commit f88b66c

File tree

1 file changed

+102
-1
lines changed

1 file changed

+102
-1
lines changed

apple/RNCWebViewImpl.m

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,7 @@ - (WKWebViewConfiguration *)setUpWkWebViewConfig
472472
wkWebViewConfig.processPool = [[RNCWKProcessPoolManager sharedManager] sharedProcessPool];
473473
}
474474
wkWebViewConfig.userContentController = [WKUserContentController new];
475+
[wkWebViewConfig.userContentController addScriptMessageHandler:self name:@"base64Handler"];
475476

476477
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 /* iOS 13 */
477478
if (@available(iOS 13.0, *)) {
@@ -771,7 +772,10 @@ - (void)setAutomaticallyAdjustsScrollIndicatorInsets:(BOOL)automaticallyAdjustsS
771772
- (void)userContentController:(WKUserContentController *)userContentController
772773
didReceiveScriptMessage:(WKScriptMessage *)message
773774
{
774-
if ([message.name isEqualToString:HistoryShimName]) {
775+
if ([message.name isEqualToString:@"base64Handler"]) {
776+
NSString *base64String = message.body;
777+
[self downloadBase64File:base64String];
778+
} else if ([message.name isEqualToString:HistoryShimName]) {
775779
if (_onLoadingFinish) {
776780
NSMutableDictionary<NSString *, id> *event = [self baseEvent];
777781
[event addEntriesFromDictionary: @{@"navigationType": message.body}];
@@ -962,6 +966,68 @@ -(void)setKeyboardDisplayRequiresUserAction:(BOOL)keyboardDisplayRequiresUserAct
962966
method_setImplementation(method, override);
963967
}
964968

969+
- (void)downloadBase64File:(NSString *)base64String {
970+
NSArray *components = [base64String componentsSeparatedByString:@","];
971+
NSString *base64ContentPart = components.lastObject;
972+
973+
NSData *fileData = [[NSData alloc] initWithBase64EncodedString:base64ContentPart options:NSDataBase64DecodingIgnoreUnknownCharacters];
974+
NSString *fileExtension = [self fileExtensionFromBase64String:base64String];
975+
976+
NSString *tempFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"File.%@", fileExtension]];
977+
[fileData writeToFile:tempFilePath atomically:YES];
978+
979+
NSURL *tempFileURL = [NSURL fileURLWithPath:tempFilePath];
980+
981+
UIDocumentPickerViewController *documentPicker = nil;
982+
if (@available(iOS 14.0, *)) {
983+
documentPicker = [[UIDocumentPickerViewController alloc] initForExportingURLs:@[tempFileURL] asCopy:YES];
984+
} else {
985+
// Usage of initWithURL:inMode: might lose file's extension and user has to type it manually
986+
// Problem was solved for iOS 14 and higher with initForExportingURLs
987+
documentPicker = [[UIDocumentPickerViewController alloc] initWithURL:tempFileURL inMode:UIDocumentPickerModeExportToService];
988+
}
989+
documentPicker.delegate = self;
990+
documentPicker.modalPresentationStyle = UIModalPresentationFullScreen;
991+
992+
UIViewController *rootViewController = [UIApplication sharedApplication].keyWindow.rootViewController;
993+
while (rootViewController.presentedViewController) {
994+
rootViewController = rootViewController.presentedViewController;
995+
}
996+
[rootViewController presentViewController:documentPicker animated:YES completion:nil];
997+
}
998+
999+
- (NSString *)fileExtensionFromBase64String:(NSString *)base64String {
1000+
NSRange mimeTypeRange = [base64String rangeOfString:@"data:"];
1001+
NSRange base64Range = [base64String rangeOfString:@";base64"];
1002+
1003+
if (mimeTypeRange.location != NSNotFound && base64Range.location != NSNotFound) {
1004+
NSUInteger start = NSMaxRange(mimeTypeRange);
1005+
NSUInteger length = base64Range.location - start;
1006+
NSString *mimeType = [base64String substringWithRange:NSMakeRange(start, length)];
1007+
1008+
NSDictionary *mimeTypeToExtension = @{
1009+
@"image/png": @"png",
1010+
@"image/jpeg": @"jpg",
1011+
@"image/gif": @"gif",
1012+
@"application/pdf": @"pdf",
1013+
@"text/plain": @"txt",
1014+
@"application/json": @"json",
1015+
@"application/octet-stream": @"bin"
1016+
};
1017+
1018+
NSString *fileExtension = mimeTypeToExtension[mimeType];
1019+
return fileExtension ?: @"bin";
1020+
}
1021+
1022+
return @"bin";
1023+
}
1024+
1025+
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
1026+
if (urls.count > 0) {
1027+
NSURL *destinationURL = urls.firstObject;
1028+
}
1029+
}
1030+
9651031
-(void)setHideKeyboardAccessoryView:(BOOL)hideKeyboardAccessoryView
9661032
{
9671033
_hideKeyboardAccessoryView = hideKeyboardAccessoryView;
@@ -1322,6 +1388,21 @@ - (void) webView:(WKWebView *)webView
13221388
static NSDictionary<NSNumber *, NSString *> *navigationTypes;
13231389
static dispatch_once_t onceToken;
13241390

1391+
NSString *urlString = navigationAction.request.URL.absoluteString;
1392+
if ([urlString hasPrefix:@"blob:"]) {
1393+
if (_onFileDownload) {
1394+
[self handleBlobDownloadUrl:urlString];
1395+
decisionHandler(WKNavigationActionPolicyCancel);
1396+
return;
1397+
}
1398+
} else if ([urlString hasPrefix:@"data:"]) {
1399+
if (_onFileDownload) {
1400+
[self downloadBase64File:urlString];
1401+
decisionHandler(WKNavigationActionPolicyCancel);
1402+
return;
1403+
}
1404+
}
1405+
13251406
dispatch_once(&onceToken, ^{
13261407
navigationTypes = @{
13271408
@(WKNavigationTypeLinkActivated): @"click",
@@ -1952,6 +2033,26 @@ - (NSURLRequest *)requestForSource:(id)json {
19522033
return request;
19532034
}
19542035

2036+
- (void)handleBlobDownloadUrl:(NSString *)urlString {
2037+
NSString *jsCode = [NSString stringWithFormat:
2038+
@"var xhr = new XMLHttpRequest();"
2039+
"xhr.open('GET', '%@', true);"
2040+
"xhr.responseType = 'blob';"
2041+
"xhr.onload = function(e) {"
2042+
" if (this.status == 200) {"
2043+
" var blobFile = this.response;"
2044+
" var reader = new FileReader();"
2045+
" reader.readAsDataURL(blobFile);"
2046+
" reader.onloadend = function() {"
2047+
" var base64 = reader.result;"
2048+
" window.webkit.messageHandlers.base64Handler.postMessage(base64);"
2049+
" };"
2050+
" }"
2051+
"};"
2052+
"xhr.send();", urlString];
2053+
[self.webView evaluateJavaScript:jsCode completionHandler:^(id result, NSError *error) {}];
2054+
}
2055+
19552056
@end
19562057

19572058
@implementation RNCWeakScriptMessageDelegate

0 commit comments

Comments
 (0)