JavaScript - Check if Object URL has been used - javascript

I can create an object URL like this:
let pixel = "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
let data = atob(pixel);
let buffer = new ArrayBuffer(data.length);
let array = new Uint8Array(buffer);
for (var i = 0;i<data.length;i++){
array[i]=data.charCodeAt(i);
}
let blob = new Blob([buffer], {type: 'image/jpeg'});
let url = URL.createObjectURL(blob);
And I can revoke an object URL like this:
URL.revokeObjectURL(url);
But what I would like to do is automatically revoke the URL after one use.
Is there a way to check if an object URL has been accessed?
Thank you in advance.
EDIT:
In this case, I can't just trigger a function when I use the URL. Instead, I would like to be able to check if it has been used even OUTSIDE of the page. An example would be if someone typed the URL into the address bar, this can't call javascript AFAIK.

If you don't have an API to do this, you could use localStorage to track this.
You could have 2 methods,
addUrlStatus - which takes a url as input and checks whether it has been accessed in the past.
updateUrlStatus - which sets the status of a url to true in localStorage once the user has used the url (not sure what action defines this in your code).
So you could do something like,
let pixel = "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
let data = atob(pixel);
let buffer = new ArrayBuffer(data.length);
let array = new Uint8Array(buffer);
for (var i = 0;i<data.length;i++){
array[i]=data.charCodeAt(i);
}
let blob = new Blob([buffer], {type: 'image/jpeg'});
let url = URL.createObjectURL(blob);
addUrlStatus(url);
function addUrlStatus(url) {
var _url = localStorage.getItem(url);
if(!_url) {
localStorage.setItem(url, false); // false indicates it hasn't been used yet.
return;
}
}
// this function needs to be called when the user has used the url.
function updateUrlStatus(url) {
localStorage.setItem(url, true);
URL.revokeObjectURL(url);
}

Related

js xhr.response store in localstorage

Is it possible to store xhr.response in local-storage , code : var xhr = new XMLHttpRequest();xhr.open('GET', 'http://cdn.dmsapp.tk/'+appUrl+'/'+evt+'?authToken='+this.getuserservice.authorizationfun()+'&force=false');localStorage['tifPdfFileObj'] = JSON.stringify(xhr.response);xhr.responseType = 'arraybuffer';xhr.onload = function (e) { localStorage['tifPdfFileObj'] = JSON.stringify(xhr.response);}
I need to store the xhr.response object in local-storage or some where, to use it whenever i want instead of hit api for same image.when i try to stringify the response, it returns empty({}).How to store in local storage and use it.
current usage : var buffer = xhr.response;var tiff = new Tiff({buffer: buffer});

How to download image from a url in chrome app using chrome filesystem api?

I have a url which has a image. I want to download that image using chrome api. I am developing a chrome app and not extension.
Can anyone please tell me where i am going wrong??
My code:
service('fileService', function($window){
this.saveUserInfo = function(theFilePath) {
// filepath is the url passed from controller{
// Get the directory entry stored (if any).
var fileUrl = "";
var config = {
type: 'saveFile',
suggestedName: 'TheBee.png',
accepts:[{extensions: ['png', 'jpg', 'jpeg']}]
};
chrome.fileSystem.chooseEntry(config, function(writableEntry) {
chrome.fileSystem.getWritableEntry(writableEntry, function(entry1) {
entry1.getFile(theFilePath, {create:true}, function(entry2) {
entry2.createWriter(function(writer) {
var blob = new Blob({type: 'image/png'});
writer.write(blob);
});
});
});
});
});
});
Change
var blob = new Blob({type: 'image/png'});
To
var blob = new Blob([entry1],{type: 'image/png'});
The Blob contructor acceptes blob parts: MSDN=> Blob(blobParts[, options])
You cannot use getFile on entry1 because it is a file itself. You will need to change config.type to "openDirectory"
You also need to add an empty array as the first input to the blob constructor.

Attach a blob to an input of type file in a form

How to append blob to input of type file?
<!-- Input of type file -->
<input type="file" name="uploadedFile" id="uploadedFile" accept="image/*"><br>
// I am getting image from webcam and converting it to a blob
function takepicture() {
canvas.width = width;
canvas.height = height;
canvas.getContext('2d').drawImage(video, 0, 1, width, height);
var data = canvas.toDataURL('image/png');
var dataURL = canvas.toDataURL();
var blob = dataURItoBlob(dataURL);
photo.setAttribute('src', data);
}
function dataURItoBlob(dataURI) {
var binary = atob(dataURI.split(',')[1]);
var array = [];
for(var i = 0; i < binary.length; i++) {
array.push(binary.charCodeAt(i));
return new Blob([new Uint8Array(array)], {type: 'image/jpeg'});
}
// How can I append this var blob to "uploadedFile". I want to add this on form submit
It is possible to set value of <input type="file">.
To do this you create File object from blob and new DataTransfer object:
let file = new File([data], "img.jpg",{type:"image/jpeg", lastModified:new Date().getTime()});
let container = new DataTransfer();
Then you add file to container thus populating its 'files' property, which can be assigned to 'files' property of file input:
container.items.add(file);
fileInputElement.files = container.files;
Here is a fiddle with output, showing that file is correctly placed into input.
The file is also passed correctly to server on form submit. This works at least on Chrome 88.
If you need to pass multiple files to input you can just call container.items.add multiple times. So you can add files to input by keeping track of them separately and overwriting its 'files' property as long as this input contains only generated files (meaning not selected by user). This can be useful for image preprocessing, generating complex files from several simple ones (e.g. pdf from several images), etc.
API references:
File object
DataTransfer object
I had a similar problem with a fairly complex form in an angular app, so instead of the form I just sent the blob individually using XMLHttpRequest(). This particular "blob" was created in a WebAudioAPI context, creating an audio track in the user's browser.
var xhr = new XMLHttpRequest();
xhr.open('POST', 'someURLForTheUpload', true); //my url had the ID of the item that the blob corresponded to
xhr.responseType = 'Blob';
xhr.setRequestHeader("x-csrf-token",csrf); //if you are doing CSRF stuff
xhr.onload = function(e) { /*irrelevant code*/ };
xhr.send(blob);
You can't change the file input but you can use a hidden input to pass data. ex.:
var hidden_elem = document.getElementById("hidden");
hidden_elem.value = blob;
I had take too much time to find how to do "url, blob to input -> preview"
so I had do a example you can check here
https://stackoverflow.com/a/70485949/6443916
https://vulieumang.github.io/vuhocjs/file2input-input2file/
image bonus

Use filedata instead of file attribute to pass data

I'm trying to render vtk objects which are send from a webserver directly on the client using XTK without storing them to the disk. According to the XTK Documentation I just have to pass the vtk file as string into X.Mesh.filedata, but it just doesn't display anything when I'm trying to do that.
I want to do something like this:
var data = recieveVTKFileAsStringFromServer();
var r = new X.renderer3D();
r.init();
// create a mesh from a .vtk file
var dataset = new X.mesh();
// dataset.file = 'someFile.vtk';
dataset.filedata = data;
// add the object
r.add(dataset);
// .. and render it
r.render();
When I load the file from the file everything works just fine, setting it using filedata doesn't. Where is my mistake?
I also came up with the similar scenario to load the binary data directly using filedata instead of setting file attribute. I did this by passing dummy name in a file attribute along with actual binary data set in filedata and everything works fine.
var xhr = new XMLHttpRequest();
xhr.open('GET', '/test.nii', true);
xhr.responseType = 'arraybuffer';
xhr.send();
xhr.onreadystatechange = function (e) {
if (this.readyState === 4) {
var r = new X.renderer2D();
r.container = 'myImg';
r.orientation = 'Z';
r.init();
volume = new X.volume();
volume.file = "abc.nii";
volume.filedata = this.response;
r.add(volume);
r.render();
}
};

How to get a file or blob from an object URL?

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 );

Categories