Save or DL a PDF from WebView

Hello,

I ran some Javascript on a webpage to fill a form that will generate a PDF.

Once the javascript is used, the pdf is generated and showed on webview but I have no idea how I can DL this PDF since the new url is blob: and cannot be shared.

Below the code so far

let url = "https://media.interieur.gouv.fr/deplacement-covid-19/"

let wv = new WebView()

await wv.loadURL(url)

let js = ' document.getElementById("field-firstname").value="RandomName"; '
let js2 = ' document.getElementById("field-lastname").value="RandomLastName"; '
let js3 = ' document.getElementById("field-birthday").value="01/01/2000"; '
let js4 = ' document.getElementById("field-placeofbirth").value="Paris"; '
let js5 = ' document.getElementById("field-address").value="1 Avenue des Champs Elyses"; '
let js6 = ' document.getElementById("field-city").value="Paris"; '
let js7 = ' document.getElementById("field-zipcode").value="75000"; '
let js8 = ' document.getElementById("field-datesortie" ).value="2020-11-22"; '
let js9 = 'document.getElementById("field-heuresortie").value="16:30"; '
let js10 = ' document.getElementById("checkbox-achats").checked = true; '
let jsend = ' document.querySelector(".btn-attestation").click(); '

wv.evaluateJavaScript(js+js2+js3+js4+js5+js6+js7+js8+js9+js10+jsend);

wv.present()

Any clues ?

This was already asked, but I don’t know if the answer works:

Thank you,

I tried your code but I always got the same error in Logs:

Error: Failed evaluating Javascript with error on line 0: SyntaxError: Unexpected indentifier 'fetch'. Expected ';' after variable declaration.

I copied exactly what you wrote but can’t find what the problem is in

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

Oh, thanks. I’ve fixed the problem in the “complete example”. Running the first two code snippets inside Scriptable (not a WebView) won’t work.

Hi, thank you for corrections.

Can’t make it succeed. I either try to execute the code from the webview (the blob pdf page) or right after my example code (which generate the pdf page), i got the same error in log :slight_smile:

Error: Failed evaluating JavaScript with error: JavaScript execution returned of an unsupported type

Maybe you could try your code with my example in order to check what is wrong?

I’ve tried it now with your example above and found a working solution.

It seems that Scriptable struggles to register the completion function in the blob page and therefore the script fails.

I’ve updated the code in the other thread to keep all the code in one place!

Thank you helping me on this.

How did you get it to work ? It looks like it actually get the blob URL but can’t success the DL part.

Error on line 65:9 Expected value of type Data but got value of type null. 

It looks like it can’t grab the B64 string from the URL and convert data.

Here is the final code

let url = "https://media.interieur.gouv.fr/deplacement-covid-19/"

let wv = new WebView()

await wv.loadURL(url)

let js = ' document.getElementById("field-firstname").value="William"; '
let js2 = ' document.getElementById("field-lastname").value="Bureau"; '
let js3 = ' document.getElementById("field-birthday").value="15/06/1991"; '
let js4 = ' document.getElementById("field-placeofbirth").value="Nantes"; '
let js5 = ' document.getElementById("field-address").value="9 rue Jean Emile Laboureur"; '
let js6 = ' document.getElementById("field-city").value="Nantes"; '
let js7 = ' document.getElementById("field-zipcode").value="44000"; '
let js8 = ' document.getElementById("field-datesortie" ).value="2020-11-24"; '
let js9 = 'document.getElementById("field-heuresortie").value="14:30"; '
let js10 = ' document.getElementById("checkbox-achats").checked = true; '
let jsend = ' document.querySelector(".btn-attestation").click(); '


wv.evaluateJavaScript(js+js2+js3+js4+js5+js6+js7+js8+js9+js10+jsend);


await wv.waitForLoad();

wv.present();

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) {
    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();


let pdf = await wv.evaluateJavaScript("location.href")

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

log(pdf)

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

I tried your script and it worked without any error, but it didn’t save the pdf. Instead it saved an HTML page… So I compared them and found no major difference. The only thing that could potentially have an effect (never say never) is the line

I have an await in front, you don’t. And that was also the solution.

Since I can’t replicate your error, try it with the await as mentioned. This will hopefully resolve your issue!

I added an await but now it results on a blank page on webview and then it is loading forever. I added an await before the wv.present() so now I have the pdf on webview but then it’s loading forever and doesn’t save the pdf. (I can’t see the log pdf btw, something is loading before). Maybe it is saved somewhere but can’t see it ? Should it be saved in Scriptable folder ?

Maybe you could share me the working code you have so I can also compare ?

Thank you

This is really interesting…

You are saving the PDF to the local Scriptable folder which is only accessible via the FileManager API.

Here is my code

let url = "https://media.interieur.gouv.fr/deplacement-covid-19/"

let wv = new WebView()

await wv.loadURL(url)

let js = `
document.getElementById("field-firstname").value="RandomName";
document.getElementById("field-lastname").value="RandomLastName";
document.getElementById("field-birthday").value="01/01/2000";
document.getElementById("field-placeofbirth").value="Paris";
document.getElementById("field-address").value="1 Avenue des Champs Elyses";
document.getElementById("field-city").value="Paris";
document.getElementById("field-zipcode").value="75000";
document.getElementById("field-datesortie" ).value="2020-11-22";
document.getElementById("field-heuresortie").value="16:30";
document.getElementById("checkbox-achats").checked = true;

document.querySelector(".btn-attestation").click();
`;

// wv.present()

await wv.evaluateJavaScript(js)

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) {
      location.href = e.target.result;
    };
    a.readAsDataURL(blob);
}
`;

await wv.evaluateJavaScript(script);

await wv.waitForLoad();

let pdf = await wv.evaluateJavaScript("location.href");

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

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);
log("done");

Thank you again for your efforts.

Yes, in my example I forget to remove FileManager.local it but I changed it to iCloud right after, it was just for a test.

your code is almost the same as mine, I tried to make 1 var JS like you instead of 10 but I got the error :

Error: Failed evaluating Javascript with error on line 2 : SyntaxError: Unexpected EOF

Got this from begining that is why I split it in 10 variables in first place.

The only difference I can see is :

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

Where /^data:[^,]+,/ is green in your code an blue in mine.

So I ended up copying your whole code and pasting it to a new script. The synthax error diseappared but nothing happens… It running with not end. No “done” in log and no file created in iCloud Scriptable Folder…

This time this is 100% the same code as your I really don’t know why it doesn’t work…

I assume you use the latest Scriptable version?

I can’t, since my phone is still on iOS 13, so I’m using version 1.4.14

Other than that I can’t think of any reason why this doesn’t work for you…

I’m going to update in the next few days. I can then try it again!


The reason for this is because I specify the language of the code block when “fencing” it:

```javascript
// code goes here
```

Off topic: How have I got three backticks in the code block? Just start the code block with more:

````
```javascript
// code goes here
```
````

I am on iOS 14.2 and Scriptable 1.6.1

Maybe something changed in Scriptable or new iOS that doesn’t allow to do this anymore… I think only @simonbs could know about it…

Let me know if you find out what is wrong when you’ve updated, I will keep searching on my side also.

Thank you for your help so far !

EDIT : i think the problem come from this script that makes the app nevers stop loading. I tried to make it run alone and I can’t get the DataURL neither. It runs without ending.


let url = "blob:https://media.interieur.gouv.fr/738e01ab-6f9d-41b5-96cc-027d6c07f1ab"

let wv = new WebView();

await wv.loadURL(url);

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) {
    location.href = e.target.result;
  };
  a.readAsDataURL(blob);
}
`;

await wv.waitForLoad();

pdf = await wv.evaluateJavaScript("location.href");

log(pdf);

I tried another way to deal with blob, but still with no sucess.

let script = `

let url = window.location.href 

var xhr = new XMLHttpRequest;
xhr.responseType = 'blob';
xhr.onload = function() {
  var recoveredBlob = xhr.response;
  var reader = new FileReader;
  reader.onload = function() {
    var blobAsDataUrl = reader.result;
    location.href = blobAsDataUrl;
   };
  reader.readAsDataUrl(recoveredBlob);
  
};

xhr.open('GET', url);
xhr.send();


`;
	

I tried (a complicated) workaround. As

"https://media.interieur.gouv.fr/deplacement-covid-19/"

is open source on Github, I forked it, I found the function to DL de blob:

export function downloadBlob (blob, fileName) {
  const link = createElement('a')
  const url = URL.createObjectURL(blob)
  link.href = url
  link.download = fileName
  document.body.appendChild(link)
  link.click()
}

than I replaced by

export function BlobToDataURL(blob) {
    var a = new FileReader();
    a.onload = function(e) {location.href = e.target.result ;}
    a.readAsDataURL(blob);

and replace eveywhere the function downloadBlob function was call by the blobToDataURL one

Then I deployed it to Heroku for testing.

It worked well trying on my firefox browser on my laptop got the DataURL link and it was displayed in reader.

But when tried on scriptable, nothing is generated. I tried on safari then, did not work either. It looks like the a.readAsDataURL(blob); does nothing.

I tried one dataURL generated from my laptop and copied it directly in Safari browser and it opened it (behaved like a blob).

Maybe I am doing something wrong (again)?

I’ve now upgraded to iOS 14.2 and can’t get my version of the script running. It can’t retrieve the object from the blob URL. This seems more to be an error from Safari than from Scriptable. You’ve probably got the same error in your try with XMLHttpRequest. Since this part doesn’t work, your only bet is your last idea, but since it looks like that this also doesn’t work, I suspect a bug in Safari that was introduced with iOS 14.

I’ve only found one mention of this issue: https://developer.apple.com/forums/thread/667148