Using window.URL.createObject in chrome intermittently failing when downloading multiple files - javascript

The crux of the question: I am consecutively downloading 2 files in javascript and chrome will intermittently switch between downloading one file or both. The setting to download multiple files automatically is activated.
The details:
For an internal app I am maintaining, there is a routine that retrieves two sets of base64 strings from an API. This app is only run in the latest version of google chrome but on both windows 8, 10 and the latest version of OSX.
The front end then decodes these strings into a data blob and then decodes the blobs into 2 csv's and downloads them.
The Web API I am using to download the files is the createObjectURL(), assuming the base64 is decoded correctly into a blob (which it is as this is used in multiple other instances) the function to download a file is as follows:
function downloadBlob (blob, filename, extension) {
var a = window.document.createElement('a');
a.href = window.URL.createObjectURL(blob);
a.download = filename + '.' + extension;
// Append anchor to body.
document.body.appendChild(a);
a.click();
// Remove anchor from body
document.body.removeChild(a);
}
Again this is used in multiple instances and has worked without a hitch.
So an AJAX request is made to the API to retrieve the two base64 strings, the success callback is essentially:
Function success (b64A, b64B) {
var csvA = b64toBlob(b64A, 'application/vnd.ms-excel');
downloadBlob(csvA, 'filename', 'csv');
var csvB = b64toBlob(b64B, 'application/vnd.ms-excel');
downloadBlob(csvB, 'filename', 'csv');
}
As you can see the downloads are executed consecutively. This has been tested across both chrome in windows 8 and chrome on OSX and both seem to intermittently download both files. Most of the time they only download the latter file.
I'm a bit confused as to why this was happening and was wondering if this is an issue with my code or some niche glitchy behaviour in chrome.

I was using HTML code and jquery to download multiple files but a few days ago i can download just the latest file.
I think now is blocked by Chrome.

I'm not sure if this is a bug in Chrome or intended behaviour however I managed to solve this by wrapping the second downloadBlob() in a setTimeout of 200ms:
Function success (b64A, b64B) {
var csvA = b64toBlob(b64A, 'application/vnd.ms-excel');
downloadBlob(csvA, 'filename', 'csv');
setTimeout(function() {
var csvB = b64toBlob(b64B, 'application/vnd.ms-excel');
downloadBlob(csvB, 'filename', 'csv');
}, 200);
}

Related

How do I open and display a base64 pdf from inside my Cordova App?

I am creating an App for Android using Cordova, and I would like to open and display a file (PDF or image) that is served from the server as Base64-encoded binary data.
Of course I have read the multiple other posts on the subject that already exist on this website, but none of the proposed solutions have worked for me, more details below.
To be more precise, the server sends a JSON-file to the app, which among many other things contains a string consisting of the base64-encoded contents of a PDF file. I want to convert this data back into the represented PDF and display it to the user.
If this were a pure browser page, I would simply package my base64 data into a data-URL, attach this as the href of some anchor, and add a download-attribute. Optionally I could wrap all of my data into a blob and create an object url for that first.
In Cordova, this does not work. Clicking the <a> does nothing. Here is what I have attempted so far:
Using the file plugin, I can write the binary data to a file on the device. This works, and using a terminal I can see that the file was downloaded correctly, but into an app-private directory which I cannot access normally (e.g. through the file explorer).
Accessing the user's "downloads" folder is blocked by the file system
Using window.open with the file path as the first argument and "_system" as the target does nothing. There is no error but also nothing happens. Setting the target to "_blank" instead, I get an error saying ACCESS_DENIED.
Using cordova.InAppBrowser behaves the same was as window.open
With the plugin file-opener2 installed, the app will not compile, because the plugin is looking for an android4 toolchain, and I am building for android 9 and up
The plugin document-viewer (restricting to PDFs for the time being) suffers the same problem and does not compile.
Passing the data-URI to window.open (or cordova.InAppBrowser) directly loads for a very long time and eventually tells me that the desired page could not be loaded.
The PDF file I am using for testing is roughly 17kb after converting to base64. I know this is technically above the spec for how long data-URIs can be, but Chrome in the browser has no trouble with it whatsoever, and using a much shorter URI (only a few dozen bytes) produces the same behavior.
Ideally, what I would like to do, is download the file and then trigger the user's standard browser to open the file itself. That was, I would not have to deal with MIME types and also it would look exactly how the user expected from their own device.
Alternatively, if that doesn't work, I would be ok with downloading the file into a system-wide directory and prompting the user to open it themselves. This is not optimal, but I would be able to swallow that pill.
And lastly, if there is a plugin or some other solution that solves the problem amazingly, but for PDFs only, then I can also work out something else for images (e.g. embedding a new into my app and assigning the URI to that).
I would be thankful for any suggestion you might have on how to solve this problem. The code I use to download the file currently is shown below.
Thank you for your time.
var filePath = cordova.file.externalDataDirectory; // Note: documentsDirectory is set to "" by Cordova, so I cannot use that
var fileName = "someFileName.pdf";
var mime = "application/pdf";
var dataBlob = /* some blob containing the binary data for a PDF */
function writeFile(fileEntry, dataBlob) {
// Create a FileWriter object for our FileEntry.
// This code is taken directly from the cordova-plugin-file documentation
fileEntry.createWriter(function (fileWriter) {
fileWriter.onwriteend = function() {
console.log("Successful file write...");
readFile(fileEntry);
};
fileWriter.onerror = function (e) {
console.log("Failed file write: " + e.toString());
};
fileWriter.write(dataBlob);
});
}
window.resolveLocalFileSystemURL(
filePath,
function onResolveSuccess (dirEntry) {
dirEntry.getFile(
fileName,
{ create: true },
function onGetFileSuccess (file) (
writeFile(file, dataBlob);
// At this point, the file has been downloaded successfully
window.open(file.toURL(), "_system"); // This line does nothing, and I don't understand why.
}
);
}
);
I managed to solve the problem.
As per the documentation of the file-opener2 plugin, you need to also add the androidx-adapter plugin to correct for the outdated (android 4) packages. With the plugins file, file-opener2 and androidx-adapter installed, the complete code is the following:
var filePath = cordova.file.externalDataDirectory; // Note: documentsDirectory is set to "" by Cordova, so I cannot use that
var fileName = "someFileName.pdf";
var mime = "application/pdf";
var dataBlob = /* some blob containing the binary data for a PDF */
function writeFile(fileEntry, dataBlob) {
// Create a FileWriter object for our FileEntry.
// This code is taken directly from the cordova-plugin-file documentation
fileEntry.createWriter(function (fileWriter) {
fileWriter.onwriteend = function() {
console.log("Successful file write...");
readFile(fileEntry);
};
fileWriter.onerror = function (e) {
console.log("Failed file write: " + e.toString());
};
fileWriter.write(dataBlob);
});
}
window.resolveLocalFileSystemURL(
filePath,
function onResolveSuccess (dirEntry) {
dirEntry.getFile(
fileName,
{ create: true },
function onGetFileSuccess (file) (
writeFile(file, dataBlob);
// At this point, the file has been downloaded successfully
cordova.plugins.fileOpener2.open(
    filepath + filename,
    mime,
    {
     error : function(){ },
success : function(){ }
    }
);
}
);
}
);

JavaScript: How to create and save text file [duplicate]

This question already has answers here:
How to create a file in memory for user to download, but not through server?
(22 answers)
Closed 2 years ago.
I have data that I want to write to a file, and open a file dialog for the user to choose where to save the file. It would be great if it worked in all browsers, but it has to work in Chrome. I want to do this all client-side.
Basically I want to know what to put in this function:
saveFile: function(data)
{
}
Where the function takes in data, has the user select a location to save the file, and creates a file in that location with that data.
Using HTML is fine too, if that helps.
A very minor improvement of the code by Awesomeness01 (no need for anchor tag) with addition as suggested by trueimage (support for IE):
// Function to download data to a file
function download(data, filename, type) {
var file = new Blob([data], {type: type});
if (window.navigator.msSaveOrOpenBlob) // IE10+
window.navigator.msSaveOrOpenBlob(file, filename);
else { // Others
var a = document.createElement("a"),
url = URL.createObjectURL(file);
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
setTimeout(function() {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
}
}
Tested to be working properly in Chrome, FireFox and IE10.
In Safari, the data gets opened in a new tab and one would have to manually save this file.
function download(text, name, type) {
var a = document.getElementById("a");
var file = new Blob([text], {type: type});
a.href = URL.createObjectURL(file);
a.download = name;
}
click here to download your file
<button onclick="download('file text', 'myfilename.txt', 'text/plain')">Create file</button>
And you would then download the file by putting the download attribute on the anchor tag.
The reason I like this better than creating a data url is that you don't have to make a big long url, you can just generate a temporary url.
This project on github looks promising:
https://github.com/eligrey/FileSaver.js
FileSaver.js implements the W3C saveAs() FileSaver interface in
browsers that do not natively support it.
Also have a look at the demo here:
http://eligrey.com/demos/FileSaver.js/
Choosing the location to save the file before creating it is not possible. But it is possible, at least in Chrome, to generate files using just JavaScript. Here is an old example of mine of creating a CSV file. The user will be prompted to download it. This, unfortunately, does not work well in other browsers, especially IE.
<!DOCTYPE html>
<html>
<head>
<title>JS CSV</title>
</head>
<body>
<button id="b">export to CSV</button>
<script type="text/javascript">
function exportToCsv() {
var myCsv = "Col1,Col2,Col3\nval1,val2,val3";
window.open('data:text/csv;charset=utf-8,' + escape(myCsv));
}
var button = document.getElementById('b');
button.addEventListener('click', exportToCsv);
</script>
</body>
</html>
For latest browser, like Chrome, you can use the File API as in this tutorial:
window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
window.requestFileSystem(window.PERSISTENT, 5*1024*1024 /*5MB*/, saveFile, errorHandler);
function SaveBlobAs(blob, file_name) {
if (typeof navigator.msSaveBlob == "function")
return navigator.msSaveBlob(blob, file_name);
var saver = document.createElementNS("http://www.w3.org/1999/xhtml", "a");
var blobURL = saver.href = URL.createObjectURL(blob),
body = document.body;
saver.download = file_name;
body.appendChild(saver);
saver.dispatchEvent(new MouseEvent("click"));
body.removeChild(saver);
URL.revokeObjectURL(blobURL);
}
Tried this in the console, and it works.
var aFileParts = ['<a id="a"><b id="b">hey!</b></a>'];
var oMyBlob = new Blob(aFileParts, {type : 'text/html'}); // the blob
window.open(URL.createObjectURL(oMyBlob));
You cannot do this purely in Javascript. Javascript running on browsers does not have enough permission yet (there have been proposals) due to security reasons.
Instead, I would recommend using Downloadify:
A tiny javascript + Flash library that enables the creation and download of text files without server interaction.
You can see a simple demo here where you supply the content and can test out saving/cancelling/error handling functionality.
For Chrome and Firefox, I have been using a purely JavaScript method.
(My application cannot make use of a package such as Blob.js because it is served from a special engine: a DSP with a WWWeb server crammed in and little room for anything at all.)
function FileSave(sourceText, fileIdentity) {
var workElement = document.createElement("a");
if ('download' in workElement) {
workElement.href = "data:" + 'text/plain' + "charset=utf-8," + escape(sourceText);
workElement.setAttribute("download", fileIdentity);
document.body.appendChild(workElement);
var eventMouse = document.createEvent("MouseEvents");
eventMouse.initMouseEvent("click", true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
workElement.dispatchEvent(eventMouse);
document.body.removeChild(workElement);
} else throw 'File saving not supported for this browser';
}
Notes, caveats, and weasel-words:
I have had success with this code in both Chrome and Firefox clients running in Linux (Maipo) and Windows (7 and 10) environments.
However, if sourceText is larger than a MB, Chrome sometimes (only sometimes) gets stuck in its own download without any failure indication; Firefox, so far, has not exhibited this behavior. The cause might be some blob limitation in Chrome. Frankly, I just don't know; if anybody has any ideas how to correct (or at least detect), please post. If the download anomaly occurs, when the Chrome browser is closed, it generates a diagnostic such as
This code is not compatible with Edge or Internet Explorer; I have not tried Opera or Safari.
StreamSaver is an alternative to save very large files without having to keep all data in the memory.In fact it emulates everything the server dose when saving a file but all client side with service worker.
You can either get the writer and manually write Uint8Array's to it or pipe a binary readableStream to the writable stream
There is a few example showcasing:
How to save multiple files as a zip
piping a readableStream from eg Response or blob.stream() to StreamSaver
manually writing to the writable stream as you type something
or recoding a video/audio
Here is an example in it's simplest form:
const fileStream = streamSaver.createWriteStream('filename.txt')
new Response('StreamSaver is awesome').body
.pipeTo(fileStream)
.then(success, error)
If you want to save a blob you would just convert that to a readableStream
new Response(blob).body.pipeTo(...) // response hack
blob.stream().pipeTo(...) // feature reference
Javascript has a FileSystem API. If you can deal with having the feature only work in Chrome, a good starting point would be: http://www.html5rocks.com/en/tutorials/file/filesystem/.

Is there anything similar to Chrome's fileSystem API for Firefox addons?

I have made a Chrome app that relies heavily on Chrome's fileSystem API to record and save video streams from various websites. Since the stream data is processed in javascript before being saved, simply downloading the streams doesn't work.
Now I am considering making a Firefox version...
I know that Firefox has a sandboxed file system API, but as far as I know, it is not possible to save the files to the physical file system.
Only option I can see is creating a blob from the sandboxed file system and download that blob.
I have actually two questions:
Are there any options I have missed to create and save files directly in the physical file system from Firefox addons?
Even if I have to rely on the sandboxed file system, is it possible to open files in append mode, ie. to append data to existing files?
Yes to your first question: there is the io/file API. Opening a file returns a stream (io/bytestream). Examples from the docs
function readBinaryDataFromFile (filename) {
var fileIO = require("sdk/io/file");
var data = null;
if (fileIO.exists(filename)) {
var ByteReader = fileIO.open(filename, "rb");
if (!ByteReader.closed) {
data = ByteReader.read();
ByteReader.close();
}
}
return data;
}
function writeBinaryDataToFile(data, filename) {
var fileIO = require("sdk/io/file");
var ByteWriter = fileIO.open(filename, "wb");
if (!ByteWriter.closed) {
ByteWriter.write(data);
ByteWriter.close();
}
}

Multiple file upload doesn't work in Safari 6.1 above, unless web inspector open

So I have a file upload site, which I develop using HTML5 chunking ability to upload multiple files. it works fine on Chrome, Firefox, IE (basicly browser with HTML5 capability) as well as Safari, but recently I test it out, Safari 6.0.5 works fine, but on Safari 6.1, If I upload multiple files, some files are 0 bytes. I'm not sure what happened.
When I tested, I upload about 70 files totalling 200MB, and each file is between 5-8MBish.. so there's no chunking happenening.. but when I check on the server, most file are 0 bytes (like it never get uploaded) except a few files (probably 3-5 files)
Is there any difference between Safari 6.0.5 and below, with Safari 6.1?
My code is basicly in a nutshell:
Javascript will chunk each file if it's bigger than 10MB/file, if not it will just upload as is.
then PHP will handle the upload (standard file upload style move_uploaded_file()).
function uploadFile(file_blob_chunk, file_name, file_part, total_file_chunk, file_id) {
//create a progress bar based on file id (check if it's the 0 part, otherwise there will be multiple bar for same file)
if(file_part == 0) {
progressBar(file_id);
}
//ajax call for creating multipart data form
fd = new FormData();
fd.append("file_for_upload", file_blob_chunk);
fd.append("file_id", file_id);
fd.append("file_name", file_name);
fd.append("file_part", file_part);
xhr = new XMLHttpRequest();
xhr.fid = file_id;
xhr.fid_name = file_name;
xhr.fid_part = file_part;
xhr.fid_total_chunk = total_file_chunk;
xhr.upload.fid = file_id;
xhr.upload.fid_part = file_part;
xhr.upload.fid_total_chunk = total_file_chunk;
xhr.open("POST", "datas/upload/" + file_name + '/' + file_part, true);
xhr.send(fd);
code wise it's something like that...
any idea what's wrong with safari 6.1?
I check the tmp folder, the tmp file during upload is 0 bytes..
NOTE: Safari 6.1+, If web inspector on, every file is uploaded correctly, if it's off, out of 10 files, only 3 got uploaded the rest is 0 bytes. what cause this difference?
There are a lot of threads discussing the same problem:
file input size issue in safari for multiple file selection
https://github.com/moxiecode/plupload/issues/363
Any workarounds for the Safari HTML5 multiple file upload bug?
And the only workaround for this problem is to disable multiple upload for Safari.

Download blobs locally using Safari

I'm trying to find a cross browser way to store data locally in HTML5. I have generated a chunk of data in a Blob (see MDN). Now I want to move this Blob to the actual filesystem and save it locally. I've found the following ways to achieve this;
Use the <a download> attribute. This works only in Chrome currently.
Microsoft introduces a saveAs function in IE 10 which will achieve this.
Open the Blob URL in the browser and save it that way.
None of these seems to work in Safari though. While (1) works in Chrome, (2) in IE and (3) in Firefox no one works in Safari 6. The download attribute is not yet implemented and when trying to open a blob using the URL Safari complains that URLs starting with blob: are not valid URLs.
There is a good script that encapsulates (1) and (3) called FileSaver.js but that does not work using the latest Safari version.
Is there a way to save Blobs locally in a cross browser fashion?
FileSaver.js has beed updated recently and it works on IE10, Safari5+ etc.
See: https://github.com/eligrey/FileSaver.js/#supported-browsers
The file name sucks, but this works for me in Safari 8:
window.open('data:attachment/csv;charset=utf-8,' + encodeURI(csvString));
UPDATE: No longer working in Safari 9.x
The only solution that I have come up with is making a data: url instead. For me this looks like:
window.open("data:image/svg+xml," + encodeURIComponent(currentSVGString));
Here data is the array buffer data coming from response while making http rest call in js. This works in safari, however there might me some issue in filename as it comes to be untitled.
var binary = '';
var bytes = new Uint8Array(data);
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
var base64 = 'data:' + contentType + ';base64,' + window.btoa(binary);
var uri = encodeURI(base64);
var anchor = document.createElement('a');
document.body.appendChild(anchor);
anchor.href = uri;
anchor.download = fileName;
anchor.click();
document.body.removeChild(anchor);
Have you read this article? http://updates.html5rocks.com/2012/06/Don-t-Build-Blobs-Construct-Them
Relating to http://caniuse.com/#search=blob, blobs are possible to use in safari.
You should consturct a servlet which delivers the blob via standard http:// url, so you can avoid using blob: url. Just make a request to that url and build your blob.
Afterwards you can save it in your filesystem or local storage.
The download attribute is supported since ~safari 10.1, so currently this is the way to go.
This is the only thing that worked for me on safari.
var newWindow = window.open();
const blobPDF = await renderMapPDF(); // Your async stuff goes here
if (!newWindow) throw new Error('Window could not be opened.');
newWindow.location = URL.createObjectURL(blobPDF);

Categories

Resources