What I want to achieve: Upon clicking icon of my Chrome extension, a file with downloaded from remote location and the user is presented with saveAs dialog.
Caveats: I'm using latest manifest from Google, namely v3
.
I have edited this post many times as I I was able to achieve more and more. I leave only my latest code.
tl;dr Now almost everything works.Crucial thing is missing: response from server (body) is not saved. Instead string [object Object]
is saved.
// when icon is clicked
chrome.action.onClicked.addListener(tab => {
if (tab.url.startsWith('http')) {
post({url: tab.url})
.then(async res => ({
filename: getFilename(res.headers.get('Content-Disposition')),
blob: await res.blob()
}))
.then(response => {
console.log(response);
chrome.scripting.executeScript({
target: {tabId: tab.id},
func: saveFile,
args: [response.filename, response.blob],
})
})
.catch((error) => chrome.scripting.executeScript({
target: {tabId: tab.id},
func: showAlert,
args: [error],
}));
}
});
function getFilename(header) {
return /filename="(. )"/.exec(header)[1];
}
async function post(data = {}) {
return await fetch('http://localhost:5000/citation',{
method: 'POST',
body: new URLSearchParams(data)
});
}
function showAlert(error) {
let message = error.error !== null ? error.error : error.message;
alert("Error: " message);
}
async function saveFile(filename, blob) {
let link = document.createElement('a');
let url = window.URL.createObjectURL(new Blob([blob],
{type: 'application/octet-stream'}));
link.href = url;
link.download = filename;
link.click();
// For Firefox it is necessary to delay revoking the ObjectURL.
setTimeout(() => {
window.URL.revokeObjectURL(url);
}, 250);
}
CodePudding user response:
The problem is in serializing the blob
object from the chrome extension to the content script, which I think it's a bug in chrome. if you try to log the blob object that saveFile()
receives, you will notice it's an empty object.
So instead of passing the blob
object, you have to pass another serializable object to the content script.
I have converted the blob
object to a base64
object then passed it to the content script.
First, let's create a function to convert the blob
object to a base64
object
function blobToBase64(blob) {
return new Promise((resolve, _) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.readAsDataURL(blob);
});
}
Then convert the blob
object
chrome.action.onClicked.addListener(tab => {
if (tab.url.startsWith('http')) {
post({url: tab.url})
.then(async res => ({
filename: getFilename(res.headers.get('Content-Disposition')),
blob: await res.blob()
}))
.then(async response => {
var base64 = await blobToBase64(response.blob)
chrome.scripting.executeScript({
target: {tabId: tab.id},
func: saveFile,
args: [response.filename, base64],
})
})
.catch((error) => chrome.scripting.executeScript({
target: {tabId: tab.id},
func: showAlert,
args: [error],
}));
}
});
then in the saveFile()
method, you need to convert that base64
into a file
async function saveFile(filename, base64URL) {
fetch(base64URL)
.then(res => res.blob())
.then(blob => {
let link = document.createElement('a');
let url = window.URL.createObjectURL(new Blob([blob],
{ type: 'application/octet-stream' }));
link.href = url;
link.download = filename;
link.click();
// For Firefox it is necessary to delay revoking the ObjectURL.
setTimeout(() => {
window.URL.revokeObjectURL(url);
}, 250);
})
}
If you will not do anything in saveFile()
but download the file, then it will be better to download the file from the extension itself because converting the blob
to base64
then getting it back to blob
again is slowing down the downloading process.