The issue lies with how Blob objects serialize strings provided in the constructor's first argument, an array of items to include in the blob. String items are treated as Unicode text and serialized in Blob output using utf8 encoding. For this question in particular it means base64 strings supplied to the constructor are rendered as a string of 8 bit ASCII characters in blob output, whereas the PHP application is looking for decoded binary content, not the base64 characters used to encode it.
Potential solutions to provide a download link derived from a base64 string received from PHP on the server include:
Use a data URL in the download link without creating a Blob.
Pros: simple, Cons: Historically limits on the size of data URLs have been imposed by various browsers.
Create a blob from the base64 data string and download it using an Object URL;
Another option could be to provide PHP end points on the server to send files in response to fetch requests from the front end. Such would require changes to the application architecture.
The html page below demonstrates two methods of creating a download link from a base64 string. For security reasons (presumably because it downloads files programmatically) it doesn't work as a code snippet.
Generating test data requires opening a local pdf file to use its data, but the file
object read is not used in tests themselves. Code syntax has been kept extremely simple but could be modernized if support for obsolete browsers is not required. Use of jQuery was not attempted.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>PDF download</title>
</head>
<body>
<label>
Open a pdf file for use as test data:
<input type="file" id="fileBrowse" accept="application/pdf" onchange="testData.call( this,event)">
</label>
<hr>
<blockquote>
<button type="button" disabled onclick="testDataURL()"> Download pdf as data URL</button>
<p>
<button type="button" disabled onclick="testObjectURL()"> Download pdf from ObjectURL</button>
</blockquote>
<script>"use strict";
var json_data = null;
function testData(event) {
var file = this.files[0];
var fReader = new FileReader();
fReader.onload = function(){
var base64 = btoa( this.result);
json_data = {
data: { name: file.name, arquivo: base64 }
};
document.querySelectorAll("button").forEach( button => button.disabled=false);
};
fReader.onerror = function(err){ console.log(err);};
fReader.readAsBinaryString(file);
}
// check validity of base64 string by using it in a data URL
function testDataURL() {
var href = "data:application/pdf;base64," + json_data.data.arquivo;
downloadLink(json_data.data.name, href, false);
}
// check code to download as ObjectUURL
function testObjectURL() {
var base64 = json_data.data.arquivo;
var bString = atob(base64); // binary string;
var length = bString.length;
var bytes = new Uint8Array(length);
for( var i = 0; i < length; ++i) {
bytes[i] = bString.charCodeAt(i);
}
var oURL = URL.createObjectURL( new Blob([bytes], {type: "application/pdf"}));
downloadLink(json_data.data.name, oURL, true)
}
// download link common to both tests
function downloadLink( name, url, revoke) {
var a = document.createElement('a');
a.href = url;
a.download = name;
document.body.append(a);
a.click();
a.remove()
if(revoke) {
URL.revokeObjectURL(url);
}
}
</script></body>
</html>