Lo que quiero lograr: al hacer clic en el ícono de mi extensión de Chrome, se descarga un archivo desde una ubicación remota y se le presenta al usuario el cuadro de diálogo Guardar como.
Advertencias: estoy usando el último manifiesto de Google, a saber, v3 .
He editado esta publicación muchas veces porque pude lograr más y más. Dejo solo mi último código.
tl; dr Ahora casi todo funciona. Falta algo crucial: la respuesta del servidor (cuerpo) no se guarda. En su lugar, se guarda la cadena [object Object] .
// 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); }El problema está en serializar el objeto blob de la extensión de Chrome al script de contenido, lo que creo que es un error en Chrome. si intenta registrar el objeto blob que recibe saveFile() , notará que es un objeto vacío.
Entonces, en lugar de pasar el objeto blob , debe pasar otro objeto serializable al script de contenido.
Convertí el objeto blob en un objeto base64 y luego lo pasé al script de contenido.
Primero, creemos una función para convertir el objeto blob en un objeto base64
function blobToBase64(blob) { return new Promise((resolve, _) => { const reader = new FileReader(); reader.onloadend = () => resolve(reader.result); reader.readAsDataURL(blob); }); } Luego convierte el objeto blob
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], })); } }); luego, en el método saveFile() , debe convertir ese base64 en un archivo
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); }) } Si no hará nada en saveFile() pero descargará el archivo, entonces será mejor descargar el archivo desde la propia extensión porque convertir el blob a base64 y luego volverlo a blob ralentiza el proceso de descarga.