Wkwebview: How to Handle Blob Url

WKWebView: How to handle BLOB URL

Note that this is a really roundabout hack. Scan all href and replace any blob urls detected with datauri.
EDIT: updated to show it running

function blobToDataURL(blob, callback) {
var a = new FileReader();
a.onload = function(e) {callback(e.target.result);}
a.readAsDataURL(blob);
}
// not sure what elements you are going to intercept:
document.querySelectorAll('a').forEach(async (el)=>{
const url = el.getAttribute('href');
if( url.indexOf('blob:')===0 ) {
let blob = await fetch(url).then(r => r.blob());
blobToDataURL(blob, datauri => el.setAttribute('href',datauri));
}
});





b=new Blob([new Int8Array([1,2,3,4,5,6,7,8,9,10]).buffer]);

test.href=URL.createObjectURL(b);

b=new Blob([new Int8Array([31,32,33,34,35]).buffer]);

test1.href=URL.createObjectURL(b);

b=new Blob([new Int8Array([51,52,53,54]).buffer]);

test2.href=URL.createObjectURL(b);




function blobToDataURL(blob, callback) {

var a = new FileReader();

a.onload = function(e) {callback(e.target.result);}

a.readAsDataURL(blob);

}


document.addEventListener('click', function(event) {

event.preventDefault();

if ( event.target.matches('a[href^="blob:"]') )

(async el=>{

const url = el.href;

const blob = await fetch(url).then(r => r.blob());

blobToDataURL(blob, datauri => el.href=datauri);

})(event.target);

});


// not sure what elements you are going to intercept:

/*document.querySelectorAll('a').forEach(async (el)=>{

const url = el.href;

if( url.indexOf('blob:')===0 ) {

let blob = await fetch(url).then(r => r.blob());

blobToDataURL(blob, datauri => el.href=datauri);

}

});*/
<a id="test">test</a>

<a id="test1">test</a>

<a id="test2">test</a>

How to download a blob URI using AlamoFire

After a few days, I was able to figure out how to download a blob URL without WKDownloadDelegate. The following code builds upon this answer.

A message handler needs to be created to respond to JS messages. I created this in the makeUIView function

webModel.webView.configuration.userContentController.add(context.coordinator, name: "jsListener")

Inside your WKNavigationDelegate, you need to add this code on a navigation action.

NOTE: Since I use SwiftUI, all of my variables/models are located in the parent class (UIViewRepresentable coordinator).

func webView(_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if let url = navigationAction.request.url, let scheme = url.scheme?.lowercased() {
if scheme == "blob" {
// Defer to JS handling
parent.webModel.executeBlobDownloadJS(url: url)

decisionHandler(.cancel)
} else {
decisionHandler(.allow)
}
}
}

Here's the JS to request for the blob stored in the browser memory. I added this JS in a wrapper function which called evaluateJavaScript with the url for cleanliness of my code.

function blobToDataURL(blob, callback) {
var reader = new FileReader()
reader.onload = function(e) {callback(e.target.result.split(",")[1])}
reader.readAsDataURL(blob)
}

async function run() {
const url = "\(url)"
const blob = await fetch(url).then(r => r.blob())

blobToDataURL(blob, datauri => {
const responseObj = {
url: url,
mimeType: blob.type,
size: blob.size,
dataString: datauri
}
window.webkit.messageHandlers.jsListener.postMessage(JSON.stringify(responseObj))
})
}

run()

In addition to the returned JS object, I had to make a struct where I can deserialize the JSON string:

struct BlobComponents: Codable {
let url: String
let mimeType: String
let size: Int64
let dataString: String
}

I then took the messages sent to the WKScriptMessageHandler and interpreted them for saving to files. I used the SwiftUI file mover here, but you can do anything you want with this content.

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
guard let jsonString = message.body as? String else {
return
}

parent.webModel.blobDownloadWith(jsonString: jsonString)
}

In my web model (needed to import CoreServices):

func blobDownloadWith(jsonString: String) {
guard let jsonData = jsonString.data(using: .utf8) else {
print("Cannot convert blob JSON into data!")
return
}

let decoder = JSONDecoder()

do {
let file = try decoder.decode(BlobComponents.self, from: jsonData)

guard let data = Data(base64Encoded: file.dataString),
let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, file.mimeType as CFString, nil),
let ext = UTTypeCopyPreferredTagWithClass(uti.takeRetainedValue(), kUTTagClassFilenameExtension)
else {
print("Error! \(error)")
return
}

let fileName = file.url.components(separatedBy: "/").last ?? "unknown"
let path = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let url = path.appendingPathComponent("blobDownload-\(fileName).\(ext.takeRetainedValue())")

try data.write(to: url)

downloadFileUrl = url
showFileMover = true
} catch {
print("Error! \(error)")
return
}
}


Related Topics



Leave a reply



Submit