@@ -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