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
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
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:
Download the PDF data in the web view.
Base 64 encode the data in the web view.
Return the base 64 encoded string to the script running outside the web view.
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.
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.