Blob download not working with JavaScript inside a WebView - javascript

I have a React application which needs to support both WebViews and Browsers. I have a functionality where-in we have an API which returns base64String and we need to convert it to PDF and provide a link to user to download the PDF. Below is a sample code snippet.
Code Snippet:
const convertBase64StringToPDF = (data: string) => {
const byteCharacters = atob(data);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
return new Blob([byteArray], { type: 'application/pdf' });
};
getbase64StringData(baseUrl2)
.then((response) => {
const blob = convertBase64StringToPDF(response.foobar);
const link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = 'MyDoc' + new Date() + '.pdf';
link.click();
})
The problem I'm facing is when a user clicks on the link we create, the download isn't getting triggered only in WebViews. I searched for solutions but most of them suggest making changes in WebView code. Problem is I don't have control over the App code which has the WebView embedded in it. Is there a way I can solve this in React/JavaScript application? I don't care where the download happens, I just need it to get triggered and get stored in users device.

Related

Download Images from image url stored in array with javascript or best alternative

Hey everyone I am trying to download images I have displayed on my react app. The app gets images from an API and displays them on the webpage and also stores the img src in an array. I'm trying to make a download button that will loop through my src array and download all images displayed and need some guidance. I have read many previous posts and realized I am not able to use cURL within my react app and the fetch API does not download the images. I'm trying to see if there is a way to do this with Javascript or is there a simple alternative with another programming language. Thank you for your help!
const downloadAll = () => {
const imgArr = document.querySelectorAll('img');
for (let i = 0; i < imgArr.length; i++) {
let a = imgArr[i].src;
}
};
Using the download attribute of an anchor should do the trick...
EDIT
download only works for same-origin URLs, or the blob: and data: schemes. ref
Since this is not your case, you will have to create a blob with each image and fortunately, that is easy with the fetch API.
const downloadAll = async () => {
// Create and append a link
let link = document.createElement("a");
document.documentElement.append(link);
const imgArr = document.querySelectorAll("img");
for (let i = 0; i < imgArr.length; i++) {
await fetch(imgArr[i].src)
.then(res => res.blob()) // Gets the response and returns it as a blob
.then(blob => {
let objectURL = URL.createObjectURL(blob);
// Set the download name and href
link.setAttribute("download", `image_${i}.jpg`);
link.href = objectURL;
// Auto click the link
link.click();
})
}
};
Tested on CodePen.

How to download exported excel from laravel using axios?

I have my code below from Backend that is working fine when I test it using Laravel's blade. But I can't do the same in using Axios in my React frontend (see frontend code below).
return (new NewsExport())->download($filename);
I somehow found some solutions from another site: they change the backend code, they use the Storage method to return a link instead of a file. But I don't want to use Storage, I want to prevent overstoring files (in case of a user in the frontend rapidly clicks the download button).
My question how can we download the returned file from the backend in the frontend Axios?
My frontend codes (the codes below successfully download an excel file, but the file is corrupted I think because I can't open it when I test it using Microsoft Excel)
let response = await newsApi.exportNewsList(payload).then(response => {
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'exc.xlsx'); //or any other extension
document.body.appendChild(link);
link.click();
});
Here is the response.data when I log: screenshot
I think you forgot to specify your responseType as blob in your api options.
const options = {
method: 'POST',
responseType: 'blob', <=== you need this one
data: formData,
url: '/yourApi',
};
return API.request(options);
It might be to do with how you are converting the response to a blob.
Here is a block of code that I always use when I have to do something like this. I have formatted it to suit your code above so hopefully it will work the same way
const b64toBlob = (b64Data, contentType = '', sliceSize = 512) => {
const byteCharacters = atob(b64Data);
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
const blob = new Blob(byteArrays, { type: contentType });
return blob;
};
let response = await newsApi.exportNewsList(payload).then(response => {
const dataBlob = b64toBlob(
response.data,
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64'
);
const link = window.document.createElement('a');
link.href = window.URL.createObjectURL(dataBlob);
link.download = 'exc.xlsx';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});

Alternative for 'download' attribute in Safari/iOS

I have a blob created with a base64, and I need to make this data downloadable as a pdf.
I created this snippet:
var blob = new Blob([byte]);
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.target = '_blank';
var fileName = name + '.pdf';
link.download = fileName;
link.click();
It works on all the browsers, except safari mobile on iOS.
The file gets actually downloaded, but its name is "unknown", then it can't be open since the extension gets lost.
The problem is that the download attribute lacks support on this browser and IE.
There are a lot of workarounds for IE, but I didn't find any for Safari/iOS.
Do you know how can I download a blob got from a base64 (no XHR involved) in this browser?
Thank you
I need to make this data downloadable as a pdf (...) in safari iOS
SHORT ANSWER: you can't. Due this bug is impossible to download the file on safari iOS
The alternative is to open the file on the browser with the proper mime type, so it can show its content (and the user can then manually download it if needed).
Make sure to pass mime type when creating the Blob. reference
var blob = new Blob([byte], {type: 'application/pdf'});
Lastly, I'd strongly suggest you to use FileSaver.js which that can handle most of the corner cases/multiple browser support for save (or in this case, open) a file in javascript.
As per the below link:-
https://caniuse.com/#feat=download
Safari 13 Beta 3 is released so you can check on the same, whether its working or not?
You can download a blob got from a base64 by using a atob function.
The atob function will decode a base64-encoded string into a new string with a character for each byte of the binary data.
You can save blob locally via FileSaver.js .
You can also check here that would be helpful:-
How to open Blob URL on Chrome iOS
This is something I have tried in my project and it is working for me.
import "./styles.css";
var pdfData =
"JVBERi0xLjcKCjEgMCBvYmogICUgZW50cnkgcG9pbnQKPDwKICAvVHlwZSAvQ2F0YWxvZwog" +
"IC9QYWdlcyAyIDAgUgo+PgplbmRvYmoKCjIgMCBvYmoKPDwKICAvVHlwZSAvUGFnZXMKICAv" +
"TWVkaWFCb3ggWyAwIDAgMjAwIDIwMCBdCiAgL0NvdW50IDEKICAvS2lkcyBbIDMgMCBSIF0K" +
"Pj4KZW5kb2JqCgozIDAgb2JqCjw8CiAgL1R5cGUgL1BhZ2UKICAvUGFyZW50IDIgMCBSCiAg" +
"L1Jlc291cmNlcyA8PAogICAgL0ZvbnQgPDwKICAgICAgL0YxIDQgMCBSIAogICAgPj4KICA+" +
"PgogIC9Db250ZW50cyA1IDAgUgo+PgplbmRvYmoKCjQgMCBvYmoKPDwKICAvVHlwZSAvRm9u" +
"dAogIC9TdWJ0eXBlIC9UeXBlMQogIC9CYXNlRm9udCAvVGltZXMtUm9tYW4KPj4KZW5kb2Jq" +
"Cgo1IDAgb2JqICAlIHBhZ2UgY29udGVudAo8PAogIC9MZW5ndGggNDQKPj4Kc3RyZWFtCkJU" +
"CjcwIDUwIFRECi9GMSAxMiBUZgooSGVsbG8sIHdvcmxkISkgVGoKRVQKZW5kc3RyZWFtCmVu" +
"ZG9iagoKeHJlZgowIDYKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDEwIDAwMDAwIG4g" +
"CjAwMDAwMDAwNzkgMDAwMDAgbiAKMDAwMDAwMDE3MyAwMDAwMCBuIAowMDAwMDAwMzAxIDAw" +
"MDAwIG4gCjAwMDAwMDAzODAgMDAwMDAgbiAKdHJhaWxlcgo8PAogIC9TaXplIDYKICAvUm9v" +
"dCAxIDAgUgo+PgpzdGFydHhyZWYKNDkyCiUlRU9G";
let download = () => {
if (pdfData) {
var iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
var isChrome =
navigator.userAgent.toLowerCase().indexOf("CriOS") > -1 ||
navigator.vendor.toLowerCase().indexOf("google") > -1;
var iOSVersion = [];
if (iOS) {
iOSVersion = navigator.userAgent
.match(/OS [\d_]+/i)[0]
.substr(3)
.split("_")
.map((n) => parseInt(n));
}
var attachmentData = pdfData;
var attachmentName = "Test.pdf";
var contentType = "application/pdf";
var binary = atob(attachmentData.replace(/\s/g, ""));
var len = binary.length;
var buffer = new ArrayBuffer(len);
var view = new Uint8Array(buffer);
for (var i = 0; i < len; i++) {
view[i] = binary.charCodeAt(i);
}
var linkElement = document.createElement("a");
try {
var hrefUrl = "";
var blob = "";
if (iOS && !isChrome && iOSVersion[0] <= 12) {
blob = "data:application/pdf;base64," + pdfData;
hrefUrl = blob;
} else {
if (iOS && !isChrome) {
contentType = "application/octet-stream";
}
blob = new Blob([view], { type: contentType });
hrefUrl = window.URL.createObjectURL(blob);
}
linkElement.setAttribute("href", hrefUrl);
linkElement.setAttribute("target", "_blank");
if ((iOS && (iOSVersion[0] > 12 || isChrome)) || !iOS) {
linkElement.setAttribute("download", attachmentName);
}
var clickEvent = new MouseEvent("click", {
view: window,
bubbles: true,
cancelable: false
});
linkElement.dispatchEvent(clickEvent);
} catch (ex) {}
}
};
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<button onClick={download}>Download</button>
</div>
);
}
The "target" attribute in Safari seems to override the "download" attribute. Currently, as to my knowledge, there is no way to solve this. So I think you have to wait for the next Safari version (13) which will be out in a few months.

Client download of a server generated zip file

Before somebody says, "duplicate", I just want to make sure, that folks know, that I have already reviewed these questions:
1) Uses angular and php, not sure what is happening here (I don't know PHP): Download zip file and trigger "save file" dialog from angular method
2) Can't get this answer to do anything: how to download a zip file using angular
3) This person can already download, which is past the point I'm trying to figure out:
Download external zip file from angular triggered on a button action
4) No answer for this one:
download .zip file from server in nodejs
5) I don't know what language this even is:
https://stackoverflow.com/questions/35596764/zip-file-download-using-angularjs-directive
Given those questions, if this is still a duplicate, I apologize. Here is, yet, another version of this question.
My angular 1.5.X client gives me a list of titles, of which each have an associated file. My Node 4.X/Express 4.X server takes that list, gets the file locations, creates a zip file, using express-zip from npm, and then streams that file back in the response. I then want my client to initiate the browser's "download a file" option.
Here's my client code (Angular 1.5.X):
function bulkdownload(titles){
titles = titles || [];
if ( titles.length > 0 ) {
$http.get('/query/bulkdownload',{
params:{titles:titles},
responseType:'arraybuffer'
})
.then(successCb,errorCb)
.catch(exceptionCb);
}
function successCb(response){
// This is the part I believe I cannot get to work, my code snippet is below
};
function errorCb(error){
alert('Error: ' + JSON.stringify(error));
};
function exceptionCb(ex){
alert('Exception: ' + JSON.stringify(ex));
};
};
Node (4.X) code with express-zip, https://www.npmjs.com/package/express-zip:
router.get('/bulkdownload',function(req,resp){
var titles = req.query.titles || [];
if ( titles.length > 0 ){
utils.getFileLocations(titles).
then(function(files){
let filename = 'zipfile.zip';
// .zip sets Content-Type and Content-disposition
resp.zip(files,filename,console.log);
},
_errorCb)
}
});
Here's my successCb in my client code (Angular 1.5.X):
function successCb(response){
var URL = $window.URL || $window.webkitURL || $window.mozURL || $window.msURL;
if ( URL ) {
var blob = new Blob([response.data],{type:'application/zip'});
var url = URL.createObjectURL(blob);
$window.open(url);
}
};
The "blob" part seems to work fine. Checking it in IE's debugger, it does look like a file stream of octet information. Now, I believe I need to get that blob into the some HTML5 directive, to initiate the "Save File As" from the browser. Maybe? Maybe not?
Since 90%+ of our users are using IE11, I test all of my angular in PhantomJS (Karma) and IE. When I run the code, I get the old "Access is denied" error in an alert window:
Exception: {"description":"Access is denied...<stack trace>}
Suggestions, clarifications, answers, etc. are welcome!
Use this one:
var url="YOUR ZIP URL HERE";
window.open(url, '_blank');
var zip_file_path = "" //put inside "" your path with file.zip
var zip_file_name = "" //put inside "" file name or something
var a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
a.href = zip_file_path;
a.download = zip_file_name;
a.click();
document.body.removeChild(a);
As indicated in this answer, I have used the below Javascript function and now I am able to download the byte[] array content successfully.
Function to convert byte array stream (type of string) to blob object:
var b64toBlob = function(b64Data, contentType, sliceSize) {
contentType = contentType || '';
sliceSize = sliceSize || 512;
var byteCharacters = atob(b64Data);
var byteArrays = [];
for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
var slice = byteCharacters.slice(offset, offset + sliceSize);
var byteNumbers = new Array(slice.length);
for (var i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
var byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
var blob = new Blob(byteArrays, {type: contentType});
return blob;
};
An this is how I call this function and save the blob object with FileSaver.js (getting data via Angular.js $http.get):
$http.get("your/api/uri").success(function(data, status, headers, config) {
//Here, data is type of string
var blob = b64toBlob(data, 'application/zip');
var fileName = "download.zip";
saveAs(blob, fileName);
});
Note: I am sending the byte[] array (Java-Server-Side) like this:
byte[] myByteArray = /*generate your zip file and convert into byte array*/ new byte[]();
return new ResponseEntity<byte[]>(myByteArray , headers, HttpStatus.OK);
I updated my bulkdownload method to use $window.open(...) instead of $http.get(...):
function bulkdownload(titles){
titles = titles || [];
if ( titles.length > 0 ) {
var url = '/query/bulkdownload?';
var len = titles.length;
for ( var ii = 0; ii < len; ii++ ) {
url = url + 'titles=' + titles[ii];
if ( ii < len-1 ) {
url = url + '&';
}
}
$window.open(url);
}
};
I have only tested this in IE11.

Save to Local File from Blob

I have a difficult question to you, which i'm struggling on for some time now.
I'm looking for a solution, where i can save a file to the users computer, without the local storage, because local storage has 5MB limit. I want the "Save to file"-dialog, but the data i want to save is only available in javascript and i would like to prevent sending the data back to the server and then send it again.
The use-case is, that the service im working on is saving compressed and encrypted chunks of the users data, so the server has no knowledge whats in those chunks and by sending the data back to the server, this would cause 4 times traffic and the server is receiving the unencrypted data, which would render the whole encryption useless.
I found a javascript function to save the data to the users computer with the "Save to file"-dialog, but the work on this has been discontinued and isnt fully supported. It's this: http://www.w3.org/TR/file-writer-api/
So since i have no window.saveAs, what is the way to save data from a Blob-object without sending everything to the server?
Would be great if i could get a hint, what to search for.
I know that this works, because MEGA is doing it, but i want my own solution :)
Your best option is to use a blob url (which is a special url that points to an object in the browser's memory) :
var myBlob = ...;
var blobUrl = URL.createObjectURL(myBlob);
Now you have the choice to simply redirect to this url (window.location.replace(blobUrl)), or to create a link to it. The second solution allows you to specify a default file name :
var link = document.createElement("a"); // Or maybe get it from the current document
link.href = blobUrl;
link.download = "aDefaultFileName.txt";
link.innerHTML = "Click here to download the file";
document.body.appendChild(link); // Or append it whereever you want
FileSaver.js implements saveAs for certain browsers that don't have it
https://github.com/eligrey/FileSaver.js
Tested with FileSaver.js 1.3.8 tested on Chromium 75 and Firefox 68, neither of which have saveAs.
The working principle seems to be to just create an <a element and click it with JavaScript oh the horrors of the web.
Here is a demo that save a blob generated with canvas.toBlob to your download folder with the chosen name mypng.png:
var canvas = document.getElementById("my-canvas");
var ctx = canvas.getContext("2d");
var pixel_size = 1;
function draw() {
console.log("draw");
for (x = 0; x < canvas.width; x += pixel_size) {
for (y = 0; y < canvas.height; y += pixel_size) {
var b = 0.5;
ctx.fillStyle =
"rgba(" +
(x / canvas.width) * 255 + "," +
(y / canvas.height) * 255 + "," +
b * 255 +
",255)"
;
ctx.fillRect(x, y, pixel_size, pixel_size);
}
}
canvas.toBlob(function(blob) {
saveAs(blob, 'mypng.png');
});
}
window.requestAnimationFrame(draw);
<canvas id="my-canvas" width="512" height="512" style="border:1px solid black;"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.8/FileSaver.min.js"></script>
Here is an animated version that downloads multiple images: Convert HTML5 Canvas Sequence to a Video File
See also:
how to save canvas as png image?
JavaScript: Create and save file
HERE is the direct way.
canvas.toBlob(function(blob){
console.log(typeof(blob)) //let you have 'blob' here
var blobUrl = URL.createObjectURL(blob);
var link = document.createElement("a"); // Or maybe get it from the current document
link.href = blobUrl;
link.download = "image.jpg";
link.innerHTML = "Click here to download the file";
document.body.appendChild(link); // Or append it whereever you want
document.querySelector('a').click() //can add an id to be specific if multiple anchor tag, and use #id
}, 'image/jpeg', 1); // JPEG at 100% quality
spent a while to come upto this solution, comment if this helps.
Thanks to Sebastien C's answer.
this node dependence was more utils fs-web;
npm i fs-web
Usage
import * as fs from 'fs-web';
async processFetch(url, file_path = 'cache-web') {
const fileName = `${file_path}/${url.split('/').reverse()[0]}`;
let cache_blob: Blob;
await fs.readString(fileName).then((blob) => {
cache_blob = blob;
}).catch(() => { });
if (!!cache_blob) {
this.prepareBlob(cache_blob);
console.log('FROM CACHE');
} else {
await fetch(url, {
headers: {},
}).then((response: any) => {
return response.blob();
}).then((blob: Blob) => {
fs.writeFile(fileName, blob).then(() => {
return fs.readString(fileName);
});
this.prepareBlob(blob);
});
}
}
From a file picker or input type=file file chooser, save the filename to local storage:
HTML:
<audio id="player1">Your browser does not support the audio element</audio>
JavaScript:
function picksinglefile() {
var fop = new Windows.Storage.Pickers.FileOpenPicker();
fop.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.musicLibrary;
fop.fileTypeFilter.replaceAll([".mp3", ".wav"]);
fop.pickSingleFileAsync().then(function (file) {
if (file) {
// save the file name to local storage
localStorage.setItem("alarmname$", file.name.toString());
} else {
alert("Operation Cancelled");
}
});
}
Then later in your code, when you want to play the file you selected, use the following, which gets the file using only it's name from the music library. (In the UWP package manifest, set your 'Capabilites' to include 'Music Library'.)
var l = Windows.Storage.KnownFolders.musicLibrary;
var f = localStorage.getItem("alarmname$").toString(); // retrieve file by name
l.getFileAsync(f).then(function (file) {
// storagefile file is available, create URL from it
var s = window.URL.createObjectURL(file);
var x = document.getElementById("player1");
x.setAttribute("src", s);
x.play();
});

Categories

Resources