In my node.js application I did an npm install btoa-atob
so that I could use the btoa()
and atob()
functions which are native in client-side javascript but for some reason weren't included in node. The new directory showed up in my node_modules
folder, which itself is in root alongside app.js
. Then I made sure to add btoa-atob as a dependency in my package.json
file which is in root.
However, for some reason, it still will not work.
console.log(btoa("Hello World!"));
^ should output "SGVsbG8gV29ybGQh" to the console, but instead, I get the error:
btoa is not defined.
Did I not do the install properly? What did I overlook?
This question is related to
node.js
The solutions posted here don't work in non-ascii characters (i.e. if you plan to exchange base64 between Node.js and a browser). In order to make it work you have to mark the input text as 'binary'.
Buffer.from('Hélló wórld!!', 'binary').toString('base64')
This gives you SOlsbPMgd/NybGQhIQ==
. If you make atob('SOlsbPMgd/NybGQhIQ==')
in a browser it will decode it in the right way. It will do it right also in Node.js via:
Buffer.from('SOlsbPMgd/NybGQhIQ==', 'base64').toString('binary')
If you don't do the "binary part", you will decode wrongly the special chars.
Here's a concise universal solution for base64 encoding:
const nodeBtoa = (b) => Buffer.from(b).toString('base64');
export const base64encode = typeof btoa !== 'undefined' ? btoa : nodeBtoa;
I understand this is a discussion point for a node application, but in the interest of universal JavaScript applications running on a node server, which is how I arrived at this post, I have been researching this for a universal / isomorphic react app I have been building, and the package abab
worked for me. In fact it was the only solution I could find that worked, rather than using the Buffer method also mentioned (I had typescript issues).
(This package is used by jsdom
, which in turn is used by the window
package.)
Getting back to my point; based on this, perhaps if this functionality is already written as an npm package like the one you mentioned, and has it's own algorithm based on W3 spec, you could install and use the abab
package rather than writing you own function that may or may not be accurate based on encoding.
---EDIT---
I started having weird issues today with encoding (not sure why it's started happening now) with package abab
. It seems to encode correctly most of the time, but sometimes on front end it encodes incorrectly. Spent a long time trying to debug, but switched to package base-64
as recommended, and it worked straight away. Definitely seemed to be down to the base64 algorithm of abab
.
export const universalBtoa = str => {
try {
return btoa(str);
} catch (err) {
return Buffer.from(str).toString('base64');
}
};
export const universalAtob = b64Encoded => {
try {
return atob(b64Encoded);
} catch (err) {
return Buffer.from(b64Encoded, 'base64').toString();
}
};
My team ran into this problem when using Node with React Native and PouchDB. Here is how we solved it...
NPM install buffer:
$ npm install --save buffer
Ensure Buffer
, btoa
, and atob
are loaded as a globals:
global.Buffer = global.Buffer || require('buffer').Buffer;
if (typeof btoa === 'undefined') {
global.btoa = function (str) {
return new Buffer(str, 'binary').toString('base64');
};
}
if (typeof atob === 'undefined') {
global.atob = function (b64Encoded) {
return new Buffer(b64Encoded, 'base64').toString('binary');
};
}
Maybe you don't need it anymore but if someone needs this using node: https://www.npmjs.com/package/btoa
I found that although the shims from answers above worked, they did not match the behaviour of desktop browsers' implementations of btoa()
and atob()
:
const btoa = function(str){ return Buffer.from(str).toString('base64'); }
// returns "4pyT", yet in desktop Chrome would throw an error.
btoa('?');
// returns "fsO1w6bCvA==", yet in desktop Chrome would return "fvXmvA=="
btoa(String.fromCharCode.apply(null, new Uint8Array([0x7e, 0xf5, 0xe6, 0xbc])));
As it turns out, Buffer
instances represent/interpret strings encoded in UTF-8 by default. By contrast, in desktop Chrome, you can't even input a string that contains characters outside of the latin1 range into btoa()
, as it will throw an exception: Uncaught DOMException: Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range.
Therefore, you need to explicitly set the encoding type to latin1
in order for your Node.js shim to match the encoding type of desktop Chrome:
const btoaLatin1 = function(str) { return Buffer.from(str, 'latin1').toString('base64'); }
const atobLatin1 = function(b64Encoded) {return Buffer.from(b64Encoded, 'base64').toString('latin1');}
const btoaUTF8 = function(str) { return Buffer.from(str, 'utf8').toString('base64'); }
const atobUTF8 = function(b64Encoded) {return Buffer.from(b64Encoded, 'base64').toString('utf8');}
btoaLatin1('?'); // returns "Ew==" (would be preferable for it to throw error because this is undecodable)
atobLatin1(btoa('?')); // returns "\u0019" (END OF MEDIUM)
btoaUTF8('?'); // returns "4pyT"
atobUTF8(btoa('?')); // returns "?"
// returns "fvXmvA==", just like desktop Chrome
btoaLatin1(String.fromCharCode.apply(null, new Uint8Array([0x7e, 0xf5, 0xe6, 0xbc])));
// returns "fsO1w6bCvA=="
btoaUTF8(String.fromCharCode.apply(null, new Uint8Array([0x7e, 0xf5, 0xe6, 0xbc])));
Same problem with the 'script' plugin in the Atom editor, which is an old version of node, not having btoa(), nor atob(), nor does it support the Buffer datatype. Following code does the trick:
var Base64 = new function() {_x000D_
var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="_x000D_
this.encode = function(input) {_x000D_
var output = "";_x000D_
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;_x000D_
var i = 0;_x000D_
input = Base64._utf8_encode(input);_x000D_
while (i < input.length) {_x000D_
chr1 = input.charCodeAt(i++);_x000D_
chr2 = input.charCodeAt(i++);_x000D_
chr3 = input.charCodeAt(i++);_x000D_
enc1 = chr1 >> 2;_x000D_
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);_x000D_
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);_x000D_
enc4 = chr3 & 63;_x000D_
if (isNaN(chr2)) {_x000D_
enc3 = enc4 = 64;_x000D_
} else if (isNaN(chr3)) {_x000D_
enc4 = 64;_x000D_
}_x000D_
output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4);_x000D_
}_x000D_
return output;_x000D_
}_x000D_
_x000D_
this.decode = function(input) {_x000D_
var output = "";_x000D_
var chr1, chr2, chr3;_x000D_
var enc1, enc2, enc3, enc4;_x000D_
var i = 0;_x000D_
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");_x000D_
while (i < input.length) {_x000D_
enc1 = keyStr.indexOf(input.charAt(i++));_x000D_
enc2 = keyStr.indexOf(input.charAt(i++));_x000D_
enc3 = keyStr.indexOf(input.charAt(i++));_x000D_
enc4 = keyStr.indexOf(input.charAt(i++));_x000D_
chr1 = (enc1 << 2) | (enc2 >> 4);_x000D_
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);_x000D_
chr3 = ((enc3 & 3) << 6) | enc4;_x000D_
output = output + String.fromCharCode(chr1);_x000D_
if (enc3 != 64) {_x000D_
output = output + String.fromCharCode(chr2);_x000D_
}_x000D_
if (enc4 != 64) {_x000D_
output = output + String.fromCharCode(chr3);_x000D_
}_x000D_
}_x000D_
output = Base64._utf8_decode(output);_x000D_
return output;_x000D_
}_x000D_
_x000D_
this._utf8_encode = function(string) {_x000D_
string = string.replace(/\r\n/g, "\n");_x000D_
var utftext = "";_x000D_
for (var n = 0; n < string.length; n++) {_x000D_
var c = string.charCodeAt(n);_x000D_
if (c < 128) {_x000D_
utftext += String.fromCharCode(c);_x000D_
} else if ((c > 127) && (c < 2048)) {_x000D_
utftext += String.fromCharCode((c >> 6) | 192);_x000D_
utftext += String.fromCharCode((c & 63) | 128);_x000D_
} else {_x000D_
utftext += String.fromCharCode((c >> 12) | 224);_x000D_
utftext += String.fromCharCode(((c >> 6) & 63) | 128);_x000D_
utftext += String.fromCharCode((c & 63) | 128);_x000D_
}_x000D_
}_x000D_
return utftext;_x000D_
}_x000D_
_x000D_
this._utf8_decode = function(utftext) {_x000D_
var string = "";_x000D_
var i = 0;_x000D_
var c = 0,_x000D_
c1 = 0,_x000D_
c2 = 0,_x000D_
c3 = 0;_x000D_
while (i < utftext.length) {_x000D_
c = utftext.charCodeAt(i);_x000D_
if (c < 128) {_x000D_
string += String.fromCharCode(c);_x000D_
i++;_x000D_
} else if ((c > 191) && (c < 224)) {_x000D_
c2 = utftext.charCodeAt(i + 1);_x000D_
string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));_x000D_
i += 2;_x000D_
} else {_x000D_
c2 = utftext.charCodeAt(i + 1);_x000D_
c3 = utftext.charCodeAt(i + 2);_x000D_
string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));_x000D_
i += 3;_x000D_
}_x000D_
}_x000D_
return string;_x000D_
}_x000D_
}()_x000D_
_x000D_
var btoa = Base64.encode;_x000D_
var atob = Base64.decode;_x000D_
_x000D_
console.log("btoa('A') = " + btoa('A'));_x000D_
console.log("btoa('QQ==') = " + atob('QQ=='));_x000D_
console.log("btoa('B') = " + btoa('B'));_x000D_
console.log("btoa('Qg==') = " + atob('Qg=='));
_x000D_
I have a code shared between server and client and I needed an implementation of btoa inside it. I tried doing something like:
const btoaImplementation = btoa || (str => Buffer.from(str).toString('base64'));
but the Server would crush with:
ReferenceError: btoa is not defined
while Buffer
is not defined on the client.
I couldn't check window.btoa (it's a shared code, remember?)
So I ended up with this implementation:
const btoaImplementation = str => {
try {
return btoa(str);
} catch(err) {
return Buffer.from(str).toString('base64')
}
};
Source: Stackoverflow.com