I have a javascript app that sends ajax POST requests to a certain URL. Response might be a JSON string or it might be a file (as an attachment). I can easily detect Content-Type and Content-Disposition in my ajax call, but once I detect that the response contains a file, how do I offer the client to download it? I've read a number of similar threads here but none of them provide the answer I'm looking for.
Please, please, please do not post answers suggesting that I shouldn't use ajax for this or that I should redirect the browser, because none of this is an option. Using a plain HTML form is also not an option. What I do need is to show a download dialog to the client. Can this be done and how?
Don't give up so quickly, because this can be done (in modern browsers) using parts of the FileAPI:
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.responseType = 'blob';
xhr.onload = function () {
if (this.status === 200) {
var blob = this.response;
var filename = "";
var disposition = xhr.getResponseHeader('Content-Disposition');
if (disposition && disposition.indexOf('attachment') !== -1) {
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) filename = matches[1].replace(/['"]/g, '');
}
if (typeof window.navigator.msSaveBlob !== 'undefined') {
// IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
window.navigator.msSaveBlob(blob, filename);
} else {
var URL = window.URL || window.webkitURL;
var downloadUrl = URL.createObjectURL(blob);
if (filename) {
// use HTML5 a[download] attribute to specify filename
var a = document.createElement("a");
// safari doesn't support this yet
if (typeof a.download === 'undefined') {
window.location.href = downloadUrl;
} else {
a.href = downloadUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
}
} else {
window.location.href = downloadUrl;
}
setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
}
}
};
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send($.param(params, true));
Or if using jQuery.ajax:
$.ajax({
type: "POST",
url: url,
data: params,
xhrFields: {
responseType: 'blob' // to avoid binary data being mangled on charset conversion
},
success: function(blob, status, xhr) {
// check for a filename
var filename = "";
var disposition = xhr.getResponseHeader('Content-Disposition');
if (disposition && disposition.indexOf('attachment') !== -1) {
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) filename = matches[1].replace(/['"]/g, '');
}
if (typeof window.navigator.msSaveBlob !== 'undefined') {
// IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
window.navigator.msSaveBlob(blob, filename);
} else {
var URL = window.URL || window.webkitURL;
var downloadUrl = URL.createObjectURL(blob);
if (filename) {
// use HTML5 a[download] attribute to specify filename
var a = document.createElement("a");
// safari doesn't support this yet
if (typeof a.download === 'undefined') {
window.location.href = downloadUrl;
} else {
a.href = downloadUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
}
} else {
window.location.href = downloadUrl;
}
setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
}
}
});
Create a form, use the POST method, submit the form - there's no need for an iframe. When the server page responds to the request, write a response header for the mime type of the file, and it will present a download dialog - I've done this a number of times.
You want content-type of application/download - just search for how to provide a download for whatever language you're using.
I faced the same issue and successfully solved it. My use-case is this.
"Post JSON data to the server and receive an excel file.
That excel file is created by the server and returned as a response to the client. Download that response as a file with custom name in browser"
$("#my-button").on("click", function(){
// Data to post
data = {
ids: [1, 2, 3, 4, 5]
};
// Use XMLHttpRequest instead of Jquery $ajax
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
var a;
if (xhttp.readyState === 4 && xhttp.status === 200) {
// Trick for making downloadable link
a = document.createElement('a');
a.href = window.URL.createObjectURL(xhttp.response);
// Give filename you wish to download
a.download = "test-file.xls";
a.style.display = 'none';
document.body.appendChild(a);
a.click();
}
};
// Post data to URL which handles post request
xhttp.open("POST", excelDownloadUrl);
xhttp.setRequestHeader("Content-Type", "application/json");
// You should set responseType as blob for binary responses
xhttp.responseType = 'blob';
xhttp.send(JSON.stringify(data));
});
The above snippet is just doing following
Posting an array as JSON to the server using XMLHttpRequest.
After fetching content as a blob(binary), we are creating a downloadable URL and attaching it to invisible "a" link then clicking it.
Here we need to carefully set few things at the server side. I set few headers in Python Django HttpResponse. You need to set them accordingly if you use other programming languages.
# In python django code
response = HttpResponse(file_content, content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
Since I download xls(excel) here, I adjusted contentType to above one. You need to set it according to your file type. You can use this technique to download any kind of files.
What server-side language are you using? In my app I can easily download a file from an AJAX call by setting the correct headers in PHP's response:
Setting headers server-side
header("HTTP/1.1 200 OK");
header("Pragma: public");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
// The optional second 'replace' parameter indicates whether the header
// should replace a previous similar header, or add a second header of
// the same type. By default it will replace, but if you pass in FALSE
// as the second argument you can force multiple headers of the same type.
header("Cache-Control: private", false);
header("Content-type: " . $mimeType);
// $strFileName is, of course, the filename of the file being downloaded.
// This won't have to be the same name as the actual file.
header("Content-Disposition: attachment; filename=\"{$strFileName}\"");
header("Content-Transfer-Encoding: binary");
header("Content-Length: " . mb_strlen($strFile));
// $strFile is a binary representation of the file that is being downloaded.
echo $strFile;
This will in fact 'redirect' the browser to this download page, but as #ahren alread said in his comment, it won't navigate away from the current page.
It's all about setting the correct headers so I'm sure you'll find a suitable solution for the server-side language you're using if it's not PHP.
Handling the response client side
Assuming you already know how to make an AJAX call, on the client side you execute an AJAX request to the server. The server then generates a link from where this file can be downloaded, e.g. the 'forward' URL where you want to point to.
For example, the server responds with:
{
status: 1, // ok
// unique one-time download token, not required of course
message: 'http://yourwebsite.com/getdownload/ska08912dsa'
}
When processing the response, you inject an iframe in your body and set the iframe's SRC to the URL you just received like this (using jQuery for the ease of this example):
$("body").append("<iframe src='" + data.message +
"' style='display: none;' ></iframe>");
If you've set the correct headers as shown above, the iframe will force a download dialog without navigating the browser away from the current page.
Note
Extra addition in relation to your question; I think it's best to always return JSON when requesting stuff with AJAX technology. After you've received the JSON response, you can then decide client-side what to do with it. Maybe, for example, later on you want the user to click a download link to the URL instead of forcing the download directly, in your current setup you would have to update both client and server-side to do so.
Here is how I got this working
https://stackoverflow.com/a/27563953/2845977
$.ajax({
url: '<URL_TO_FILE>',
success: function(data) {
var blob=new Blob([data]);
var link=document.createElement('a');
link.href=window.URL.createObjectURL(blob);
link.download="<FILENAME_TO_SAVE_WITH_EXTENSION>";
link.click();
}
});
Updated answer using download.js
$.ajax({
url: '<URL_TO_FILE>',
success: download.bind(true, "<FILENAME_TO_SAVE_WITH_EXTENSION>", "<FILE_MIME_TYPE>")
});
For those looking for a solution from an Angular perspective, this worked for me:
$http.post(
'url',
{},
{responseType: 'arraybuffer'}
).then(function (response) {
var headers = response.headers();
var blob = new Blob([response.data],{type:headers['content-type']});
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = "Filename";
link.click();
});
For those looking for a more modern approach, you can use the fetch API. The following code shows how to download a spreadsheet file.
fetch(url, {
body: JSON.stringify(data),
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8'
},
})
.then(response => response.blob())
.then(response => {
const blob = new Blob([response], {type: 'application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
const downloadUrl = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = downloadUrl;
a.download = "file.xlsx";
document.body.appendChild(a);
a.click();
})
I believe this approach to be much easier to understand than other XMLHttpRequest solutions. Also, it has a similar syntax to the jQuery approach, without the need to add any additional libraries.
Of course, I would advise checking to which browser you are developing, since this new approach won't work on IE. You can find the full browser compatibility list on the following [link][1].
Important: In this example I am sending a JSON request to a server listening on the given url. This url must be set, on my example I am assuming you know this part. Also, consider the headers needed for your request to work. Since I am sending a JSON, I must add the Content-Type header and set it to application/json; charset=utf-8, as to let the server know the type of request it will receive.
I see you've already found out a solution, however I just wanted to add some information which may help someone trying to achieve the same thing with big POST requests.
I had the same issue a couple of weeks ago, indeed it isn't possible to achieve a "clean" download through AJAX, the Filament Group created a jQuery plugin which works exactly how you've already found out, it is called jQuery File Download however there is a downside to this technique.
If you're sending big requests through AJAX (say files +1MB) it will negatively impact responsiveness. In slow Internet connections you'll have to wait a lot until the request is sent and also wait for the file to download. It isn't like an instant "click" => "popup" => "download start". It's more like "click" => "wait until data is sent" => "wait for response" => "download start" which makes it appear the file double its size because you'll have to wait for the request to be sent through AJAX and get it back as a downloadable file.
If you're working with small file sizes <1MB you won't notice this. But as I discovered in my own app, for bigger file sizes it is almost unbearable.
My app allow users to export images dynamically generated, these images are sent through POST requests in base64 format to the server (it is the only possible way), then processed and sent back to users in form of .png, .jpg files, base64 strings for images +1MB are huge, this force users to wait more than necessary for the file to start downloading. In slow Internet connections it can be really annoying.
My solution for this was to temporary write the file to the server, once it is ready, dynamically generate a link to the file in form of a button which changes between "Please wait..." and "Download" states and at the same time, print the base64 image in a preview popup window so users can "right-click" and save it. This makes all the waiting time more bearable for users, and also speed things up.
Update Sep 30, 2014:
Months have passed since I posted this, finally I've found a better approach to speed things up when working with big base64 strings. I now store base64 strings into the database (using longtext or longblog fields), then I pass its record ID through the jQuery File Download, finally on the download script file I query the database using this ID to pull the base64 string and pass it through the download function.
Download Script Example:
<?php
// Record ID
$downloadID = (int)$_POST['id'];
// Query Data (this example uses CodeIgniter)
$data = $CI->MyQueries->GetDownload( $downloadID );
// base64 tags are replaced by [removed], so we strip them out
$base64 = base64_decode( preg_replace('#\[removed\]#', '', $data[0]->image) );
// This example is for base64 images
$imgsize = getimagesize( $base64 );
// Set content headers
header('Content-Disposition: attachment; filename="my-file.png"');
header('Content-type: '.$imgsize['mime']);
// Force download
echo $base64;
?>
I know this is way beyond what the OP asked, however I felt it would be good to update my answer with my findings. When I was searching for solutions to my problem, I read lots of "Download from AJAX POST data" threads which didn't give me the answer I was looking for, I hope this information helps someone looking to achieve something like this.
Here is my solution using a temporary hidden form.
//Create an hidden form
var form = $('<form>', {'method': 'POST', 'action': this.href}).hide();
//Add params
var params = { ...your params... };
$.each(params, function (k, v) {
form.append($('<input>', {'type': 'hidden', 'name': k, 'value': v}));
});
//Make it part of the document and submit
$('body').append(form);
form.submit();
//Clean up
form.remove();
Note that I massively use JQuery but you can do the same with native JS.
I want to point out some difficulties that arise when using the technique in the accepted answer, i.e. using a form post:
You can't set headers on the request. If your authentication schema involves headers, a Json-Web-Token passed in the Authorization header, you'll have to find other way to send it, for example as a query parameter.
You can't really tell when the request has finished. Well, you can use a cookie that gets set on response, as done by jquery.fileDownload, but it's FAR from perfect. It won't work for concurrent requests and it will break if a response never arrives.
If the server responds with a error, the user will be redirected to the error page.
You can only use the content types supported by a form. Which means you can't use JSON.
I ended up using the method of saving the file on S3 and sending a pre-signed URL to get the file.
As others have stated, you can create and submit a form to download via a POST request. However, you don't have to do this manually.
One really simple library for doing exactly this is jquery.redirect. It provides an API similar to the standard jQuery.post method:
$.redirect(url, [values, [method, [target]]])
This is a 3 years old question but I had the same problem today. I looked your edited solution but I think that it can sacrifice the performance because it has to make a double request. So if anyone needs another solution that doesn't imply to call the service twice then this is the way I did it:
<form id="export-csv-form" method="POST" action="/the/path/to/file">
<input type="hidden" name="anyValueToPassTheServer" value="">
</form>
This form is just used to call the service and avoid to use a window.location(). After that you just simply have to make a form submit from jquery in order to call the service and get the file. It's pretty simple but this way you can make a download using a POST. I now that this could be easier if the service you're calling is a GET, but that's not my case.
I used this FileSaver.js. In my case with csv files, i did this (in coffescript):
$.ajax
url: "url-to-server"
data: "data-to-send"
success: (csvData)->
blob = new Blob([csvData], { type: 'text/csv' })
saveAs(blob, "filename.csv")
I think for most complicated case, the data must be processed properly. Under the hood FileSaver.js implement the same approach of the answer of Jonathan Amend.
see: http://www.henryalgus.com/reading-binary-files-using-jquery-ajax/
it'll return a blob as a response, which can then be put into filesaver
Here is my solution, gathered from different sources:
Server side implementation :
String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
// Set headers
response.setHeader("content-disposition", "attachment; filename =" + fileName);
response.setContentType(contentType);
// Copy file to output stream
ServletOutputStream servletOutputStream = response.getOutputStream();
try (InputStream inputStream = new FileInputStream(file)) {
IOUtils.copy(inputStream, servletOutputStream);
} finally {
servletOutputStream.flush();
Utils.closeQuitely(servletOutputStream);
fileToDownload = null;
}
Client side implementation (using jquery):
$.ajax({
type: 'POST',
contentType: 'application/json',
url: <download file url>,
data: JSON.stringify(postObject),
error: function(XMLHttpRequest, textStatus, errorThrown) {
alert(errorThrown);
},
success: function(message, textStatus, response) {
var header = response.getResponseHeader('Content-Disposition');
var fileName = header.split("=")[1];
var blob = new Blob([message]);
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = fileName;
link.click();
}
});
Below is my solution for downloading multiple files depending on some list which consists of some ids and looking up in database, files will be determined and ready for download - if those exist.
I am calling C# MVC action for each file using Ajax.
And Yes, like others said, it is possible to do it in jQuery Ajax.
I did it with Ajax success and I am always sending response 200.
So, this is the key:
success: function (data, textStatus, xhr) {
And this is my code:
var i = 0;
var max = 0;
function DownloadMultipleFiles() {
if ($(".dataTables_scrollBody>tr.selected").length > 0) {
var list = [];
showPreloader();
$(".dataTables_scrollBody>tr.selected").each(function (e) {
var element = $(this);
var orderid = element.data("orderid");
var iscustom = element.data("iscustom");
var orderlineid = element.data("orderlineid");
var folderPath = "";
var fileName = "";
list.push({ orderId: orderid, isCustomOrderLine: iscustom, orderLineId: orderlineid, folderPath: folderPath, fileName: fileName });
});
i = 0;
max = list.length;
DownloadFile(list);
}
}
Then calling:
function DownloadFile(list) {
$.ajax({
url: '#Url.Action("OpenFile","OrderLines")',
type: "post",
data: list[i],
xhrFields: {
responseType: 'blob'
},
beforeSend: function (xhr) {
xhr.setRequestHeader("RequestVerificationToken",
$('input:hidden[name="__RequestVerificationToken"]').val());
},
success: function (data, textStatus, xhr) {
// check for a filename
var filename = "";
var disposition = xhr.getResponseHeader('Content-Disposition');
if (disposition && disposition.indexOf('attachment') !== -1) {
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) filename = matches[1].replace(/['"]/g, '');
var a = document.createElement('a');
var url = window.URL.createObjectURL(data);
a.href = url;
a.download = filename;
document.body.append(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url);
}
else {
getErrorToastMessage("Production file for order line " + list[i].orderLineId + " does not exist");
}
i = i + 1;
if (i < max) {
DownloadFile(list);
}
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
},
complete: function () {
if(i===max)
hidePreloader();
}
});
}
C# MVC:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult OpenFile(OrderLineSimpleModel model)
{
byte[] file = null;
try
{
if (model != null)
{
//code for getting file from api - part is missing here as not important for this example
file = apiHandler.Get<byte[]>(downloadApiUrl, token);
var contentDispositionHeader = new System.Net.Mime.ContentDisposition
{
Inline = true,
FileName = fileName
};
// Response.Headers.Add("Content-Disposition", contentDispositionHeader.ToString() + "; attachment");
Response.Headers.Add("Content-Type", "application/pdf");
Response.Headers.Add("Content-Disposition", "attachment; filename=" + fileName);
Response.Headers.Add("Content-Transfer-Encoding", "binary");
Response.Headers.Add("Content-Length", file.Length.ToString());
}
}
catch (Exception ex)
{
this.logger.LogError(ex, "Error getting pdf", null);
return Ok();
}
return File(file, System.Net.Mime.MediaTypeNames.Application.Pdf);
}
As long as you return response 200, success in Ajax can work with it, you can check if file actually exist or not as the line below in this case would be false and you can inform user about that:
if (disposition && disposition.indexOf('attachment') !== -1) {
To get Jonathan Amends answer to work in Edge I made the following changes:
var blob = typeof File === 'function'
? new File([this.response], filename, { type: type })
: new Blob([this.response], { type: type });
to this
var f = typeof File+"";
var blob = f === 'function' && Modernizr.fileapi
? new File([this.response], filename, { type: type })
: new Blob([this.response], { type: type });
I would rather have posted this as a comment but I don't have enough reputation for that
there is another solution to download a web page in ajax. But I am referring to a page that must first be processed and then downloaded.
First you need to separate the page processing from the results download.
1) Only the page calculations are made in the ajax call.
$.post("CalculusPage.php", { calculusFunction: true, ID: 29, data1: "a", data2: "b" },
function(data, status)
{
if (status == "success")
{
/* 2) In the answer the page that uses the previous calculations is downloaded. For example, this can be a page that prints the results of a table calculated in the ajax call. */
window.location.href = DownloadPage.php+"?ID="+29;
}
}
);
// For example: in the CalculusPage.php
if ( !empty($_POST["calculusFunction"]) )
{
$ID = $_POST["ID"];
$query = "INSERT INTO ExamplePage (data1, data2) VALUES ('".$_POST["data1"]."', '".$_POST["data2"]."') WHERE id = ".$ID;
...
}
// For example: in the DownloadPage.php
$ID = $_GET["ID"];
$sede = "SELECT * FROM ExamplePage WHERE id = ".$ID;
...
$filename="Export_Data.xls";
header("Content-Type: application/vnd.ms-excel");
header("Content-Disposition: inline; filename=$filename");
...
I hope this solution can be useful for many, as it was for me.
If response is an Array Buffer, try this under onsuccess event in Ajax:
if (event.data instanceof ArrayBuffer) {
var binary = '';
var bytes = new Uint8Array(event.data);
for (var i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i])
}
$("#some_id").append("<li><img src=\"data:image/png;base64," + window.btoa(binary) + "\"/></span></li>");
return;
}
where event.data is response received in success function of xhr event.
I needed a similar solution to #alain-cruz's one, but in nuxt/vue with multiple downloads. I know browsers block multiple file downloads, and I also have API which returns a set of csv formatted data.I was going to use JSZip at first but I needed IE support so here is my solution. If anyone can help me improve this that would be great, but it's working for me so far.
API returns:
data : {
body: {
fileOne: ""col1", "col2", "datarow1.1", "datarow1.2"...so on",
fileTwo: ""col1", "col2"..."
}
}
page.vue:
<template>
<b-link #click.prevent="handleFileExport">Export<b-link>
</template>
export default = {
data() {
return {
fileNames: ['fileOne', 'fileTwo'],
}
},
computed: {
...mapState({
fileOne: (state) => state.exportFile.fileOne,
fileTwo: (state) => state.exportFile.fileTwo,
}),
},
method: {
handleExport() {
//exportFileAction in store/exportFile needs to return promise
this.$store.dispatch('exportFile/exportFileAction', paramsToSend)
.then(async (response) => {
const downloadPrep = this.fileNames.map(async (fileName) => {
// using lodash to get computed data by the file name
const currentData = await _.get(this, `${fileName}`);
const currentFileName = fileName;
return { currentData, currentFileName };
});
const response = await Promise.all(downloadPrep);
return response;
})
.then(async (data) => {
data.forEach(({ currentData, currentFileName }) => {
this.forceFileDownload(currentData, currentFileName);
});
})
.catch(console.error);
},
forceFileDownload(data, fileName) {
const url = window.URL
.createObjectURL(new Blob([data], { type: 'text/csv;charset=utf-8;' }));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', `${fileName}.csv`);
document.body.appendChild(link);
link.click();
},
}
I used Naren Yellavula's solution and got it working with few changes to the script, after trying several other solutions using jquery. But, jquery will not download a zip file properly. I can't unzip the file after download.
In my use case, I have to upload a zip file, which is unzipped in the Servlet, files are processed and zipped again before the zip file is downloaded to the client. This is what you need to do on client side.
$('#fileUpBtn').click(function (e){
e.preventDefault();
var file = $('#fileUpload')[0].files[0];
var formdata = new FormData();
formdata.append('file', file);
// Use XMLHttpRequest instead of Jquery $ajax to download zip files
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (xhttp.readyState === 4 && xhttp.status === 200) {
var a = document.createElement('a');
a.href = window.URL.createObjectURL(xhttp.response);
a.download = "modified_" + file.name;
a.style.display = 'none';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(a.href);
}
};
xhttp.open("POST", "<URL to Servlet>", true);
xhttp.responseType = 'blob';
xhttp.send(formdata);
});
<div class="form-group">
<label id="fileUpLabel" for="fileUpload"></label>
<input type="file" class="form-control" id="fileUpload" name="file" accept="" required/>
</div>
<button class="btn" type="submit" id="fileUpBtn"></button>
Related
I found a few questions related to the same but the answer didn't work for me so posting the same here.
I have some XML content which has been created programmatically and displayed in Textarea. I need an option to export or download the same to the local system. So I am using the POST request to send these data to NODE.JS where I am creating my XML file and trying to download it. I know if I am using the GET it would work directly but with POST it's failing. Is there a way I can do this?
Here is the code I have:
Angularjs POST request:
I have a button when clicked I am passing all the XML data to my NODE.js function:
$scope.ExportXML = function(XMLContent){
var XMLContent = XMLContent;
$http({
url : "/ExportData",
method : "POST",
data : XMLContent
}).success(function(data, status, headers, config) {
console.log(data);
console.log("Data Exported");
window.location.assign(data);
$window.open(data);
}).error(function(error, status, headers, config) {
console.log('ERROR: could not download file');
console.log(error)
});
}
My Node.js function which would create the XML file with the data:
const fs = require('fs');
const path = require('path');
const reqPath = path.join(__dirname, '../');
exports.exportfile = function(req,res)
{
var ContentData = req.body;
var FileName = "XMLDATA.xml";
fs.appendFile(FileName, ContentData, function (err)
{
const FilePath = reqPath+FileName;
res.download(FilePath);
})
}
As you can see from the success function of the ANGULARJS I tried a couple of things but none worked. I tried sending back the path of the file using the callback(FilePath); and then I am trying to download the file using the $window.open(data); but I am getting the following error Not allowed to load local resource:.
Can someone please help me with this?
After a bit of searching I was able to do it. Rather than sending the DATA to NODE.JS using the POST, I tried creating the file within my AngularjS function and downloaded it from there itself. if in case anyone is looking for the solution here it is:
//Export the contents of XML to text file
$scope.ExportXML = function(XMLContent){
var filename = 'HelloWorld.xml'
var blob = new Blob([XMLContent], {type: "text/xml"});
if (window.navigator && window.navigator.msSaveOrOpenBlob)
{
window.navigator.msSaveOrOpenBlob(blob, filename);
}
else
{
var e = document.createEvent('MouseEvents'),
a = document.createElement('a');
a.download = filename;
a.href = window.URL.createObjectURL(blob);
a.dataset.downloadurl = ['text/json', a.download, a.href].join(':');
e.initEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
a.dispatchEvent(e);
}
}
I have set up in an SPA application the ability to send files to Azure Blob Storage.
To do this I used XMLHttpRequest and FormData (my users are on computers managed by my company and all have access to HTML5).
In order to manage security, the sending of each file is preceded by a call to a method of a Web Api to obtain the shared access signature.
I forward the Content-Type of the file as well as other information to headers.
Everything happens for the better, the files are correctly sent and saved in Azure Blob Storage, but during the transfer, the image files seem to be "altered".
They are well present, I can download them and read them after the download, but I can not open them directly from an img tag.
On the other hand if I send the same image file via Microsoft Azure Storage Explorer, there is no problem, the image is well recognized in the img tag.
However, in one case as in the other, the content-type is marked as "image / jpeg". The only noticeable difference is that the MD5 is not the same between these 2 mailings while it is the same file of origin.
From my findings it seems that there is text added to the beginning and the end of the file when sending via XMLHttpRequest.
I explain my code so that you can guide me:
Note 1 : I use typescript (but a javascript solution will suit me) and Promise.
Note 2 : I have resolve all the CORS problems.
Note 3 : I'm using Azure Storage Emulator, but i try with the normal Azure service and the problem is the same.
Here is the text added in the image in Chrome:
------WebKitFormBoundaryKj5cK88faAwJd4av
Content-Disposition: form-data; name="file1"; filename="test.jpg"
Content-Type: image/jpeg
[image content]
------WebKitFormBoundaryKj5cK88faAwJd4av--
My Web Api :
[Route(#"api/Storage/FileSas/Customers/{id:int}")]
public async Task<IHttpActionResult> GetFileSas(int id, string fileName, long? fileSize = 0, string contentType = null)
{
if (string.IsNullOrWhiteSpace(fileName))
this.ModelState.AddModelError("fileName", "File name i");
if (!fileSize.HasValue || fileSize.Value > maxFileSize)
this.ModelState.AddModelError("fileSize", "File size exceeded");
if (!this.ModelState.IsValid)
return BadRequest(this.ModelState);
var serverUrl = ConfigurationManager.AppSettings[SERVER_URL];
var container = ConfigurationManager.AppSettings[CONTAINER_NAME];
SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy()
{
Permissions = SharedAccessBlobPermissions.Write,
SharedAccessStartTime = DateTime.UtcNow.AddMinutes(-60),
SharedAccessExpiryTime = DateTime.UtcNow.AddMinutes(60),
};
CloudBlockBlob blobFile = blobContainer.GetBlockBlobReference(Path.Combine("customers", id.ToString(), fileName));
var exists = await blobFile.ExistsAsync();
if (exists)
{
await blobFile.SnapshotAsync();
}
var signature = blobFile.GetSharedAccessSignature(policy);
return Content<string>(HttpStatusCode.Created, Path.Combine(serverUrl, container, blobFile.Name + signature));
}
My TypeScript file :
context.Storage.getFileSas(customerId, file)
.then((response: Interfaces.Result<string>) => {
let sasUrl = response.Data;
let formData = new FormData();
formData.append("file1", file, file.name);
var xhr = new XMLHttpRequest();
xhr.upload.onprogress = (event) => {
if (event.total > 0)
this.Progress(event.loaded * 100 / event.total);
};
xhr.onloadstart = function (e) {
}
xhr.onloadend = (e) => {
this.Progress(0);
}
xhr.open("PUT", sasUrl, true);
xhr.setRequestHeader('x-ms-blob-type', 'BlockBlob');
xhr.setRequestHeader('Content-Type', file.type);
xhr.setRequestHeader('x-ms-blob-content-type', file.type);
xhr.setRequestHeader('x-ms-version', "2016-05-31");
xhr.setRequestHeader('x-ms-meta-CustomerId', customerId);
xhr.setRequestHeader('x-ms-meta-UserId', context.User.User.Id.toString());
xhr.setRequestHeader('x-ms-meta-UserName', context.User.User.Name);
xhr.send(formData);
})
})).catch((error) => {
console.log(error);
});
File come from here :
let fileInputElement1: HTMLInputElement = <HTMLInputElement>document.getElementById("file1");
let file = fileInputElement1.files[0];
My HTML part : (i'm using knockout)
<form method="put" target="_blank" enctype="multipart/form-data">
<input type="file" name="name" value="" id="file1" />
<button data-bind="click:send"> Send</button>
</form>
If someone have an idea ? ...
Thank's in advance.
PS : sasUrl is like this : http://127.0.0.1:10000/devstoreaccount1/customers/65143/test.jpg?sv=2016-05-31&sr=b&sig=s0671%2BLvCZTqyNfhlCthZW8KftjKyIMAlOT1nbsnlng%3D&st=2017-03-05T11%3A38%3A22Z&se=2017-03-06T12%3A38%3A22Z&sp=r&rsct=image%2Fjpeg
Thank's to Gaurav Mantri, he point me to the right, here my modifications (only because i use typescript) :
context.Storage.getFileSas(customerId, file)
.then((response: Interfaces.Result<string>) => {
let sasUrl = response.Data;
var xhr = new XMLHttpRequest();
xhr.upload.onprogress = (event) => {
if (event.total > 0)
this.Progress(event.loaded * 100 / event.total);
};
xhr.onloadstart = function (e) {
}
xhr.onloadend = (e) => {
this.Progress(0);
}
let reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onloadend = (event) => {
let target = <FileReader>event.target;
if (target.readyState == reader.DONE) {
var requestData = new Uint8Array(target.result);
xhr.open("PUT", sasUrl, true);
xhr.responseType = "blob";
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.setRequestHeader('X-File-Name', file.name);
xhr.setRequestHeader('x-ms-blob-type', 'BlockBlob');
xhr.setRequestHeader('Content-Type', file.type || 'application/octet-stream');
xhr.setRequestHeader('x-ms-blob-content-type', file.type || 'application/octet-stream');
xhr.setRequestHeader('x-ms-version', "2016-05-31");
xhr.setRequestHeader('x-ms-meta-CustomerId', customerId);
xhr.setRequestHeader('x-ms-meta-UserId', context.User.Id.toString());
xhr.setRequestHeader('x-ms-meta-UserName', context.User.Name);
xhr.send(requestData);
}
}
})
})).catch((error) => {
console.log(error);
});
Now i'll start to write a Promise to embedded this fonctionality.
PS : i didn't find the way to mark the Gaurav Mantri as answer so i create mine.
PS 2 : I'll like to put some +1 to Gaurav Mantri for the help... but i can't :/
I am trying to upload files directly to dropbox [from a browser / web application], The "uploadFile" function on the code API needs the file to be uploaded available on the server, this puts me in trouble, because I do not want any files to be uploaded to my server and from there to dropbox.
$f = fopen("test.jpg", "rb"); // requires file on server
$result = $dbxClient->uploadFile("test.jpg", dbx\WriteMode::add(), $f);
fclose($f);
Tried out this https://github.com/dropbox/dropbox-js disappointed to say that there is no clear documentation, many of the links on the documentation part is broken.
I need the files to be uploaded to my account and the clients need not login to dropbox.
Any pointers would be really appreciated. looking for Ajax / JavaScript methods.
Update
I have tried the following, but no response from Dropbox
HTML
<input type="file" name="file" id="file" onchange="doUpload(event)">
JavaScript
var doUpload = function(event){
var input = event.target;
var reader = new FileReader();
reader.onload = function(){
var arrayBuffer = reader.result;
$.ajax({
url: "https://api-content.dropbox.com/1/files_put/auto/uploads/" + input.files[0].name,
headers: {
Authorization: 'Bearer ' + MyAccessToken,
contentLength: file.size
},
crossDomain: true,
crossOrigin: true,
type: 'PUT',
contentType: input.files[0].type,
data: arrayBuffer,
dataType: 'json',
processData: false,
success : function(result) {
$('#uploadResults').html(result);
}
});
}
reader.readAsArrayBuffer(input.files[0]);
}
Dropbox just posted a blog with instructions on how to do this. You can find it at https://blogs.dropbox.com/developers/2016/03/how-formio-uses-dropbox-as-a-file-backend-for-javascript-apps/ (Full disclosure, I wrote the blog post.)
Here is how to upload a file.
/**
* Two variables should already be set.
* dropboxToken = OAuth token received then signing in with OAuth.
* file = file object selected in the file widget.
*/
var xhr = new XMLHttpRequest();
xhr.upload.onprogress = function(evt) {
var percentComplete = parseInt(100.0 * evt.loaded / evt.total);
// Upload in progress. Do something here with the percent complete.
};
xhr.onload = function() {
if (xhr.status === 200) {
var fileInfo = JSON.parse(xhr.response);
// Upload succeeded. Do something here with the file info.
}
else {
var errorMessage = xhr.response || 'Unable to upload file';
// Upload failed. Do something here with the error.
}
};
xhr.open('POST', 'https://content.dropboxapi.com/2/files/upload');
xhr.setRequestHeader('Authorization', 'Bearer ' + dropboxToken);
xhr.setRequestHeader('Content-Type', 'application/octet-stream');
xhr.setRequestHeader('Dropbox-API-Arg', JSON.stringify({
path: '/' + file.name,
mode: 'add',
autorename: true,
mute: false
}));
xhr.send(file);
Then to download a file from dropbox do this.
var downloadFile = function(evt, file) {
evt.preventDefault();
var xhr = new XMLHttpRequest();
xhr.responseType = 'arraybuffer';
xhr.onload = function() {
if (xhr.status === 200) {
var blob = new Blob([xhr.response], {type: ’application/octet-stream’});
FileSaver.saveAs(blob, file.name, true);
}
else {
var errorMessage = xhr.response || 'Unable to download file';
// Upload failed. Do something here with the error.
}
};
xhr.open('POST', 'https://content.dropboxapi.com/2/files/download');
xhr.setRequestHeader('Authorization', 'Bearer ' + dropboxToken);
xhr.setRequestHeader('Dropbox-API-Arg', JSON.stringify({
path: file.path_lower
}));
xhr.send();
}
FileSaver and Blob will not work on older browsers so you could add a workaround to them.
As other answers have noted, each session uploading or downloading the file will need to have access to a dropbox token. Sending someone else's token to a user is a security issue since having the token will give them complete control over the dropbox account. The only way to make this work is to have each person authenticate with Dropbox and get their own token.
At Form.io we've implemented both the authentication and the upload/download into our platform. This makes it really easy to build web apps with dropbox as a backend for files.
"I need the files to be uploaded to my account and the clients need not login to dropbox."
Then you'll really need to do the upload server-side. To do it client side would mean sending the access token to the browser, at which point any user of your app could use that access token to do whatever they wanted with your account. (E.g. delete all the other files, upload their private DVD collection, etc.)
For security reasons, I would strongly recommend doing the upload server-side where you can keep the access token a secret.
The answers given so far don't utilize the Dropbox javascript SDK which I think would prob be the best way to go about it. Check out this link here:
https://github.com/dropbox/dropbox-sdk-js/blob/master/examples/javascript/upload/index.html
which provides an example which is ofc dependent on having downloaded the SDK. (Edit: after playing with SDK I realize that it creates a POST request similar to the accepted answer in this thread. However something the popular answer omits is the presence of an OPTIONS preflight call that the sdk makes prior to the actual POST)
I might also add that something that is not shown in the dropbox sdk examples is that you can upload a blob object to dropbox; this is useful for instance if you want to dynamically extract images from a canvas and upload them and don't want to upload something that has been selected from the file system via the file uploaded input.
Here is a brief example of the scenario I'm describing:
//code below after having included dropbox-sdk-js in your project.
//Dropbox is in scope!
var dbx = new Dropbox.Dropbox({ accessToken: ACCESS_TOKEN });
//numerous stack overflow examples on creating a blob from data uri
var blob = dataURIToBlob(canvas.toDataUrl());
//the path here is the path of the file as it will exist on dropbox.
//should be unique or you will get a 4xx error
dbx.filesUpload({path: `unq_filename.png`, contents: blob})
Many thanks to #smarx with his pointers I was able to reach the final solution.
Also I have added a few extra features like listening to upload progress so that the users can be showed with the upload progress percentage.
HTML
<input type="file" name="file" id="file" onchange="doUpload(event)">
JavaScript
var doUpload = function(event){
var input = event.target;
var reader = new FileReader();
reader.onload = function(){
var arrayBuffer = reader.result;
var arrayBufferView = new Uint8Array( arrayBuffer );
var blob = new Blob( [ arrayBufferView ], { type: input.files[0].type } );
var urlCreator = window.URL || window.webkitURL;
var imageUrl = urlCreator.createObjectURL( blob );
$.ajax({
url: "https://api-content.dropbox.com/1/files_put/auto/YourDirectory/" + input.files[0].name,
headers: {
'Authorization':'Bearer ' +YourToken,
'Content-Length':input.files[0].size
},
crossDomain: true,
crossOrigin: true,
type: 'PUT',
contentType: input.files[0].type,
data: arrayBuffer,
dataType: 'json',
processData: false,
xhr: function()
{
var xhr = new window.XMLHttpRequest();
//Upload progress, litsens to the upload progress
//and get the upload status
xhr.upload.addEventListener("progress", function(evt){
if (evt.lengthComputable) {
var percentComplete = parseInt( parseFloat(evt.loaded / evt.total) * 100);
//Do something with upload progress
$('#uploadProgress').html(percentComplete);
$('#uploadProgressBar').css('width',percentComplete+'%');
}
}, false);
},
beforeSend: function(){
// Things you do before sending the file
// like showing the loader GIF
},
success : function(result) {
// Display the results from dropbox after upload
// Other stuff on complete
},
});
}
reader.readAsArrayBuffer(input.files[0]);
}
U have used the PUT method as our only objective is to upload files,As per my studies on various resources ( StackOverflow and zacharyvoase ) A put method can stream large files, also its desigend to put files on a specified URI , if file exist the file must be replaced. A PUT method cannot be moved to a different URL other than the URL Specified.
The Risk
You are at risk by using access token at client side, there needs to be high security measures to mask the token. But modern Web dev tools like Browser consoles , Firebug etc can monitor your server requests and can see your access token.
upload.html
Upload
upload.js
$('#form_wizard_1 .button-submit').click(function () {
var ACCESS_TOKEN ="Your token get from dropbox";
var dbx = new Dropbox({ accessToken: ACCESS_TOKEN });
var fileInput = document.getElementById('files1');
var file = fileInput.files[0];
res=dbx.filesUpload({path: '/' + file.name, contents: file})
.then(function(response) {
var results = document.getElementById('results');
results.appendChild(document.createTextNode('File uploaded!'));
res=response;
console.log(response);
})
.catch(function(error) {
console.error(error);
});
}
I am attempting to send the user an alert when an empty file is downloaded (the server sends back the number 0 when this is the case:
$http.get(service)
.success(function (response) {
if (response === parseInt(response, 10)) {
alert("That query had no results");
$("#dialog").dialog("close");
}
else {
var blob = new Blob([response], { type: "application/vnd.ms-excel" });
var objectUrl = URL.createObjectURL(blob);
$("#dialog").dialog("close");
window.location = objectUrl;
}
});
The file is downloading fine but when I go to open it, I get a "file formatted incorrectly message" from excel. I know that the excel file is coming down alright because this:
window.location = service
works just fine.
I have tried both:
application/vnd.ms-excel
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
file types. Neither one works. Thanks in advance.
edit: I thought that perhaps I needed to get the raw response (for this angular $http service) so I tried:
http.get(service, {
transformResponse:
function (d, h) {
return d;
}
})
.success(function (response) {
..same as above
});
Still not working though. In fact, I put a break point on d, it looks like a lot of the "unrecognized" characters are being changed to question marks (I compared d to my network tab.) In the network tab, the characters are one way but when I check d they have been changed to question marks.
The response returned from the $http.get is an object that includes the headers and you can't form a blob from it directly. You can access the binary content returned by the service like this:
var blob = new Blob([response.data], { type: "application/vnd.ms-excel" });
I am allowing the user to load images into a page via drag&drop and other methods. When an image is dropped, I'm using URL.createObjectURL to convert to an object URL to display the image. I am not revoking the url, as I do reuse it.
So, when it comes time to create a FormData object so I can allow them to upload a form with one of those images in it, is there some way I can then reverse that Object URL back into a Blob or File so I can then append it to a FormData object?
Modern solution:
let blob = await fetch(url).then(r => r.blob());
The url can be an object url or a normal url.
As gengkev alludes to in his comment above, it looks like the best/only way to do this is with an async xhr2 call:
var xhr = new XMLHttpRequest();
xhr.open('GET', 'blob:http%3A//your.blob.url.here', true);
xhr.responseType = 'blob';
xhr.onload = function(e) {
if (this.status == 200) {
var myBlob = this.response;
// myBlob is now the blob that the object URL pointed to.
}
};
xhr.send();
Update (2018): For situations where ES5 can safely be used, Joe has a simpler ES5-based answer below.
Maybe someone finds this useful when working with React/Node/Axios. I used this for my Cloudinary image upload feature with react-dropzone on the UI.
axios({
method: 'get',
url: file[0].preview, // blob url eg. blob:http://127.0.0.1:8000/e89c5d87-a634-4540-974c-30dc476825cc
responseType: 'blob'
}).then(function(response){
var reader = new FileReader();
reader.readAsDataURL(response.data);
reader.onloadend = function() {
var base64data = reader.result;
self.props.onMainImageDrop(base64data)
}
})
The problem with fetching the blob URL again is that this will create a full copy of the Blob's data, and so instead of having it only once in memory, you'll have it twice. With big Blobs this can blow your memory usage quite quickly.
It's rather unfortunate that the File API doesn't give us access to the currently linked Blobs, certainly they thought web-authors should store that Blob themselves at creation time anyway, which is true:
The best here is to store the object you used when creating the blob:// URL.
If you are afraid this would prevent the Blob from being Garbage Collected, you're right, but so does the blob:// URL in the first place, until you revoke it. So holding yourself a pointer to that Blob won't change a thing.
But for those who aren't responsible for the creation of the blob:// URI (e.g because a library made it), we can still fill that API hole ourselves by overriding the default URL.createObjectURL and URL.revokeObjectURL methods so that they do store references to the object passed.
Be sure to call this function before the code that does generate the blob:// URI is called.
// Adds an URL.getFromObjectURL( <blob:// URI> ) method
// returns the original object (<Blob> or <MediaSource>) the URI points to or null
(() => {
// overrides URL methods to be able to retrieve the original blobs later on
const old_create = URL.createObjectURL;
const old_revoke = URL.revokeObjectURL;
Object.defineProperty(URL, 'createObjectURL', {
get: () => storeAndCreate
});
Object.defineProperty(URL, 'revokeObjectURL', {
get: () => forgetAndRevoke
});
Object.defineProperty(URL, 'getFromObjectURL', {
get: () => getBlob
});
const dict = {};
function storeAndCreate(blob) {
const url = old_create(blob); // let it throw if it has to
dict[url] = blob;
return url
}
function forgetAndRevoke(url) {
old_revoke(url);
try {
if(new URL(url).protocol === 'blob:') {
delete dict[url];
}
} catch(e){}
}
function getBlob(url) {
return dict[url] || null;
}
})();
// Usage:
const blob = new Blob( ["foo"] );
const url = URL.createObjectURL( blob );
console.log( url );
const retrieved = URL.getFromObjectURL( url );
console.log( "retrieved Blob is Same Object?", retrieved === blob );
fetch( url ).then( (resp) => resp.blob() )
.then( (fetched) => console.log( "fetched Blob is Same Object?", fetched === blob ) );
And an other advantage is that it can even retrieve MediaSource objects, while the fetching solutions would just err in that case.
Using fetch for example like below:
fetch(<"yoururl">, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + <your access token if need>
},
})
.then((response) => response.blob())
.then((blob) => {
// 2. Create blob link to download
const url = window.URL.createObjectURL(new Blob([blob]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', `sample.xlsx`);
// 3. Append to html page
document.body.appendChild(link);
// 4. Force download
link.click();
// 5. Clean up and remove the link
link.parentNode.removeChild(link);
})
You can paste in on Chrome console to test. the file with download with 'sample.xlsx' Hope it can help!
See Getting BLOB data from XHR request which points out that BlobBuilder doesn't work in Chrome so you need to use:
xhr.responseType = 'arraybuffer';
Unfortunately #BrianFreud's answer doesn't fit my needs, I had a little different need, and I know that is not the answer for #BrianFreud's question, but I am leaving it here because a lot of persons got here with my same need. I needed something like 'How to get a file or blob from an URL?', and the current correct answer does not fit my needs because its not cross-domain.
I have a website that consumes images from an Amazon S3/Azure Storage, and there I store objects named with uniqueidentifiers:
sample: http://****.blob.core.windows.net/systemimages/bf142dc9-0185-4aee-a3f4-1e5e95a09bcf
Some of this images should be download from our system interface.
To avoid passing this traffic through my HTTP server, since this objects does not require any security to be accessed (except by domain filtering), I decided to make a direct request on user's browser and use local processing to give the file a real name and extension.
To accomplish that I have used this great article from Henry Algus:
http://www.henryalgus.com/reading-binary-files-using-jquery-ajax/
1. First step: Add binary support to jquery
/**
*
* jquery.binarytransport.js
*
* #description. jQuery ajax transport for making binary data type requests.
* #version 1.0
* #author Henry Algus <henryalgus#gmail.com>
*
*/
// use this transport for "binary" data type
$.ajaxTransport("+binary", function (options, originalOptions, jqXHR) {
// check for conditions and support for blob / arraybuffer response type
if (window.FormData && ((options.dataType && (options.dataType == 'binary')) || (options.data && ((window.ArrayBuffer && options.data instanceof ArrayBuffer) || (window.Blob && options.data instanceof Blob))))) {
return {
// create new XMLHttpRequest
send: function (headers, callback) {
// setup all variables
var xhr = new XMLHttpRequest(),
url = options.url,
type = options.type,
async = options.async || true,
// blob or arraybuffer. Default is blob
dataType = options.responseType || "blob",
data = options.data || null,
username = options.username || null,
password = options.password || null;
xhr.addEventListener('load', function () {
var data = {};
data[options.dataType] = xhr.response;
// make callback and send data
callback(xhr.status, xhr.statusText, data, xhr.getAllResponseHeaders());
});
xhr.open(type, url, async, username, password);
// setup custom headers
for (var i in headers) {
xhr.setRequestHeader(i, headers[i]);
}
xhr.responseType = dataType;
xhr.send(data);
},
abort: function () {
jqXHR.abort();
}
};
}
});
2. Second step: Make a request using this transport type.
function downloadArt(url)
{
$.ajax(url, {
dataType: "binary",
processData: false
}).done(function (data) {
// just my logic to name/create files
var filename = url.substr(url.lastIndexOf('/') + 1) + '.png';
var blob = new Blob([data], { type: 'image/png' });
saveAs(blob, filename);
});
}
Now you can use the Blob created as you want to, in my case I want to save it to disk.
3. Optional: Save file on user's computer using FileSaver
I have used FileSaver.js to save to disk the downloaded file, if you need to accomplish that, please use this javascript library:
https://github.com/eligrey/FileSaver.js/
I expect this to help others with more specific needs.
If you show the file in a canvas anyway you can also convert the canvas content to a blob object.
canvas.toBlob(function(my_file){
//.toBlob is only implemented in > FF18 but there is a polyfill
//for other browsers https://github.com/blueimp/JavaScript-Canvas-to-Blob
var myBlob = (my_file);
})
Following #Kaiido answer, another way to overload URL without messing with URL is to extend the URL class like this:
export class URLwithStore extends URL {
static createObjectURL(blob) {
const url = super.createObjectURL(blob);
URLwithStore.store = { ...(URLwithStore.store ?? {}), [url]: blob };
return url;
}
static getFromObjectURL(url) {
return (URLwithStore.store ?? {})[url] ?? null;
}
static revokeObjectURL(url) {
super.revokeObjectURL(url);
if (
new URL(url).protocol === "blob:" &&
URLwithStore.store &&
url in URLwithStore.store
)
delete URLwithStore.store[url];
}
}
Usage
const blob = new Blob( ["foo"] );
const url = URLwithStore.createObjectURL( blob );
const retrieved = URLwithStore.getFromObjectURL( url );
console.log( "retrieved Blob is Same Object?", retrieved === blob );