-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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 Blob downloading feature (Android) #3022
base: master
Are you sure you want to change the base?
Changes from 1 commit
8e128cd
51bdc13
dced91e
bd51354
1d7e282
1d9bef1
1de0cdc
2c4628c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,45 @@ | ||||||||||||
// This is used because download from native side won't have session changes. | ||||||||||||
|
||||||||||||
window.reactNativeDownloadBlobUrl = function reactNativeDownloadBlobUrl(url) { | ||||||||||||
var req = new XMLHttpRequest(); | ||||||||||||
req.open('GET', url, true); | ||||||||||||
req.responseType = 'blob'; | ||||||||||||
|
||||||||||||
req.onload = function(event) { | ||||||||||||
var blob = req.response; | ||||||||||||
saveBlob(blob); | ||||||||||||
}; | ||||||||||||
req.send(); | ||||||||||||
|
||||||||||||
function sendMessage(message) { | ||||||||||||
ReactNativeWebViewDownloader.downloadFile(JSON.stringify(message)); | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
} | ||||||||||||
|
||||||||||||
function saveBlob(blob, filename) { | ||||||||||||
var reader = new FileReader(); | ||||||||||||
reader.readAsDataURL(blob); | ||||||||||||
let fileName = ""; | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
|
||||||||||||
|
||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
reader.onloadend = function() { | ||||||||||||
|
||||||||||||
const popularExts = ["pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt", "jpg", "jpeg", "png", "gif", "bmp", "tiff", "tif", "svg", "mp3", "mp4", "avi", "mov", "wmv", "flv", "ogg", "webm", "mkv", "zip", "rar", "7z", "tar", "gz", "bz2", "iso", "dmg", "exe", "apk", "torrent", "epub", "mobi", "azw", "azw3", "djvu", "djv", "fb2", "rtf", "odt", "odp", "ods", "odg", "odf", "odb", "csv", "tsv", "ics", "vcf", "msg", "eml", "emlx", "mht", "mhtml", "xps", "oxps", "ps", "rtfd", "key", "numbers", "pages", "apk", "torrent", "epub", "mobi", "azw", "azw3", "djvu", "djv", "fb2", "rtf", "odt", "odp", "ods", "odg", "odf", "odb", "csv", "tsv", "ics", "vcf", "msg", "eml", "emlx", "mht", "mhtml", "xps", "oxps", "ps", "rtfd", "key", "numbers", "pages"]; | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems like a more accurate name would be |
||||||||||||
let ext = blob.type.split("/")[1]; | ||||||||||||
if (!ext || !popularExts.includes(ext)) { | ||||||||||||
ext = "bin"; | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
} | ||||||||||||
|
||||||||||||
fileName = `${filename || "download"}.${ext}`; | ||||||||||||
|
||||||||||||
sendMessage({ | ||||||||||||
event: 'file', | ||||||||||||
fileName, | ||||||||||||
data: reader.result | ||||||||||||
}); | ||||||||||||
|
||||||||||||
devmuhnnad marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||
} | ||||||||||||
}; | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
|
||||||||||||
} | ||||||||||||
devmuhnnad marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||
|
||||||||||||
|
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -13,8 +13,10 @@ import android.view.ViewGroup | |||
import android.view.WindowManager | ||||
import android.webkit.CookieManager | ||||
import android.webkit.DownloadListener | ||||
import android.webkit.JavascriptInterface | ||||
import android.webkit.WebSettings | ||||
import android.webkit.WebView | ||||
import androidx.annotation.RequiresApi | ||||
import androidx.webkit.WebSettingsCompat | ||||
import androidx.webkit.WebViewFeature | ||||
import com.facebook.react.bridge.ReadableArray | ||||
|
@@ -24,6 +26,8 @@ import com.facebook.react.common.build.ReactBuildConfig | |||
import com.facebook.react.uimanager.ThemedReactContext | ||||
import org.json.JSONException | ||||
import org.json.JSONObject | ||||
import java.io.BufferedInputStream | ||||
import java.io.ByteArrayOutputStream | ||||
import java.io.UnsupportedEncodingException | ||||
import java.net.MalformedURLException | ||||
import java.net.URL | ||||
|
@@ -81,6 +85,7 @@ class RNCWebViewManagerImpl { | |||
setAllowUniversalAccessFromFileURLs(webView, false) | ||||
setMixedContentMode(webView, "never") | ||||
|
||||
|
||||
devmuhnnad marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
// Fixes broken full-screen modals/galleries due to body height being 0. | ||||
webView.layoutParams = ViewGroup.LayoutParams( | ||||
ViewGroup.LayoutParams.MATCH_PARENT, | ||||
|
@@ -90,6 +95,16 @@ class RNCWebViewManagerImpl { | |||
WebView.setWebContentsDebuggingEnabled(true) | ||||
} | ||||
webView.setDownloadListener(DownloadListener { url, userAgent, contentDisposition, mimetype, contentLength -> | ||||
|
||||
devmuhnnad marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
Log.i("ReactNative", mimetype); | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This log line should probably include some context, e.g. what's being logged |
||||
if(url.startsWith("blob:")){ | ||||
Log.i("ReactNative", "Downloading " + url); | ||||
downloadBlob(url, webView) | ||||
return@DownloadListener | ||||
} | ||||
Comment on lines
+99
to
+103
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this should only be executed if the feature is enabled? |
||||
// print the url | ||||
devmuhnnad marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
|
||||
|
||||
webView.setIgnoreErrFailedForThisURL(url) | ||||
val module = webView.themedReactContext.getNativeModule(RNCWebViewModule::class.java) ?: return@DownloadListener | ||||
val request: DownloadManager.Request = try { | ||||
|
@@ -105,6 +120,8 @@ class RNCWebViewManagerImpl { | |||
|
||||
val downloadMessage = "Downloading $fileName" | ||||
|
||||
|
||||
|
||||
Comment on lines
+122
to
+123
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||
//Attempt to add cookie, if it exists | ||||
var urlObj: URL? = null | ||||
try { | ||||
|
@@ -137,6 +154,31 @@ class RNCWebViewManagerImpl { | |||
return webView | ||||
} | ||||
|
||||
|
||||
|
||||
Comment on lines
+156
to
+157
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||
class RNCWebViewBridge internal constructor() { | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the difference between this and |
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O) | ||||
@JavascriptInterface | ||||
fun downloadFile() { | ||||
Log.i("ReactNative", "invoked by js"); | ||||
} | ||||
Comment on lines
+160
to
+164
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks unused |
||||
|
||||
|
||||
Comment on lines
+165
to
+166
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||
} | ||||
|
||||
fun downloadBlob(url : String, webview: RNCWebView){ | ||||
injectJs(webview, "window.reactNativeDownloadBlobUrl('" + url + "');"); | ||||
} | ||||
|
||||
fun injectJs(webview: RNCWebView, js: String){ | ||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think React Native supports versions older than Kit Kat |
||||
webview.evaluateJavascript(js, null); | ||||
} else { | ||||
webview.loadUrl("javascript:$js"); | ||||
} | ||||
} | ||||
|
||||
private fun setupWebChromeClient( | ||||
webView: RNCWebView, | ||||
) { | ||||
|
@@ -499,6 +541,11 @@ class RNCWebViewManagerImpl { | |||
view.setMessagingEnabled(value) | ||||
} | ||||
|
||||
fun setBlobDownloadingEnabled(view: RNCWebView, value: Boolean) { | ||||
view.setDownloadingBlobEnabled(value) | ||||
view.setDownloadingMessage(getDownloadingMessageOrDefault()) | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems like you wanted a "downloaded" message, not the "downloading" message that |
||||
} | ||||
|
||||
fun setMediaPlaybackRequiresUserAction(view: RNCWebView, value: Boolean) { | ||||
view.settings.mediaPlaybackRequiresUserGesture = value | ||||
} | ||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -41,6 +41,7 @@ const WebViewComponent = forwardRef<{}, AndroidWebViewProps>(({ | |||||
allowFileAccess = false, | ||||||
saveFormDataDisabled = false, | ||||||
cacheEnabled = true, | ||||||
blobDownloadingEnabled = false, | ||||||
androidLayerType = "none", | ||||||
originWhitelist = defaultOriginWhitelist, | ||||||
setSupportMultipleWindows = true, | ||||||
|
@@ -79,7 +80,7 @@ const WebViewComponent = forwardRef<{}, AndroidWebViewProps>(({ | |||||
} | ||||||
}, []); | ||||||
|
||||||
const { onLoadingStart, onShouldStartLoadWithRequest, onMessage, viewState, setViewState, lastErrorEvent, onHttpError, onLoadingError, onLoadingFinish, onLoadingProgress, onRenderProcessGone } = useWebViewLogic({ | ||||||
const { onLoadingStart, onShouldStartLoadWithRequest, onMessage, viewState, setViewState, lastErrorEvent, onHttpError, onLoadingError, onLoadingFinish, onLoadingProgress, onRenderProcessGone } = useWebViewLogic({ | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
onNavigationStateChange, | ||||||
onLoad, | ||||||
onError, | ||||||
|
@@ -175,7 +176,7 @@ const WebViewComponent = forwardRef<{}, AndroidWebViewProps>(({ | |||||
{...otherProps} | ||||||
messagingEnabled={typeof onMessageProp === 'function'} | ||||||
messagingModuleName={messagingModuleName} | ||||||
|
||||||
blobDownloadingEnabled={blobDownloadingEnabled} | ||||||
hasOnScroll={!!otherProps.onScroll} | ||||||
onLoadingError={onLoadingError} | ||||||
onLoadingFinish={onLoadingFinish} | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what what this means about "session changes"?