Node.js抛出“未定义btoa”错误

Node.js throws “btoa is not defined” error

在我的node.js应用程序中,我做了一个npm install btoa-atob,以便可以使用客户端javascript中原生的btoa()atob()函数,但由于某些原因而未包含在node中。 新目录显示在我的node_modules文件夹中,该文件夹本身位于app.js的根目录中。 然后,确保将btoa-atob作为依赖项添加到根目录下的package.json文件中。

但是,由于某种原因,它仍然无法工作。

1
console.log(btoa("Hello World!"));

^应该向控制台输出" SGVsbG8gV29ybGQh",但是,我得到了错误:

btoa is not defined.

我没有正确安装吗? 我忽略了什么?


" btoa-atob"模块不导出编程接口,它仅提供命令行实用程序。

如果您需要转换为Base64,可以使用Buffer进行:

1
console.log(Buffer.from('Hello World!').toString('base64'));

反向(假设您要解码的内容是utf8字符串):

1
console.log(Buffer.from(b64Encoded, 'base64').toString());

注意:在节点v4之前,请使用new Buffer而不是Buffer.from


此处发布的解决方案不适用于非ASCII字符(即,如果您计划在Node.js和浏览器之间交换base64)。为了使其正常工作,您必须将输入文本标记为"二进制"。

1
Buffer.from('Hélló wórld!!', 'binary').toString('base64')

这使您SOlsbPMgd/NybGQhIQ==。如果在浏览器中输入atob('SOlsbPMgd/NybGQhIQ=='),它将以正确的方式对其进行解码。它也可以通过以下方式在Node.js中做到这一点:

1
Buffer.from('SOlsbPMgd/NybGQhIQ==', 'base64').toString('binary')

如果不执行"二进制部分",则会错误地解码特殊字符。

我是从btoa npm包的实现中得到的:


我的团队在将Node与React Native和PouchDB一起使用时遇到了这个问题。这是我们解决的方法...

NPM安装缓冲区:

1
$ npm install --save buffer

确保Bufferbtoaatob作为全局变量加载:

1
2
3
4
5
6
7
8
9
10
11
12
13
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');
  };
}


我发现,尽管以上答案中的填充效果都可以,但是它们与桌面浏览器的btoa()atob()实现的行为不匹配:

1
2
3
4
5
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])));

事实证明,默认情况下Buffer实例表示/解释以UTF-8编码的字符串。相比之下,在台式机Chrome中,您甚至无法在btoa()中输入包含latin1范围之外的字符的字符串,因为它将引发异常:Uncaught DOMException: Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range.

因此,您需要将编码类型显式设置为latin1,以使您的Node.js填充程序与桌面Chrome的编码类型匹配:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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])));


我在服务器和客户端之间共享一个代码,并且需要在其中实现btoa。
我尝试做类似的事情:

1
const btoaImplementation =  btoa || (str => Buffer.from(str).toString('base64'));

但是服务器会崩溃:

ReferenceError: btoa is not defined

Buffer尚未在客户端上定义。

我无法检查window.btoa(这是共享代码,还记得吗?)

因此,我最终完成了该实现:

1
2
3
4
5
6
7
const btoaImplementation = str => {
    try {
        return btoa(str);
    } catch(err) {
        return Buffer.from(str).toString('base64')
    }
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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();
  }
};

这是针对base64编码的简洁通用解决方案:

1
2
const nodeBtoa = (b) => Buffer.from(b).toString('base64');
export const base64encode = typeof btoa !== 'undefined' ? btoa : nodeBtoa;

Atom编辑器中的"脚本"插件存在相同的问题,该插件是旧版本的节点,没有btoa()或atob(),也不支持Buffer数据类型。以下代码可以解决问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
var Base64 = new function() {
  var keyStr ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
  this.encode = function(input) {
    var output ="";
    var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
    var i = 0;
    input = Base64._utf8_encode(input);
    while (i < input.length) {
      chr1 = input.charCodeAt(i++);
      chr2 = input.charCodeAt(i++);
      chr3 = input.charCodeAt(i++);
      enc1 = chr1 >> 2;
      enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
      enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
      enc4 = chr3 & 63;
      if (isNaN(chr2)) {
        enc3 = enc4 = 64;
      } else if (isNaN(chr3)) {
        enc4 = 64;
      }
      output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4);
    }
    return output;
  }

  this.decode = function(input) {
    var output ="";
    var chr1, chr2, chr3;
    var enc1, enc2, enc3, enc4;
    var i = 0;
    input = input.replace(/[^A-Za-z0-9\\+\\/\\=]/g,"");
    while (i < input.length) {
      enc1 = keyStr.indexOf(input.charAt(i++));
      enc2 = keyStr.indexOf(input.charAt(i++));
      enc3 = keyStr.indexOf(input.charAt(i++));
      enc4 = keyStr.indexOf(input.charAt(i++));
      chr1 = (enc1 << 2) | (enc2 >> 4);
      chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
      chr3 = ((enc3 & 3) << 6) | enc4;
      output = output + String.fromCharCode(chr1);
      if (enc3 != 64) {
        output = output + String.fromCharCode(chr2);
      }
      if (enc4 != 64) {
        output = output + String.fromCharCode(chr3);
      }
    }
    output = Base64._utf8_decode(output);
    return output;
  }

  this._utf8_encode = function(string) {
    string = string.replace(/\
\
/g,"\
");
    var utftext ="";
    for (var n = 0; n < string.length; n++) {
      var c = string.charCodeAt(n);
      if (c < 128) {
        utftext += String.fromCharCode(c);
      } else if ((c > 127) && (c < 2048)) {
        utftext += String.fromCharCode((c >> 6) | 192);
        utftext += String.fromCharCode((c & 63) | 128);
      } else {
        utftext += String.fromCharCode((c >> 12) | 224);
        utftext += String.fromCharCode(((c >> 6) & 63) | 128);
        utftext += String.fromCharCode((c & 63) | 128);
      }
    }
    return utftext;
  }

  this._utf8_decode = function(utftext) {
    var string ="";
    var i = 0;
    var c = 0,
      c1 = 0,
      c2 = 0,
      c3 = 0;
    while (i < utftext.length) {
      c = utftext.charCodeAt(i);
      if (c < 128) {
        string += String.fromCharCode(c);
        i++;
      } else if ((c > 191) && (c < 224)) {
        c2 = utftext.charCodeAt(i + 1);
        string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
        i += 2;
      } else {
        c2 = utftext.charCodeAt(i + 1);
        c3 = utftext.charCodeAt(i + 2);
        string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
        i += 3;
      }
    }
    return string;
  }
}()

var btoa = Base64.encode;
var atob = Base64.decode;

console.log("btoa('A') =" + btoa('A'));
console.log("btoa('QQ==') =" + atob('QQ=='));
console.log("btoa('B') =" + btoa('B'));
console.log("btoa('Qg==') =" + atob('Qg=='));


我了解这是节点应用程序的讨论点,但是出于在节点服务器上运行的通用JavaScript应用程序的兴趣(这是我到达此职位的方式),我一直在研究通用/同构React应用程序。建筑,而包装abab为我工作。实际上,这是我能找到的唯一可行的解??决方案,而不是使用也提到的Buffer方法(我遇到打字稿问题)。

(此包由jsdom使用,而window包又使用。)

回到我的观点;基于此,也许如果该功能已经像您提到的那样已作为npm软件包编写,并且具有基于W3规范的自己的算法,则可以安装和使用abab软件包,而不必编写可能或根据编码可能不准确。

- -编辑 - -

我今天开始用包abab进行编码的奇怪问题(不确定为什么现在开始发生)。大部分时间似乎编码正确,但有时在前端编码不正确。花了很长时间尝试调试,但根据建议切换到包base-64,它立即起作用。显然,这取决于abab的base64算法。


也许您不再需要它了,但是如果有人使用节点需要它:
https://www.npmjs.com/package/btoa