How to check whether it is a blob url using javascript - javascript

I have a situation where I am converting blobURL to base64 dataURLs, but I want to do this only if url is a blobURL.
So is there any way to check whether it is valid blob url?
my blob url - blob:http://192.168.0.136/85017e84-0f2d-4791-b563-240794abdcbf

You are facing an x-y problem.
You absolutely don't need to check if your blobURI is a valid one, because you absolutely don't need to use the blobURI in order to create a base64 version of the Blob it's pointing to.
The only way to do it is to fetch the Blob and this means creating a copy of its data in memory for no-good.
What you need is a way to retrieve this Blob.
There is unfortunately no official way to do so with the web APIs, but it's not that hard to make it ourselves:
We simply have to overwrite the default URL.createObjectURL method in order to map the passed Blob in a dictionnary using the blobURI as key:
(() => {
// 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, 'getBlobFromObjectURL', {
get: () => getBlob
});
const dict = {};
function storeAndCreate(blob) {
var url = old_create(blob); // let it throw if it has to
dict[url] = blob;
return url
}
function forgetAndRevoke(url) {
old_revoke(url);
// some checks just because it's what the question titel asks for, and well to avoid deleting bad things
try {
if(new URL(url).protocol === 'blob:')
delete dict[url];
}catch(e){} // avoided deleting some bad thing ;)
}
function getBlob(url) {
return dict[url];
}
})();
// a few example uses
const blob = new Blob(['foo bar']);
// first normal use everyhting is alive
const url = URL.createObjectURL(blob);
const retrieved = URL.getBlobFromObjectURL(url);
console.log('retrieved: ', retrieved);
console.log('is same object: ', retrieved === blob);
// a revoked URL, of no use anymore
const revoked = URL.createObjectURL(blob);
URL.revokeObjectURL(revoked);
console.log('revoked: ', URL.getBlobFromObjectURL(revoked));
// an https:// URL
console.log('https: ', URL.getBlobFromObjectURL(location.href));
PS: for the ones concerned about the case a Blob might be closed (e.g user provided file has been deleted from disk) then simply listen for the onerror event of the FileReader you'd use in next step.

you could do something like
var url = 'blob:http://192.168.0.136/85017e84-0f2d-4791-b563-240794abdcbf';
if(url.search('blob:') == -1){
//do something
}
you may also use reg-expression based check with url.match('url expression')

Related

Displaying cached image via Cache API

I'm trying to use the Cache API to store large files (one being 500MB) for offline use. I'm able to create/open the cache, fetch the image URL from AWS S3, and put it into the cache:
let cache = await caches.open('my-cache');
let url = 'https://[mybucket].s3.amazonaws.com/example.png';
await fetch(url).then( async (response) => {
if (!response.ok) {
throw new TypeError("Bad response status");
}
return cache.put(url, response);
});
This appears properly in the inspector with (what I think) are the correct CORS/content size responses:
I'm also able to find it in the cache, however instead of searching for the full S3 url it seems to only store it with /example.png as the match:
const options = {
ignoreSearch: true,
ignoreMethod: true,
ignoreVary: true
};
let img = await cache.match('/example.png', options);
This returns a ReadableStream successfully, but I don't know where to go next.
I am hoping to default to loading from the cache since the file sizes can get quite large.
Checking the documentation for Cache.match(), what you're getting in response should be - well, a Response! So, from there, you can use Response.blob() and pass that into URL.createObjectURL(), which you can make the src of your image.
That was a mouthful, so here it is in code.
// Get the cached item...
const options = {
ignoreSearch: true,
ignoreMethod: true,
ignoreVary: true
};
let img = await cache.match('/example.png', options);
// Handle the possibility of undefined
if(img === undefined) { return false; }
// Convert the image data to a blob...
let blob = img.blob();
// Assuming imgElement is our image element...
imgElement.src = URL.createObjectURL(blob);
// And finish it all off nicely.
return true;

How do I save the inputs of an HTML form into a JSON file with JavaScript?

As my question already reveals, I am trying to save inputs from an HTML form to a json file - However I only get to save the inputs to the localStorage.
let movies = [];
const addMovie = (ev)=>{
ev.preventDefault();
let movie = {
id: Date.now(),
title: document.getElementById('title').value,
year: document.getElementById('yr').value
}
movies.push(movie);
document.querySelector('form').reset();
//saving to localStorage
localStorage.setItem('key', JSON.stringify(movies) );
}
document.addEventListener('DOMContentLoaded', ()=>{
document.getElementById('btn').addEventListener('click', addMovie);
});
Now, I've already tried the fs.writeFile methods but somehow my pc doesn't want to import fs.
So I'm asking if there's another way to write the inputs into JSON or how to fix the import fs bug.
In a web browser, you can invoke download in multiple ways. One of possibilities are window.opening a window, redirection, or creating a link and invoking its click method.
const jsonToDownload = '{"foo":42}'
function download() {
const aElem = document.createElement("A")
aElem.href = 'data:application/octet-stream,' + jsonToDownload
aElem.download = 'filename.json'
aElem.click()
}
<button onclick=download()>Download</button>
You can use any filename. Just set the download attribute on the link to the filename.
If you want do download binary or large data, using BLOB object might be better idea. It is quite simple – create the BLOB, generate its URL, initiate the download and revoke the URL of the BLOB.
const jsonToDownload = '{"foo":42}'
function download() {
const blob = new Blob([jsonToDownload, ], {
type: 'application/json',
})
const url = URL.createObjectURL(blob)
const aElem = document.createElement("A")
aElem.href = 'data:application/octet-stream,' + jsonToDownload
aElem.download = 'a'
aElem.click()
URL.revokeObjectURL(url)
}
<button onclick=download()>Download</button>
Please note that the snippet above will not probably work, because StackOverflow's iframe usage prohibits this behavior in some browsers. Copy the code in your own HTML file adn try it there.

How to check if a URL is same origin as current page and/or worker

There's a similar question here
javascript How to check if a URL is same origin as current page?
Unfortunately it doesn't specify that answers must work in both a page and a worker so none of the answers work in a worker.
In particular I'm trying to use the fetch API to fetch images in a way that works as automatically as possible. When an image is fetched I want to set the mode to cors but only if the URL is not the same origin. I need to know how to do that in both a page context and a worker context.
Pseudo code
function loadImageBitmap(url) {
const options = {};
if (urlIsNotSameOrigin(url)) {
options.mode = 'cors';
}
return fetch(url, options)
.then((response) => {
if (!response.ok) {
throw response;
}
return response.blob();
}).then((blob) => {
return global.createImageBitmap(blob);
});
}
function urlIsNotSameOrigin(url) {
// what do I put here?
}
// examples
async function main() {
const bitmaps = Promise.all([
'https://notsamedomain.com/foo.jpg',
'../relative/image.png',
'/absolute/thing.jpg',
'//other/absolute/thing.jpg',
'https://samedomain.com/bar.gif',
].map(loadImageBitmap));
}
Most of the solutions I've seen are to make an anchor <a> element, assign the src property and then read it. But anchors don't exist in workers. The URL object doesn't seem to handle relative resources like the anchor does.
The difference between the URL() constructor and the anchor <a> element is that the URL() constructor is not a Node nor is it attached to a particular Document object, and thus doesn't have a baseURI it can hook on.
So what you need in order to make the URL() constructor behave the same as an anchor <a> element is to pass a baseURI as the second argument of the constructor.
This means that in a document new URL(uri, document.baseURI) would return the same URL properties as Object.assign(document.createElement('a'), {href: uri}), as long as the URI produced is a valid one.
Now, in a Worker we still don't have access to a Node's baseURI either, and it's quite unclear which you want to hook on.
In most cases, you can simply use self.location.href as a base URI, and that might actually be what you want if you are going to fetch same-origin resources, but if you do initialize your Worker from a blobURL, you may have to pass it from the main scope, just like I had to do in StackSnippet®'s over-protected iframe.
// init worker from a blobURI...
const worker_url = getWorkerURL(worker_script);
const worker = new Worker(worker_url);
worker.onmessage = e => console.log(e.data);
worker.postMessage({
// we pass the base URL
base: 'https://samedomain.com/foo.html',
uris: [
'https://notsamedomain.com/foo.jpg',
'../relative/image.png',
'/absolute/thing.jpg',
'//other/absolute/thing.jpg',
'https://samedomain.com/bar.gif'
]
});
//__MISC__________
// gets our Worker's blobURL based on a Blob made
// from a <script> element textContent
function getWorkerURL(el) {
const content = el.textContent;
const blob = new Blob([content], {type: 'application/javascript'});
return URL.createObjectURL(blob);
}
<script id="worker_script" type="worker_script">
onmessage = e => {
const d = e.data;
// if we weren't in a null origined iframe's blobURI we could do
//const self_url = new URL(location.href)
// but here we pass the fake base domain
const self_url = new URL(d.base);
const sameorigins = d.uris.filter( uri => {
try { // wrap in a try-catch, invalids throw
const url = new URL(uri, self_url);
return url.origin === self_url.origin;
} catch(e) { return false; }
})
postMessage(sameorigins);
};
</script>

file input files not read onChange on mobile

I'm building a puzzle app in React that allows the user to upload their own puzzles. This works fine on the web (the user clicks the input's label and it opens a dialog. When the user picks a file the onChange event is triggered), but on mobile, or at least on Chrome on Android, the files are not read...
This is where the input is declared:
<div className="file-input-wrapper">
<label for="puzzleUpload" className="button-dark">Upload Puzzle(s)</label>
<input type="file"
accept="application/json"
multiple
id="puzzleUpload"
onChange={handleFiles}/>
</div>
and this is the handleFiles() method
// when a file is uploaded, this checks to see that it's the right type, then adds it to the puzzle list
const handleFiles = () => {
var selectedFiles = document.getElementById('puzzleUpload').files;
// checks if the JSON is a valid puzzle
const validPuzzle = (puzzle) => {
let keys = ["name", "entitySetID", "logic", "size"];
return keys.every((key) => {return puzzle.hasOwnProperty(key)});
};
const onLoad = (event) => {
let puzzle = JSON.parse(event.target.result);
if(validPuzzle(puzzle)) {
appendPuzzleList(puzzle);
}
else {
console.log("JSON file does not contain a properly formatted Logike puzzle")
}
};
//checks the file type before attempting to read it
for (let i = 0; i < selectedFiles.length; i++) {
if(selectedFiles[i].type === 'application/json') {
//creates new readers so that it can read many files sequentially.
var reader = new FileReader();
reader.onload = onLoad;
reader.readAsText(selectedFiles[i]);
}
}
};
A working prototype with the most recent code can be found at http://logike.confusedretriever.com and it's possible to quickly write compatible JSON using the builder in the app.
I've been looking up solutions for the past hour and a half and have come up empty handed, so any help would be greatly appreciated! I read the FileReader docs, and everything seems to be supported, so I'm kind of stumped.
Interestingly, the file IS selected (you can see the filename in the ugly default version of the input once it's selected, but I hide it via CSS), so I'm tempted to implement a mobile-only button to trigger the event, if there isn't a more legit solution...
Chrome uses the OS's list of known MIME Types.
I guess Android doesn't know about "application/json", and at least, doesn't map the .json extension to this MIME type, this means that when you upload your File in this browser, you won't have the correct type property set, instead, it is set to the empty string ("").
But anyway, you shouldn't trust this type property, ever.
So you could always avoid some generic types, like image/*, video/*, but the only reliable way to know if it was a valid JSON file or not will be by actually reading the data contained in your file.
But I understand you don't want to start this operation if your user provides a huge file, like a video.
One simple solution might be to check the size property instead, if you know in which range your generated files might come.
One less simple but not so hard either solution would be to prepend a magic number (a.k.a File Signature)to your generated files (if your app is the only way to handle these files).
Then you would just have to check this magic number only before going to read the whole file:
// some magic-number (here "•MJS")
const MAGIC_NB = new Uint8Array([226, 128, 162, 77, 74, 83]);
// creates a json-like File, with our magic_nb prepended
function generateFile(data) {
const str = JSON.stringify(data);
const blob = new Blob([MAGIC_NB, str], {
type: 'application/myjson' // won't be used anyway
});
return new File([blob], 'my_file.json');
}
// checks whether the provided blob starts with our magic numbers or not
function checkFile(blob) {
return new Promise((res, rej) => {
const reader = new FileReader();
reader.onload = e => {
const arr = new Uint8Array(reader.result);
res(!arr.some((v, i) => MAGIC_NB[i] !== v));
};
reader.onerror = rej;
// read only the length of our magic nb
reader.readAsArrayBuffer(blob.slice(0, MAGIC_NB.length));
});
}
function handleFile(file) {
return checkFile(file).then(isValid => {
if (isValid) {
return readFile(file);
} else {
throw new Error('invalid file');
}
});
}
function readFile(file) {
return new Promise((res, rej) => {
const reader = new FileReader();
reader.onload = e => res(JSON.parse(reader.result));
reader.onerror = rej;
// don't read the magic_nb part again
reader.readAsText(file.slice(MAGIC_NB.length));
});
}
const my_file = generateFile({
key: 'value'
});
handleFile(my_file)
.then(obj => console.log(obj))
.catch(console.error);
And in the same way note that all browsers won't accept all the schemes for the accept attribute, and that you might want to double your MIME notation with a simple extension one (anyway even MIMEs are checked only against this extension).

How to retrieve a MediaStream from a Blob url?

It was possible to get an URL using window.URL.createObjectURL() from a stream like in below code.
navigator.getUserMedia({ video: true, audio: true }, function (localMediaStream) {
var video = document.querySelector('video');
video.src = window.URL.createObjectURL(localMediaStream);
video.onloadedmetadata = function (e) {
// Do something with the video here.
};
},
function (err) {
console.log("The following error occured: " + err);
}
);
Problem is now I have a blob URL like:
blob:http%3A//localhost%3A1560/f43bed15-da6c-4ff1-b73c-5640ed94e8ee
Is there a way to retrieve the MediaStream object from that?
Note:
URL.createObjectURL(MediaStream) has been deprecated.
Do not use it in code anymore, it will throw in any recent browsers.
The premise of the question is still valid though.
There is no built in way to retrieve the original object a blob URL points to.
With Blobs, we can still fetch this blob URL and we'll get a copy of the original Blob.
const blob = new Blob(['hello']);
const url = URL.createObjectURL(blob);
fetch(url)
.then(r => r.blob())
.then(async (copy) => {
console.log('same Blobs?', copy === blob);
const blob_arr = new Uint8Array(await new Response(blob).arrayBuffer());
const copy_arr = new Uint8Array(await new Response(copy).arrayBuffer());
console.log("same content?", JSON.stringify(blob_arr) === JSON.stringify(copy_arr))
console.log(JSON.stringify(copy_arr));
})
With other objects though, this won't work...
const source = new MediaSource();
const url = URL.createObjectURL(source);
fetch(url)
.then(r => r.blob())
.then(console.log)
.catch(console.error);
The only way then is to keep track of your original objects.
To do so, we can come up with simple wrappers around createObjectURL and revokeObjectURL to update a dictionary of objects accessible by URL:
(() => {
// 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) {
var url = old_create(blob); // let it throw if it has to
dict[url] = blob;
return url
}
function forgetAndRevoke(url) {
old_revoke(url);
// some checks just because it's what the question titel asks for, and well to avoid deleting bad things
try {
if(new URL(url).protocol === 'blob:')
delete dict[url];
}catch(e){} // avoided deleting some bad thing ;)
}
function getBlob(url) {
return dict[url];
}
})();
// a few example uses
// first a simple Blob
test(new Blob(['foo bar']));
// A more complicated MediaSource
test(new MediaSource());
function test(original) {
const url = URL.createObjectURL(original);
const retrieved = URL.getFromObjectURL(url);
console.log('retrieved: ', retrieved);
console.log('is same object: ', retrieved === original);
URL.revokeObjectURL(url);
}
In case you are using angular2, you can use the DOMSanitizer provided in the platform-browser-package:
import { DomSanitizer } from '#angular/platform-browser';
constructor(
private sanitizer: DomSanitizer) {
}
and then use your stream like the following:
//your code comes here...
video.src = this.sanitizer.bypassSecurityTrustUrl(window.URL.createObjectURL(stream));
This should only
video.src is NOT video.srcObject
And yes they will conflict ;) !
video.src takes source URL
video.srcObject takes source OBJECT (currently as of 2019 only MediaStream is safely supported, maybe in the future you could put the Blob directly here, but not now...)
So it depends on what you really want to do:
A) Display what is currently being recorded
You must have MediaStream object available (which you do) and just put it into video.srcObject
navigator.getUserMedia({ video: true, audio: true }, function (localMediaStream) {
var video = document.querySelector('video');
video.src = ''; // just to be sure src does not conflict with us
video.srcObject = localMediaStream;
}
B) Display existing video / just recorded video
video.srcObject = null; // make sure srcObject is empty and does not overlay our src
video.src = window.URL.createObjectURL(THE_BLOB_OBJECT);
THE_BLOB_OBJECT - you either already have one created through File API, or usually if you have some kind of recorder, let's assume in recorder variable, usually there is getBlob() or something similar available like recorder.getBlob() I strongly recommend you use some existing recorder library for this, but to be complete there is an official MediaRecorder API - https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder
So you see you've just combined 2 things together, you just need to separate them and make sure they don't conflict :)

Categories