Sharesheet from WebView does not recognize PDF

Hello everyone! I am new to this forum but was hoping somebody may be able to help.

I have a simple script that uses a WebView to open a webpage to a news database and use several calls to evaluateJavaScript to login, navigate through a few links, and selects a link to a PDF of an article.

For some reason when I then tried to load this url directly with await wv.loadURL(pdf_url) it would always give me the error: Error: URL is invalid: ... Due to some form of a login token / cookies issue, I am also unable to call Safari.open(pdf_url) as well.

So, to get around this, instead of having the javascript just return this url in the last step I change the window location: window.location.href = pdf_url and then make a call to await wv.present()

Everything works and in the popup window I see the PDF, however when I click on the share button the only option that comes up is SHARE URL. It does not recognize that there is a file being displayed. So, I cannot save the PDF from share sheet as I could in do if I were to go through all the steps by hand in Safari.

I am wondering if anybody has some suggested work arounds or advice!

I am happy to share the full code if anybody would like however it will not run on anybody’s machine since the url can only be accessed from my university network.

I would really appreciate any help as I have been stuck on this for quite awhile! Thank you :smile:

1 Like

I have the same problem, someone for a solution ?
:slightly_smiling_face:

@simonbs
Is it possible to add a feature that will allow you to save in pdf the result of the Webview function ?

I am not sure if I can actually recognize that you’re viewing a PDF and then extract it. I’ll have to think a little more about this :thinking:

The absolutely best would be if you could use the QuickLook API but that’s going to be difficult if the PDF have to be loaded in the web view first. A crazy workaround that I’m not even sure would work is:

  1. Download the PDF data in the web view.
  2. Base 64 encode the data in the web view.
  3. Return the base 64 encoded string to the script running outside the web view.
  4. Decode the base 64 to Data.
  5. Save the data to disk.
  6. QuickLook the file path you saved the data at.

Again I’m not sure this would work but it might.

I have a form, I fill it with my information then I click on a button that will generate a PDF dynamically, I think the PDF is built in the browser it’s not a physical file stored on a server that I can download.

With the webview function I can load the URL, inject javascript, simulate pressing the button, and view the PDF.

However as all these actions are done in the webview, I can’t save the PDF on my ipad.
I manage to get the URL of the dynamically generated PDF, this one is in the form :
blob:https://urlwebsite.com/28833711-0699-4232-9a03-abb69f833517

Do you have an advice for me ? To grab this PDF

Normally you should be able to return base64 from a blob to scriptable using: 

var reader = new FileReader();
reader.readAsDataURL(blob);
reader.onloadend = function() {
var base64data = reader.result;
competion(base64data);
}


So I would try to pass your blog url to the function reasAsDataURL

I was wrong, you probably need to transform you dataurl to a blob first:

Thank you for your answer,
I tried several things but I still can’t get that famous PDF.

I also confess that I’m a beginner in javascript and I don’t necessarily master all the subtleties.

In the webview instance I visualize the PDF via the webview.present() function.
From there, I have a URL which is in the form :
“blob:https://website.com/28833711-0699-4232-9a03-abb69f833517

If you can provide me with a sample code to retrieve the PDF, that would be great.

There is a way (didn’t know it myself):

At first, you have to get the blob that contains the PDF from the blob URL (Source):

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

Then, you have to convert this blob into a data URL, like @flyingeek has linked (Source):

//**blob to dataURL**
function blobToDataURL(blob, callback) {
    var a = new FileReader();
    a.onload = function(e) {callback(e.target.result);}
    a.readAsDataURL(blob);
}

And all that has to be evaluated in the WebView. So as a complete example:

This code doesn’t work. Look below for a working version

let script = `
(async function() {
  let url = window.location.href; // or any other method to get the url

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

  blobToDataURL(blob, completion); // pass the result directly to the completion function
})();

function blobToDataURL(blob, callback) {
    var a = new FileReader();
    a.onload = function(e) {callback(e.target.result);}
    a.readAsDataURL(blob);
}
`;
// wv is the WebView instance containing your PDF
let pdf = await wv.evaluateJavaScript(script, true);

I hope, it works. I haven’t tested it, nor do I know, if you can execute javascript in a WebView that displays a PDF…

Edit:

If this works, then pdf contains the data URL of the PDF. To save it as a file:

let pdf = await wv.evaluateJavaScript(script);

pdf = pdf.replace(/^data:[^,]+,/, "");
log(pdf)

let data = Data.fromBase64String(pdf);
let fm = FileManager.iCloud();
// change "my.pdf" to the name you want
let path = fm.joinPath(fm.documentsDirectory(), "my.pdf");
fm.write(path, data);

This code should work, at least it did for me.

Another edit

I’ve now had the chance to test this code thanks to this thread:

It looks like you can run javascript in the blob page, but Scriptable seems to struggle to register its global variables (log and completion). Because of this the code fails and never returns anything.

A workaround is to redirect the WebView to the data URL and then extract it via javascript:

// wait for the page to display the pdf as blob
await wv.waitForLoad();

let script = `
let url = window.location.href;

let blob = fetch(url)
  .then(r => r.blob())
  .then((blob) => {
    blobToDataURL(blob);
  });

function blobToDataURL(blob) {
  var a = new FileReader();
  a.onload = function(e) {
    // instead of trying to return it, load it
    location.href = e.target.result;
  };
  a.readAsDataURL(blob);
}
`;
// since we don't return anything from the script, we don't need to run it async. because scriptable struggles with registering the completion function, it won't even work when running it async
await wv.evaluateJavaScript(script);

// wait until the new url has loaded
await wv.waitForLoad();

// get the url
let pdf = await wv.evaluateJavaScript("location.href");

To now save the PDF, look at my previous edit above.