Firefox Extension FavIconUrl is super long. Any way to shrink it? - javascript

I am working on a cross-browser extension and use the "tabs API" (https://developer.chrome.com/docs/extensions/reference/tabs/) to store relevant information about each tab.
One of these fields is the tab's favIconUrl.
Problem
In Chrome & Microsoft Edge, this field is given as a URL string and is usually relatively short (a few bytes).
However in Firefox, this is given as a data:image/x-icon;base64 string and is VERY long (a couple of KB).
The problem is that I use storage.sync and this resource is sparse per item stored - so what takes up nothing in Chrome/Edge, is barely enough in Firefox for a single tab.
Example (Facebook Tab)
Chrome & Microsoft Edge:
https://static.xx.fbcdn.net/rsrc.php/yo/r/iRmz9lCMBD2.ico
Firefox:
data:image/x-icon;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAs2hCtbJnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L///////////+yZ0L/smdC/7JnQv+yZ0L/s2hCtbJnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC////////////smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv///////////7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L///////////+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC////////////smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv///////////7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/////////////////////////////////8WMcP+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv/////////////////////////////////RpI3/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv///////////7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L///////////+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC//ny7v//////z6GK/7NoQ/+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv/ixbf/////////////////2bOh/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/t3BO/+LGuP/59PH///7+/9evm/+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/s2hCtbJnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/s2hCtQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAsmlCSbNnQuOyZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L//////////////////////7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/s2dC47JpQkmzZ0LjsmdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv//////////////////////smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/s2dC47JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC//////////////////////+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L//////////////////////7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv//////////////////////smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC//////////////////////+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L//////////////////////7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv//////////////////////smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC//////////////////////+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L//////////////////////7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv//////////////////////smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC//////////////////////+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv/////////////////////////////////////////////////////////////////SpZD/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/////////////////////////////////////////////////////////////////969rf+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/////////////////////////////////////////////////////////////////6tXK/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv/////////////////////////////////////////////////////////////////27Oj/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv//////////////////////smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC//////////////////////+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L//////////////////////7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv//////////////////////s2lF/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC//fv6//////////////////Kl33/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/69fN//////////////////nz8P/Iknj/tWxI/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv/bt6X///////////////////////////////////////////+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7ZvTP/37ur//////////////////////////////////////7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/8aNcf/48e3/////////////////////////////////smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7ZvTP/atqT/7dzT//n08f///////v38//fv6/+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7NpRP+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/s2dC47JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7NnQuOyaUJJs2dC47JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+yZ0L/smdC/7JnQv+zZ0LjsmlCSQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Ideas?
Any ideas how I can shorten this Base64 string?
Ideally, I would need some decoder that decodes base64 but keeps regular URL strings unchanged.

For others that might face the same issue in the future.
THIS APPROACH IS GOOD, BUT YOU RISK VIOLATING CSP DUE TO USING BLOBS... SEE VERY BOTTOM FOR BETTER SOLUTION
I ended up converting it to a blob url by modifying this answer to my needs:
https://stackoverflow.com/a/16245768/4298115
function convertToShortURL(input_str, sliceSize = 512) {
if (input_str && input_str.includes("base64")) {
input_str = input_str.split(",")[1]; // get the base64 part
const byteCharacters = atob(input_str);
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
const blob = new Blob(byteArrays);
return URL.createObjectURL(blob);
} else {
return input_str;
}
}
Explanation
This function takes in an input string that you would get from tab.favIconUrl using the "Tabs API" and if it is a base64 string, will return a short blob url - which you can then use the same way as a url string. Otherwise, if the input is already a url string or undefined (not base64), the function simply returns it as is.
BEST APPROACH
Based on:
https://stackoverflow.com/a/8498629/4298115 (domain from url)
https://stackoverflow.com/a/3880629/4298115 (favicon from domain)
function getFavIconURL(url) {
var matches = url.match(/^https?\:\/\/([^\/?#]+)(?:[\/?#]|$)/i);
var domain = matches && matches[1];
return "http://www.google.com/s2/favicons?domain=" + domain;
}
This function simply returns the favicon url without returning a blob file.
Simple and elegant in my opinion. Cheers!

Related

Having Trouble Parsing a Base64 Encoded Array into Geometry Data [duplicate]

I need to convert a base64 encode string into an ArrayBuffer.
The base64 strings are user input, they will be copy and pasted from an email, so they're not there when the page is loaded.
I would like to do this in javascript without making an ajax call to the server if possible.
I found those links interesting, but they didt'n help me:
ArrayBuffer to base64 encoded string
this is about the opposite conversion, from ArrayBuffer to base64, not the other way round
http://jsperf.com/json-vs-base64/2
this looks good but i can't figure out how to use the code.
Is there an easy (maybe native) way to do the conversion? thanks
Try this:
function _base64ToArrayBuffer(base64) {
var binary_string = window.atob(base64);
var len = binary_string.length;
var bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}
Using TypedArray.from:
Uint8Array.from(atob(base64_string), c => c.charCodeAt(0))
Performance to be compared with the for loop version of Goran.it answer.
For Node.js users:
const myBuffer = Buffer.from(someBase64String, 'base64');
myBuffer will be of type Buffer which is a subclass of Uint8Array. Unfortunately, Uint8Array is NOT an ArrayBuffer as the OP was asking for. But when manipulating an ArrayBuffer I almost always wrap it with Uint8Array or something similar, so it should be close to what's being asked for.
Goran.it's answer does not work because of unicode problem in javascript - https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding.
I ended up using the function given on Daniel Guerrero's blog: http://blog.danguer.com/2011/10/24/base64-binary-decoding-in-javascript/
Function is listed on github link: https://github.com/danguer/blog-examples/blob/master/js/base64-binary.js
Use these lines
var uintArray = Base64Binary.decode(base64_string);
var byteArray = Base64Binary.decodeArrayBuffer(base64_string);
Async solution, it's better when the data is big:
// base64 to buffer
function base64ToBufferAsync(base64) {
var dataUrl = "data:application/octet-binary;base64," + base64;
fetch(dataUrl)
.then(res => res.arrayBuffer())
.then(buffer => {
console.log("base64 to buffer: " + new Uint8Array(buffer));
})
}
// buffer to base64
function bufferToBase64Async( buffer ) {
var blob = new Blob([buffer], {type:'application/octet-binary'});
console.log("buffer to blob:" + blob)
var fileReader = new FileReader();
fileReader.onload = function() {
var dataUrl = fileReader.result;
console.log("blob to dataUrl: " + dataUrl);
var base64 = dataUrl.substr(dataUrl.indexOf(',')+1)
console.log("dataUrl to base64: " + base64);
};
fileReader.readAsDataURL(blob);
}
Javascript is a fine development environment so it seems odd than it doesn't provide a solution to this small problem. The solutions offered elsewhere on this page are potentially slow. Here is my solution. It employs the inbuilt functionality that decodes base64 image and sound data urls.
var req = new XMLHttpRequest;
req.open('GET', "data:application/octet;base64," + base64Data);
req.responseType = 'arraybuffer';
req.onload = function fileLoaded(e)
{
var byteArray = new Uint8Array(e.target.response);
// var shortArray = new Int16Array(e.target.response);
// var unsignedShortArray = new Int16Array(e.target.response);
// etc.
}
req.send();
The send request fails if the base 64 string is badly formed.
The mime type (application/octet) is probably unnecessary.
Tested in chrome. Should work in other browsers.
Pure JS - no string middlestep (no atob)
I write following function which convert base64 in direct way (without conversion to string at the middlestep). IDEA
get 4 base64 characters chunk
find index of each character in base64 alphabet
convert index to 6-bit number (binary string)
join four 6 bit numbers which gives 24-bit numer (stored as binary string)
split 24-bit string to three 8-bit and covert each to number and store them in output array
corner case: if input base64 string ends with one/two = char, remove one/two numbers from output array
Below solution allows to process large input base64 strings. Similar function for convert bytes to base64 without btoa is HERE
function base64ToBytesArr(str) {
const abc = [..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"]; // base64 alphabet
let result = [];
for(let i=0; i<str.length/4; i++) {
let chunk = [...str.slice(4*i,4*i+4)]
let bin = chunk.map(x=> abc.indexOf(x).toString(2).padStart(6,0)).join('');
let bytes = bin.match(/.{1,8}/g).map(x=> +('0b'+x));
result.push(...bytes.slice(0,3 - (str[4*i+2]=="=") - (str[4*i+3]=="=")));
}
return result;
}
// --------
// TEST
// --------
let test = "Alice's Adventure in Wonderland.";
console.log('test string:', test.length, test);
let b64_btoa = btoa(test);
console.log('encoded string:', b64_btoa);
let decodedBytes = base64ToBytesArr(b64_btoa); // decode base64 to array of bytes
console.log('decoded bytes:', JSON.stringify(decodedBytes));
let decodedTest = decodedBytes.map(b => String.fromCharCode(b) ).join``;
console.log('Uint8Array', JSON.stringify(new Uint8Array(decodedBytes)));
console.log('decoded string:', decodedTest.length, decodedTest);
Caution!
If you want to decode base64 to STRING (not bytes array) and you know that result contains utf8 characters then atob will fail in general e.g. for character 💩 the atob("8J+SqQ==") will give wrong result . In this case you can use above solution and convert result bytes array to string in proper way e.g. :
function base64ToBytesArr(str) {
const abc = [..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"]; // base64 alphabet
let result = [];
for(let i=0; i<str.length/4; i++) {
let chunk = [...str.slice(4*i,4*i+4)]
let bin = chunk.map(x=> abc.indexOf(x).toString(2).padStart(6,0)).join('');
let bytes = bin.match(/.{1,8}/g).map(x=> +('0b'+x));
result.push(...bytes.slice(0,3 - (str[4*i+2]=="=") - (str[4*i+3]=="=")));
}
return result;
}
// --------
// TEST
// --------
let testB64 = "8J+SqQ=="; // for string: "💩";
console.log('input base64 :', testB64);
let decodedBytes = base64ToBytesArr(testB64); // decode base64 to array of bytes
console.log('decoded bytes :', JSON.stringify(decodedBytes));
let result = new TextDecoder("utf-8").decode(new Uint8Array(decodedBytes));
console.log('properly decoded string :', result);
let result_atob = atob(testB64);
console.log('decoded by atob :', result_atob);
Snippets tested 2022-08-04 on: chrome 103.0.5060.134 (arm64), safari 15.2, firefox 103.0.1 (64 bit), edge 103.0.1264.77 (arm64), and node-js v12.16.1
I would strongly suggest using an npm package implementing correctly the base64 specification.
The best one I know is rfc4648
The problem is that btoa and atob use binary strings instead of Uint8Array and trying to convert to and from it is cumbersome. Also there is a lot of bad packages in npm for that. I lose a lot of time before finding that one.
The creators of that specific package did a simple thing: they took the specification of Base64 (which is here by the way) and implemented it correctly from the beginning to the end. (Including other formats in the specification that are also useful like Base64-url, Base32, etc ...) That doesn't seem a lot but apparently that was too much to ask to the bunch of other libraries.
So yeah, I know I'm doing a bit of proselytism but if you want to avoid losing your time too just use rfc4648.
I used the accepted answer to this question to create base64Url string <-> arrayBuffer conversions in the realm of base64Url data transmitted via ASCII-cookie [atob, btoa are base64[with +/]<->js binary string], so I decided to post the code.
Many of us may want both conversions and client-server communication may use the base64Url version (though a cookie may contain +/ as well as -_ characters if I understand well, only ",;\ characters and some wicked characters from the 128 ASCII are disallowed). But a url cannot contain / character, hence the wider use of b64 url version which of course not what atob-btoa supports...
Seeing other comments, I would like to stress that my use case here is base64Url data transmission via url/cookie and trying to use this crypto data with the js crypto api (2017) hence the need for ArrayBuffer representation and b64u <-> arrBuff conversions... if array buffers represent other than base64 (part of ascii) this conversion wont work since atob, btoa is limited to ascii(128). Check out an appropriate converter like below:
The buff -> b64u version is from a tweet from Mathias Bynens, thanks for that one (too)! He also wrote a base64 encoder/decoder:
https://github.com/mathiasbynens/base64
Coming from java, it may help when trying to understand the code that java byte[] is practically js Int8Array (signed int) but we use here the unsigned version Uint8Array since js conversions work with them. They are both 256bit, so we call it byte[] in js now...
The code is from a module class, that is why static.
//utility
/**
* Array buffer to base64Url string
* - arrBuff->byte[]->biStr->b64->b64u
* #param arrayBuffer
* #returns {string}
* #private
*/
static _arrayBufferToBase64Url(arrayBuffer) {
console.log('base64Url from array buffer:', arrayBuffer);
let base64Url = window.btoa(String.fromCodePoint(...new Uint8Array(arrayBuffer)));
base64Url = base64Url.replaceAll('+', '-');
base64Url = base64Url.replaceAll('/', '_');
console.log('base64Url:', base64Url);
return base64Url;
}
/**
* Base64Url string to array buffer
* - b64u->b64->biStr->byte[]->arrBuff
* #param base64Url
* #returns {ArrayBufferLike}
* #private
*/
static _base64UrlToArrayBuffer(base64Url) {
console.log('array buffer from base64Url:', base64Url);
let base64 = base64Url.replaceAll('-', '+');
base64 = base64.replaceAll('_', '/');
const binaryString = window.atob(base64);
const length = binaryString.length;
const bytes = new Uint8Array(length);
for (let i = 0; i < length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
console.log('array buffer:', bytes.buffer);
return bytes.buffer;
}
made a ArrayBuffer from a base64:
function base64ToArrayBuffer(base64) {
var binary_string = window.atob(base64);
var len = binary_string.length;
var bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}
I was trying to use above code and It's working fine.
The result of atob is a string that is separated with some comma
,
A simpler way is to convert this string to a json array string and after that parse it to a byteArray
below code can simply be used to convert base64 to an array of number
let byteArray = JSON.parse('['+atob(base64)+']');
let buffer = new Uint8Array(byteArray);
Solution without atob
I've seen many people complaining about using atob and btoa in the replies. There are some issues to take into account when using them.
There's a solution without using them in the MDN page about Base64. Below you can find the code to convert a base64 string into a Uint8Array copied from the docs.
Note that the function below returns a Uint8Array. To get the ArrayBuffer version you just need to do uintArray.buffer.
function b64ToUint6(nChr) {
return nChr > 64 && nChr < 91
? nChr - 65
: nChr > 96 && nChr < 123
? nChr - 71
: nChr > 47 && nChr < 58
? nChr + 4
: nChr === 43
? 62
: nChr === 47
? 63
: 0;
}
function base64DecToArr(sBase64, nBlocksSize) {
const sB64Enc = sBase64.replace(/[^A-Za-z0-9+/]/g, "");
const nInLen = sB64Enc.length;
const nOutLen = nBlocksSize
? Math.ceil(((nInLen * 3 + 1) >> 2) / nBlocksSize) * nBlocksSize
: (nInLen * 3 + 1) >> 2;
const taBytes = new Uint8Array(nOutLen);
let nMod3;
let nMod4;
let nUint24 = 0;
let nOutIdx = 0;
for (let nInIdx = 0; nInIdx < nInLen; nInIdx++) {
nMod4 = nInIdx & 3;
nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << (6 * (3 - nMod4));
if (nMod4 === 3 || nInLen - nInIdx === 1) {
nMod3 = 0;
while (nMod3 < 3 && nOutIdx < nOutLen) {
taBytes[nOutIdx] = (nUint24 >>> ((16 >>> nMod3) & 24)) & 255;
nMod3++;
nOutIdx++;
}
nUint24 = 0;
}
}
return taBytes;
}
If you're interested in the reverse operation, ArrayBuffer to base64, you can find how to do it in the same link.

How to use crypto-js to save the binary data, of an encryption-output, to a file?

I'm trying to assist a mobile developer with encrypting an image and uploading it to Azure blob storage. The mobile app uses expo and crypto js. I'm not super familiar with mobile dev or expo but it looks like expo gives you access to a base64 encoded version of the image.
My goal is to encrypt that image data, using crypto js, and upload it to Azure blob storage.
The specifics of expo or Azure aren't really that important to my question, but I figure they're worth mentioning. What is important, I think, is that I'm using crypto js to AES encrypt that image data.
I'm starting with a base64 string of image data and so I use crypto js to parse that like follows ...
const words = CryptoES.enc.Base64.parse(data);
This gives me a WordArray representing the image data, I think (from the base64 string which the mobile API gives me).
Next I can encrypt that image data like so ...
const encrypted = CryptoES.AES.encrypt(words, AES_KEY, { iv: AES_IV });
Now that I have the encrypted data, I would like to write it out to a file in just a binary hex format or whatever. I don't want base64 text in the file and I don't want a hex-string in the file - I'd like it to contain the literal encrypted data byte for byte.
I'm not sure how to get this data.
I guess it's just "toString" but when I do that it says invalid utf8. This is a JPG file that is being dealt with.
How can I get just the actual byte data and write that to a file with crypto js?
CryptoJS.AES.encrypt() returns a CipherParams object that encapsulates, among others, the ciphertext as WordArray. One possibility is to convert this WordArray to a Uint8Array with a custom method. In the following code this conversion is done by convertWordArrayToUint8Array():
function convertWordArrayToUint8Array(wordArray) {
var arrayOfWords = wordArray.hasOwnProperty("words") ? wordArray.words : [];
var length = wordArray.hasOwnProperty("sigBytes") ? wordArray.sigBytes : arrayOfWords.length * 4;
var uInt8Array = new Uint8Array(length), index=0, word, i;
for (i=0; i<length; i++) {
word = arrayOfWords[i];
uInt8Array[index++] = word >> 24;
uInt8Array[index++] = (word >> 16) & 0xff;
uInt8Array[index++] = (word >> 8) & 0xff;
uInt8Array[index++] = word & 0xff;
}
return uInt8Array;
}
var AES_KEY = CryptoJS.enc.Utf8.parse('0123456789012345');
var AES_IV = CryptoJS.enc.Utf8.parse('5432109876543210');
var plaintext = 'The quick brown fox jumps over the lazy dog';
var ciphertextCP = CryptoJS.AES.encrypt(plaintext, AES_KEY, { iv: AES_IV }); // CipherParams object
var ciphertextWA = ciphertextCP.ciphertext; // WordArray
var ciphertextArr = convertWordArrayToUint8Array(ciphertextWA); // Uint8Array
This Uint8Array can now be stored in a file, e.g. with:
var fileName = "encdata.bin";
saveByteArray([ciphertextArr], fileName);
using saveByteArray() from here.
Another approach is to convert the WordArray to a binary string using the Latin1 encoder:
var ciphertextBinStr = ciphertextWA.toString(CryptoJS.enc.Latin1);
which can then easily be converted to a Uint8Array, e.g. with:
function str2Uint8Array(str) {
const arr = new Uint8Array(new ArrayBuffer(str.length));
for (let i = 0, strLen = str.length; i < strLen; i++)
arr[i] = str.charCodeAt(i);
return arr;
}
var ciphertextArr = str2Uint8Array(ciphertextBinStr);

HTML Table to Excel (xls) using javascript/jQuery

I am trying to export a html table to an Excel document via a javascript/jquery export. I so far have found 2 solutions but neither meet my requirements:
HTML Table Export
Does not export inline styling (styling is a must-have requirement)
Table2Excel
Does not work in all IE versions (need all browser compatibility)
I am looking for a solution that is as minimal as possible (both of these solutions were very "tidy" but as per my comments above they both had a downfall that fell short of my requirements.
Does anyone know a better solution? My requirements for this export are:
Needs to work on all browsers
Needs to export the inline styling
IF POSSIBLE it would be nice to be able to name the file
Would appreciate any assistance here, pulling my hair out as I can't be the first person that has required this feature...
Thanks!
I'm assuming that PHP / Flash is a no go. (If not, checkout PHPExcel and DataTables' export feature.)
Also, naming the file is nigh impossible without server-side programming of some type. I'm pretty sure this is a security problem for most browsers.
Before we get to the code, some limitations:
You'll probably need to lookup MS Office XML formats for XLS and customize
Once the file downloads (it will be .xls) you will likely get an error like "This file says it's in XLS format but might not be, do you want to open anyway?"
You will likely need to "Save As" some valid excel format to force it to convert from HTML in the excel document body.
I've used this fairly extensively in Chrome / Firefox / Internet Explorer but you should test yourself.
Currently set to freeze rows / columns. Change the XML and / or freeze vars at the top of the tableToExcel() function.
Resource-expensive on the client side. Probably best to use a server-side script of some kind.
There is a limit to the size of the table which is (I'm guessing) based on the size of the javascript variable. I used it with some tables like 50x300 but I wouldn't recommend on much larger tables.
It accepts a string of HTML table text (include <table>). You can format that HTML with inline styles (won't work with a CSS stylesheet unfortunately).
tableToExcel($('table').html(),'Worksheet Name');
Here's the functions. Enjoy!
function b64toBlob(b64Data, contentType, sliceSize) {
contentType = contentType || '';
sliceSize = sliceSize || 512;
var byteCharacters = atob(b64Data);
var byteArrays = [];
for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
var slice = byteCharacters.slice(offset, offset + sliceSize);
var byteNumbers = new Array(slice.length);
for (var i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
var byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
var blob = new Blob(byteArrays, {type: contentType});
return blob;
}
function tableToExcel(table,name) {
var freezeTopRowNumber = '4';
var freezeColNumber = '6';
var template = '<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40">';
template += '<head><!--[if gte mso 9]>';
template += '<xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet><x:Name>{worksheet}</x:Name>';
template += '<x:WorksheetOptions><x:Selected/><x:FreezePanes/><x:FrozenNoSplit/><x:SplitHorizontal>'+freezeTopRowNumber+'</x:SplitHorizontal><x:TopRowBottomPane>'+freezeTopRowNumber+'</x:TopRowBottomPane>';
template += '<x:SplitVertical>'+freezeColNumber+'</x:SplitVertical><x:LeftColumnRightPane>'+freezeColNumber+'</x:LeftColumnRightPane>';
template += '<x:ActivePane>2</x:ActivePane><x:Panes><x:Pane><x:Number>3</x:Number></x:Pane><x:Pane><x:Number>2</x:Number></x:Pane></x:Panes>';
template += '<x:ProtectContents>False</x:ProtectContents><x:ProtectObjects>False</x:ProtectObjects><x:ProtectScenarios>False</x:ProtectScenarios>';
template += '<x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]--></head>';
template += '<body>{table}</body></html>';
var base64 = function(s) { return window.btoa(unescape(encodeURIComponent(s))) };
var format = function(s, c) { return s.replace(/{(\w+)}/g, function(m, p) { return c[p]; }) };
var ctx = {worksheet: name || 'Worksheet', table: table};
var b = base64(format(template,ctx));
var blob = b64toBlob(b,'application/vnd.ms-excel');
var blobURL = URL.createObjectURL(blob);
window.location.href = blobURL;
}

Invalid character error while using atob() method

I have read a question in stack overflow with this code work in IE 10 but not work in ie9,
but still i am facing issue on this.
var image = canvas.toDataURL();
image = image.replace(/^data:[a-z]*;,/, '');
var byteString = atob(image);
var buffer = new ArrayBuffer(byteString.length);
var intArray = new Uint8Array(buffer);
for (var i = 0; i < byteString.length; i++) {
intArray[i] = byteString.charCodeAt(i);
}
blob = new Blob([buffer], {type: "image/png"});
window.navigator.msSaveOrOpenBlob(blob, "test.png");
while converting atob(image) it throw an exception
0x800a139e - JavaScript runtime error: InvalidCharacterError
i tried several things but nothing works...
i got this in image variable
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAcIAAAGQCAYAAAA9XmC5AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAABJFSURBVHhe7dvPa5x3fsDxeaR0l3UO9dLuSnJLE3rpzc6pp0KUQ2MLL8Xk1BZWt+7BhmKaGYctfKR3ng8vjyZTE6W5St98PZnF9ZO/NeZsiyWt0+c//RUWQBHxIkQjlBGsEx7Eb/7ZbqQF23zz22vvf+L6+f/Um4BQDeNRqPtvMoy194TQsc5EcIRatv2SZnmY9L1HOOUuDPdADpJCOEIRfQex7BaviCzkXsRxwc5At3kyzJwxMoXZmZfdLnZ7/c3yxwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAvkF6vf8DAs32KwowBEUAAAAASUVORK5CYII=
please help me..
Thanks in advance..
1) your base64 encoded string probably is not fully valid. you can try to use this code instead of atob
var decodeBase64 = function(s) {
var e={},i,b=0,c,x,l=0,a,r='',w=String.fromCharCode,L=s.length;
var A="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
for(i=0;i<64;i++){e[A.charAt(i)]=i;}
for(x=0;x<L;x++){
c=e[s.charAt(x)];b=(b<<6)+c;l+=6;
while(l>=8){((a=(b>>>(l-=8))&0xff)||(x<(L-2)))&&(r+=w(a));}
}
return r;
};
2) I think it should be image = image.replace(/^[^,]+,/, '');
3) As far as I know, support of the Blob in IE starts from version 10 - https://developer.mozilla.org/en-US/docs/Web/API/Blob
In case someone reachs this and the image = image.replace(/^[^,]+,/, ''); solution doesn't work for them, I got the same error calling atob function in IE11.
In my case, the error was caused because the base64 string had a carriage return each 76 characters.
This wasn't a problem for Chrome or Firefox, but IE11 produced the InvalidCharacterError.
b64Data = b64Data.replace(/\r\n/g, ''); solved my problem.

Convert base64 string to ArrayBuffer

I need to convert a base64 encode string into an ArrayBuffer.
The base64 strings are user input, they will be copy and pasted from an email, so they're not there when the page is loaded.
I would like to do this in javascript without making an ajax call to the server if possible.
I found those links interesting, but they didt'n help me:
ArrayBuffer to base64 encoded string
this is about the opposite conversion, from ArrayBuffer to base64, not the other way round
http://jsperf.com/json-vs-base64/2
this looks good but i can't figure out how to use the code.
Is there an easy (maybe native) way to do the conversion? thanks
Try this:
function _base64ToArrayBuffer(base64) {
var binary_string = window.atob(base64);
var len = binary_string.length;
var bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}
Using TypedArray.from:
Uint8Array.from(atob(base64_string), c => c.charCodeAt(0))
Performance to be compared with the for loop version of Goran.it answer.
For Node.js users:
const myBuffer = Buffer.from(someBase64String, 'base64');
myBuffer will be of type Buffer which is a subclass of Uint8Array. Unfortunately, Uint8Array is NOT an ArrayBuffer as the OP was asking for. But when manipulating an ArrayBuffer I almost always wrap it with Uint8Array or something similar, so it should be close to what's being asked for.
Goran.it's answer does not work because of unicode problem in javascript - https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding.
I ended up using the function given on Daniel Guerrero's blog: http://blog.danguer.com/2011/10/24/base64-binary-decoding-in-javascript/
Function is listed on github link: https://github.com/danguer/blog-examples/blob/master/js/base64-binary.js
Use these lines
var uintArray = Base64Binary.decode(base64_string);
var byteArray = Base64Binary.decodeArrayBuffer(base64_string);
Async solution, it's better when the data is big:
// base64 to buffer
function base64ToBufferAsync(base64) {
var dataUrl = "data:application/octet-binary;base64," + base64;
fetch(dataUrl)
.then(res => res.arrayBuffer())
.then(buffer => {
console.log("base64 to buffer: " + new Uint8Array(buffer));
})
}
// buffer to base64
function bufferToBase64Async( buffer ) {
var blob = new Blob([buffer], {type:'application/octet-binary'});
console.log("buffer to blob:" + blob)
var fileReader = new FileReader();
fileReader.onload = function() {
var dataUrl = fileReader.result;
console.log("blob to dataUrl: " + dataUrl);
var base64 = dataUrl.substr(dataUrl.indexOf(',')+1)
console.log("dataUrl to base64: " + base64);
};
fileReader.readAsDataURL(blob);
}
Javascript is a fine development environment so it seems odd than it doesn't provide a solution to this small problem. The solutions offered elsewhere on this page are potentially slow. Here is my solution. It employs the inbuilt functionality that decodes base64 image and sound data urls.
var req = new XMLHttpRequest;
req.open('GET', "data:application/octet;base64," + base64Data);
req.responseType = 'arraybuffer';
req.onload = function fileLoaded(e)
{
var byteArray = new Uint8Array(e.target.response);
// var shortArray = new Int16Array(e.target.response);
// var unsignedShortArray = new Int16Array(e.target.response);
// etc.
}
req.send();
The send request fails if the base 64 string is badly formed.
The mime type (application/octet) is probably unnecessary.
Tested in chrome. Should work in other browsers.
Pure JS - no string middlestep (no atob)
I write following function which convert base64 in direct way (without conversion to string at the middlestep). IDEA
get 4 base64 characters chunk
find index of each character in base64 alphabet
convert index to 6-bit number (binary string)
join four 6 bit numbers which gives 24-bit numer (stored as binary string)
split 24-bit string to three 8-bit and covert each to number and store them in output array
corner case: if input base64 string ends with one/two = char, remove one/two numbers from output array
Below solution allows to process large input base64 strings. Similar function for convert bytes to base64 without btoa is HERE
function base64ToBytesArr(str) {
const abc = [..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"]; // base64 alphabet
let result = [];
for(let i=0; i<str.length/4; i++) {
let chunk = [...str.slice(4*i,4*i+4)]
let bin = chunk.map(x=> abc.indexOf(x).toString(2).padStart(6,0)).join('');
let bytes = bin.match(/.{1,8}/g).map(x=> +('0b'+x));
result.push(...bytes.slice(0,3 - (str[4*i+2]=="=") - (str[4*i+3]=="=")));
}
return result;
}
// --------
// TEST
// --------
let test = "Alice's Adventure in Wonderland.";
console.log('test string:', test.length, test);
let b64_btoa = btoa(test);
console.log('encoded string:', b64_btoa);
let decodedBytes = base64ToBytesArr(b64_btoa); // decode base64 to array of bytes
console.log('decoded bytes:', JSON.stringify(decodedBytes));
let decodedTest = decodedBytes.map(b => String.fromCharCode(b) ).join``;
console.log('Uint8Array', JSON.stringify(new Uint8Array(decodedBytes)));
console.log('decoded string:', decodedTest.length, decodedTest);
Caution!
If you want to decode base64 to STRING (not bytes array) and you know that result contains utf8 characters then atob will fail in general e.g. for character 💩 the atob("8J+SqQ==") will give wrong result . In this case you can use above solution and convert result bytes array to string in proper way e.g. :
function base64ToBytesArr(str) {
const abc = [..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"]; // base64 alphabet
let result = [];
for(let i=0; i<str.length/4; i++) {
let chunk = [...str.slice(4*i,4*i+4)]
let bin = chunk.map(x=> abc.indexOf(x).toString(2).padStart(6,0)).join('');
let bytes = bin.match(/.{1,8}/g).map(x=> +('0b'+x));
result.push(...bytes.slice(0,3 - (str[4*i+2]=="=") - (str[4*i+3]=="=")));
}
return result;
}
// --------
// TEST
// --------
let testB64 = "8J+SqQ=="; // for string: "💩";
console.log('input base64 :', testB64);
let decodedBytes = base64ToBytesArr(testB64); // decode base64 to array of bytes
console.log('decoded bytes :', JSON.stringify(decodedBytes));
let result = new TextDecoder("utf-8").decode(new Uint8Array(decodedBytes));
console.log('properly decoded string :', result);
let result_atob = atob(testB64);
console.log('decoded by atob :', result_atob);
Snippets tested 2022-08-04 on: chrome 103.0.5060.134 (arm64), safari 15.2, firefox 103.0.1 (64 bit), edge 103.0.1264.77 (arm64), and node-js v12.16.1
I would strongly suggest using an npm package implementing correctly the base64 specification.
The best one I know is rfc4648
The problem is that btoa and atob use binary strings instead of Uint8Array and trying to convert to and from it is cumbersome. Also there is a lot of bad packages in npm for that. I lose a lot of time before finding that one.
The creators of that specific package did a simple thing: they took the specification of Base64 (which is here by the way) and implemented it correctly from the beginning to the end. (Including other formats in the specification that are also useful like Base64-url, Base32, etc ...) That doesn't seem a lot but apparently that was too much to ask to the bunch of other libraries.
So yeah, I know I'm doing a bit of proselytism but if you want to avoid losing your time too just use rfc4648.
I used the accepted answer to this question to create base64Url string <-> arrayBuffer conversions in the realm of base64Url data transmitted via ASCII-cookie [atob, btoa are base64[with +/]<->js binary string], so I decided to post the code.
Many of us may want both conversions and client-server communication may use the base64Url version (though a cookie may contain +/ as well as -_ characters if I understand well, only ",;\ characters and some wicked characters from the 128 ASCII are disallowed). But a url cannot contain / character, hence the wider use of b64 url version which of course not what atob-btoa supports...
Seeing other comments, I would like to stress that my use case here is base64Url data transmission via url/cookie and trying to use this crypto data with the js crypto api (2017) hence the need for ArrayBuffer representation and b64u <-> arrBuff conversions... if array buffers represent other than base64 (part of ascii) this conversion wont work since atob, btoa is limited to ascii(128). Check out an appropriate converter like below:
The buff -> b64u version is from a tweet from Mathias Bynens, thanks for that one (too)! He also wrote a base64 encoder/decoder:
https://github.com/mathiasbynens/base64
Coming from java, it may help when trying to understand the code that java byte[] is practically js Int8Array (signed int) but we use here the unsigned version Uint8Array since js conversions work with them. They are both 256bit, so we call it byte[] in js now...
The code is from a module class, that is why static.
//utility
/**
* Array buffer to base64Url string
* - arrBuff->byte[]->biStr->b64->b64u
* #param arrayBuffer
* #returns {string}
* #private
*/
static _arrayBufferToBase64Url(arrayBuffer) {
console.log('base64Url from array buffer:', arrayBuffer);
let base64Url = window.btoa(String.fromCodePoint(...new Uint8Array(arrayBuffer)));
base64Url = base64Url.replaceAll('+', '-');
base64Url = base64Url.replaceAll('/', '_');
console.log('base64Url:', base64Url);
return base64Url;
}
/**
* Base64Url string to array buffer
* - b64u->b64->biStr->byte[]->arrBuff
* #param base64Url
* #returns {ArrayBufferLike}
* #private
*/
static _base64UrlToArrayBuffer(base64Url) {
console.log('array buffer from base64Url:', base64Url);
let base64 = base64Url.replaceAll('-', '+');
base64 = base64.replaceAll('_', '/');
const binaryString = window.atob(base64);
const length = binaryString.length;
const bytes = new Uint8Array(length);
for (let i = 0; i < length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
console.log('array buffer:', bytes.buffer);
return bytes.buffer;
}
made a ArrayBuffer from a base64:
function base64ToArrayBuffer(base64) {
var binary_string = window.atob(base64);
var len = binary_string.length;
var bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}
I was trying to use above code and It's working fine.
The result of atob is a string that is separated with some comma
,
A simpler way is to convert this string to a json array string and after that parse it to a byteArray
below code can simply be used to convert base64 to an array of number
let byteArray = JSON.parse('['+atob(base64)+']');
let buffer = new Uint8Array(byteArray);
Solution without atob
I've seen many people complaining about using atob and btoa in the replies. There are some issues to take into account when using them.
There's a solution without using them in the MDN page about Base64. Below you can find the code to convert a base64 string into a Uint8Array copied from the docs.
Note that the function below returns a Uint8Array. To get the ArrayBuffer version you just need to do uintArray.buffer.
function b64ToUint6(nChr) {
return nChr > 64 && nChr < 91
? nChr - 65
: nChr > 96 && nChr < 123
? nChr - 71
: nChr > 47 && nChr < 58
? nChr + 4
: nChr === 43
? 62
: nChr === 47
? 63
: 0;
}
function base64DecToArr(sBase64, nBlocksSize) {
const sB64Enc = sBase64.replace(/[^A-Za-z0-9+/]/g, "");
const nInLen = sB64Enc.length;
const nOutLen = nBlocksSize
? Math.ceil(((nInLen * 3 + 1) >> 2) / nBlocksSize) * nBlocksSize
: (nInLen * 3 + 1) >> 2;
const taBytes = new Uint8Array(nOutLen);
let nMod3;
let nMod4;
let nUint24 = 0;
let nOutIdx = 0;
for (let nInIdx = 0; nInIdx < nInLen; nInIdx++) {
nMod4 = nInIdx & 3;
nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << (6 * (3 - nMod4));
if (nMod4 === 3 || nInLen - nInIdx === 1) {
nMod3 = 0;
while (nMod3 < 3 && nOutIdx < nOutLen) {
taBytes[nOutIdx] = (nUint24 >>> ((16 >>> nMod3) & 24)) & 255;
nMod3++;
nOutIdx++;
}
nUint24 = 0;
}
}
return taBytes;
}
If you're interested in the reverse operation, ArrayBuffer to base64, you can find how to do it in the same link.

Categories

Resources