ArrayBuffer to base64 encoded string - javascript

I need an efficient (read native) way to convert an ArrayBuffer to a base64 string which needs to be used on a multipart post.

function _arrayBufferToBase64( buffer ) {
var binary = '';
var bytes = new Uint8Array( buffer );
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode( bytes[ i ] );
}
return window.btoa( binary );
}
but, non-native implementations are faster e.g. https://gist.github.com/958841
see http://jsperf.com/encoding-xhr-image-data/6
Updated benchmarks: https://jsben.ch/wnaZC

This works fine for me:
var base64String = btoa(String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)));
In ES6, the syntax is a little simpler:
const base64String = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer)));
As pointed out in the comments, this method may result in a runtime error in some browsers when the ArrayBuffer is large. The exact size limit is implementation dependent in any case.

For those who like it short, here's an other one using Array.reduce which will not cause stack overflow:
var base64 = btoa(
new Uint8Array(arrayBuffer)
.reduce((data, byte) => data + String.fromCharCode(byte), '')
);

There is another asynchronous way use Blob and FileReader.
I didn't test the performance. But it is a different way of thinking.
function arrayBufferToBase64( buffer, callback ) {
var blob = new Blob([buffer],{type:'application/octet-binary'});
var reader = new FileReader();
reader.onload = function(evt){
var dataurl = evt.target.result;
callback(dataurl.substr(dataurl.indexOf(',')+1));
};
reader.readAsDataURL(blob);
}
//example:
var buf = new Uint8Array([11,22,33]);
arrayBufferToBase64(buf, console.log.bind(console)); //"CxYh"

const blob = new Blob([array]);
const reader = new FileReader();
reader.onload = (event) => {
const dataUrl = event.target.result;
const [_, base64] = dataUrl.split(',');
// do something with base64
};
reader.readAsDataURL(blob);
Or as a promisified utility:
async function encode(array) {
return new Promise((resolve) => {
const blob = new Blob([array]);
const reader = new FileReader();
reader.onload = (event) => {
const dataUrl = event.target.result;
const [_, base64] = dataUrl.split(',');
resolve(base64);
};
reader.readAsDataURL(blob);
});
}
const encoded = await encode(typedArray);

The OP did not specify the Running Enviroment but if you are using Node.JS there is a very simple way to do such thing.
Accordig with the official Node.JS docs
https://nodejs.org/api/buffer.html#buffer_buffers_and_character_encodings
// This step is only necessary if you don't already have a Buffer Object
const buffer = Buffer.from(yourArrayBuffer);
const base64String = buffer.toString('base64');
Also, If you are running under Angular for example, the Buffer Class will also be made available in a Browser Environment.

My recommendation for this is to NOT use native btoa strategies—as they don't correctly encode all ArrayBuffer's…
rewrite the DOMs atob() and btoa()
Since DOMStrings are 16-bit-encoded strings, in most browsers calling window.btoa on a Unicode string will cause a Character Out Of Range exception if a character exceeds the range of a 8-bit ASCII-encoded character.
While I have never encountered this exact error, I have found that many of the ArrayBuffer's I have tried to encode have encoded incorrectly.
I would either use MDN recommendation or gist.
https://github.com/beatgammit/base64-js
https://gist.github.com/jonleighton/958841

I used this and works for me.
function arrayBufferToBase64( buffer ) {
var binary = '';
var bytes = new Uint8Array( buffer );
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode( bytes[ i ] );
}
return window.btoa( binary );
}
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;
}

Below are 2 simple functions for converting Uint8Array to Base64 String and back again
arrayToBase64String(a) {
return btoa(String.fromCharCode(...a));
}
base64StringToArray(s) {
let asciiString = atob(s);
return new Uint8Array([...asciiString].map(char => char.charCodeAt(0)));
}

If you're okay with adding a library, base64-arraybuffer:
yarn add base64-arraybuffer
then:
encode(buffer) - Encodes ArrayBuffer into base64 string
decode(str) - Decodes base64 string to ArrayBuffer

This worked for me:
Buffer.from(myArrayBuffer).toString("base64");

ABtoB64(ab) {
return new Promise(res => {
const fr = new FileReader();
fr.onload = ({target: {result: s}}) => res(s.slice(s.indexOf(';base64,') + 8));
fr.readAsDataURL(new Blob([ab]));
});
}
asynchronous method using file reader.

You can derive a normal array from the ArrayBuffer by using Array.prototype.slice.
Use a function like Array.prototype.map to convert bytes in to characters and join them together to forma string.
function arrayBufferToBase64(ab){
var dView = new Uint8Array(ab); //Get a byte view
var arr = Array.prototype.slice.call(dView); //Create a normal array
var arr1 = arr.map(function(item){
return String.fromCharCode(item); //Convert
});
return window.btoa(arr1.join('')); //Form a string
}
This method is faster since there are no string concatenations running in it.

In the Browser suggested solutions with btoa seem fine.
But in Node.js btoa is deprecated
It is recommended to use buffer.toString(encoding)
like
const myString = buffer.toString("base64")

i use TextDecode api to convert it to normal text and then convert it to Base64
const uint = new Uint8Array([ 73, 32, 108, 111, 118, 101, 32, 121, 111, 117 ]).buffer
const decoder = new TextDecoder()
const decodedText = decoder.decode(uint)
const base64Code = btoa(decodedText)

Use uint8-to-b64 package to do encoding/decoding in browser and Node.js

By my side, using Chrome navigator, I had to use DataView() to read an arrayBuffer
function _arrayBufferToBase64( tabU8A ) {
var binary = '';
let lecteur_de_donnees = new DataView(tabU8A);
var len = lecteur_de_donnees.byteLength;
var chaine = '';
var pos1;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode( lecteur_de_donnees.getUint8( i ) );
}
chaine = window.btoa( binary )
return chaine;}

function _arrayBufferToBase64(uarr) {
var strings = [], chunksize = 0xffff;
var len = uarr.length;
for (var i = 0; i * chunksize < len; i++){
strings.push(String.fromCharCode.apply(null, uarr.subarray(i * chunksize, (i + 1) * chunksize)));
}
return strings.join("");
}
This is better, if you use JSZip for unpack archive from string

Related

Convert base64 string into byte array through javascript

I want to convert "base64" string into bytes array through the javascript.
I received string from the URL like.
("data:image/jpeg;base64,/9j/4QN6RXhpZgAASUkqAAgAAAAIA)
And i want to convert the string into bytes array. because i need this byte array to send Rest API endpoint. The rest API Content-type=application/octet-stream.
You can use XMLHttpRequest to do the dirty work for you:
var url = document.createElement("canvas").toDataURL(); // some data-uri
var xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.responseType = "arraybuffer";
xhr.onload = function() {
// result = ArrayBuffer, from here assign a view to it
if (xhr.status === 200) console.log(new Uint8Array(xhr.response));
};
xhr.send();
It's a little more code than using atob() but all conversion happens internally. It's async too which can help with larger Data-URIs.
In newer browsers (which supports it) you can instead use fetch():
// note: atm not all browsers support these features.
fetch(document.createElement("canvas").toDataURL()) // pass in some data-uri
.then(function(response) {return response.arrayBuffer()})
.then(function(buffer) {
console.log(new Uint8Array(buffer));
});
You can try with the following;
function test(base64StringFromURL)
{
var parts = base64StringFromURL.split(";base64,");
var contentType = parts[0].replace("data:", "");
var base64 = parts[1];
var byteArray = base64ToByteArray(base64);
..
}
function base64ToByteArray(base64String) {
try {
var sliceSize = 1024;
var byteCharacters = atob(base64String);
var bytesLength = byteCharacters.length;
var slicesCount = Math.ceil(bytesLength / sliceSize);
var byteArrays = new Array(slicesCount);
for (var sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) {
var begin = sliceIndex * sliceSize;
var end = Math.min(begin + sliceSize, bytesLength);
var bytes = new Array(end - begin);
for (var offset = begin, i = 0; offset < end; ++i, ++offset) {
bytes[i] = byteCharacters[offset].charCodeAt(0);
}
byteArrays[sliceIndex] = new Uint8Array(bytes);
}
return byteArrays;
} catch (e) {
console.log("Couldn't convert to byte array: " + e);
return undefined;
}
}
This would prove to be much short solution.
const byteArray = new Buffer(base64String.replace(/^[\w\d;:\/]+base64\,/g, ''), 'base64');
base64String is the string containing the base 64 string.
byteArray is the array you need.
The regex replacement is optional and is just there to deal with prefix as in the case of dataurl string.

Converting arraybuffer to string : Maximum call stack size exceeded

This is my code.
var xhr = new XMLHttpRequest();
xhr.open('GET',window.location.href, true);
xhr.responseType = "arraybuffer";
xhr.onload = function(event) {
debugger;
console.log(" coverting array buffer to string ");
alert(String.fromCharCode.apply(null, new Uint8Array(this.response)));
};
xhr.send();
That request is being made to a PDF URL which is around 3 MB in size. I have read a few threads with same error, Maximum call stack size exceeded, telling me that there must be some recursive call but I do not see any recursive call here. Can anyone help?
I had the same problem and I finally used this code:
function _arrayBufferToBase64( buffer ) {
var binary = '';
var bytes = new Uint8Array( buffer );
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode( bytes[ i ] );
}
return window.btoa( binary );
}
The error is caused by a limitation in the number of function arguments. See "RangeError: Maximum call stack size exceeded" Why?
Instead of String.fromCharCode.apply(), use e. g. a TextEncoder. See Uint8Array to string in Javascript
this.response.arrayBuffer()
.then((buf) => {
const uint8Array = new Uint8Array(buf);
const data = uint8Array.reduce((acc, i) => acc += String.fromCharCode.apply(null, [i]), '');
return data;
})
Thank you so much Anthony O, you saved my 10 days work.
small modification in my scenario is:
Angular 7 :
/** Convert Uint8Array to string */
private static arrayBufferToBase64( buffer: Uint8Array ) {
var binary = '';
var bytes = new Uint8Array( buffer );
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode( bytes[ i ] );
}
return binary;
}

how to open base64 String response from server in external viewer (viewer.html) using pdf.js?

In my application i am sending a path to the server and which returns me a pdf file contents in base64 string. Now i want to show this string in external viewer (viewer.html) using pdf.js. I am doing in this in following way, what wrong am i doing?
var res="JVBERi0xLjUK..."; // Base64 String from response shortened
var pdfData = base64ToUint8Array(res);
window.open('pdfJs/viewer.html?file='+pdfData);
function base64ToUint8Array(base64) {
var raw = atob(base64); //This is a native function that decodes a base64-encoded string.
var uint8Array = new Uint8Array(new ArrayBuffer(raw.length));
for(var i = 0; i < raw.length; i++) {
uint8Array[i] = raw.charCodeAt(i);
}
return uint8Array;
}
Output i am getting now:
I have gone through some of answers but cant sort it out. So please help me.
The Uint8Array cannot be passed via query string. Use PDFViewerApplication.open() method on the opened window, e.g.
var w = window.open('web/viewer.html?file=');
w.addEventListener('load', function () {
var res="JVBERi0xLjUK..."; // Base64 String from response shortened
var pdfData = base64ToUint8Array(res);
w.PDFViewerApplication.open(pdfData);
function base64ToUint8Array(base64) {
var raw = atob(base64);
var uint8Array = new Uint8Array(raw.length);
for(var i = 0; i < raw.length; i++) {
uint8Array[i] = raw.charCodeAt(i);
}
return uint8Array;
}
}, true);

Encrypting files with SJCL client-side

I have problem encrypting files with SJCL and javascript.
I have managed to encrypt text-files with using FileReader API and readAsBinaryString.
When it comes to encrypting pdf/png/.. then problem arrises probably due to encoding.
I found that I can use readAsArrayBuffer which suits this task perfectly, so I basically read file and create new typed array with new Uint8Array() but i dont know how exactly I am supposed to encrypt such a data.
Here's my code:
/** Convert from an array of bytes to a bitArray. */
function toBitArrayCodec(bytes) {
var out = [], i, tmp=0;
for (i=0; i<bytes.length; i++) {
tmp = tmp << 8 | bytes[i];
if ((i&3) === 3) {
out.push(tmp);
tmp = 0;
}
}
if (i&3) {
out.push(sjcl.bitArray.partial(8*(i&3), tmp));
}
return out;
}
/** Convert from a bitArray to an array of bytes. */
function fromBitArrayCodec(arr) {
var out = [], bl = sjcl.bitArray.bitLength(arr), i, tmp;
for (i=0; i<bl/8; i++) {
if ((i&3) === 0) {
tmp = arr[i/4];
}
out.push(tmp >>> 24);
tmp <<= 8;
}
return out;
}
var reader = new FileReader();
reader.readAsArrayBuffer(fileData); //filedata comes from function
reader.onload = function() {
var bytes = new Uint8Array(reader.result);
var bits = toBitArrayCodec(bytes);
var crypt = sjcl.encrypt("aaaaa", bits);
var decrypt = sjcl.decrypt("aaaaa", crypt);
var byteNumbers = fromBitArrayCodec(decrypt);
var byteArray = new Uint8Array(byteNumbers);
saveData(byteArray, 'png.png');
I am getting error on
Uncaught URIError: URI malformed sjcl.js:12sjcl.codec.utf8String.fromBits sjcl.js:12sjcl.json.decrypt sjcl.js:44reader.onload
I need to know how to encrypt uint8array or another alternative how to encrypt(pdf/png/..) files.
The plaintext in sjcl is expected to be utf8 encoded. Encrypting a manually built bitArray works, because the encryption is done on the bitArray and it doesn't have to be decoded. But at the end of the decryption is an encoding step which converts the recovered plaintext bitArray into a utf8string. This doesn't work, because it contains unprintable characters, because the source was probably binary.
The solution would be to encode it as Base64 before encrypting and convert it back after decrypting.
var bytes = new Uint8Array(reader.result);
var bits = toBitArrayCodec(bytes);
var base64bits = sjcl.codec.base64.fromBits(bits); // added
var crypt = sjcl.encrypt("aaaaa", base64bits);
var base64decrypt = sjcl.decrypt("aaaaa", crypt);
var decrypt = sjcl.codec.base64.toBits(base64decrypt); // added
var byteNumbers = fromBitArrayCodec(decrypt);
var byteArray = new Uint8Array(byteNumbers);

javascript how to Constructing a Signature?

i have this pseudo code and i would like to convert it into working code:
string constructSignature(string timestamp, string UID, string secretKey) {
baseString = timestamp + "_" + UID; // Construct a "base string" for signing
binaryBaseString = ConvertUTF8ToBytes(baseString); // Convert the base string into a binary array
binaryKey = ConvertFromBase64ToBytes(secretKey); // Convert secretKey from BASE64 to a binary array
binarySignature = hmacsha1(binaryKey, baseString); // Use the HMAC-SHA1 algorithm to calculate the signature
signature = ConvertToBase64(binarySignature); // Convert the signature to a BASE64
return signature;
}
any idea?
thanks
Some ideas:
Don't calculate BinaryBaseString, given that you never use it
Don't refer to UTF8 for no reason at all -- strings in JavaScript are Unicode, which is only distantly connected to UTF.
Don't put implementation details in your pseudocode -- especially ones you don't actually need (like assuming the secret key should be in "bytes", whatever that means, when in fact, the standard library takes and returns strings).
Don't write comments that just restate what the code is doing.
Which leaves us with:
var constructSignature = function(timestamp, UID, secretKey) {
return Crypto.HMAC(Crypto.SHA1, timestamp + "_" + UID, secretKey,
{ asString: true });
};
Here is a full implementation of your code:
let atob = function(a){
return new Buffer(a, 'base64').toString('binary');
}
function ConvertUTF8ToBytes(str) {
var utf8 = unescape(encodeURIComponent(str));
var arr = [];
for (var i = 0; i < utf8.length; i++) {
arr.push(utf8.charCodeAt(i));
}
return new Uint8Array(arr);
}
let ConvertFromBase64ToBytes = function (b64Data){
var byteCharacters = atob(b64Data);
var byteNumbers = new Array(byteCharacters.length);
for (var i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
return new Uint8Array(byteNumbers);
}
let constructSignature = function(timestamp, UID, secretKey) {
let baseString = timestamp + "_" + UID;
let binaryBaseString = ConvertUTF8ToBytes(baseString)
let binaryKey = ConvertFromBase64ToBytes(secretKey)
let binarySignature = Crypto.createHmac('sha1', binaryKey)
.update( binaryBaseString )
.digest('base64');
return binarySignature;
};

Categories

Resources