@@ -18,48 +18,61 @@ limitations under the License.
1818 * Base64 encoding and decoding utilities
1919 */
2020
21+ function toBase64 ( uint8Array : Uint8Array , options : Uint8ArrayToBase64Options ) : string {
22+ if ( typeof uint8Array . toBase64 === "function" ) {
23+ // Currently this is only supported in Firefox,
24+ // but we match the options in the hope in the future we can rely on it for all environments.
25+ // https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.tobase64
26+ return uint8Array . toBase64 ( options ) ;
27+ }
28+
29+ let base64 = btoa ( uint8Array . reduce ( ( acc , current ) => acc + String . fromCharCode ( current ) , "" ) ) ;
30+ if ( options . omitPadding ) {
31+ base64 = base64 . replace ( / = { 1 , 2 } $ / , "" ) ;
32+ }
33+ if ( options . alphabet === "base64url" ) {
34+ base64 = base64 . replace ( / \+ / g, "-" ) . replace ( / \/ / g, "_" ) ;
35+ }
36+
37+ return base64 ;
38+ }
39+
2140/**
2241 * Encode a typed array of uint8 as base64.
2342 * @param uint8Array - The data to encode.
2443 * @returns The base64.
2544 */
26- export function encodeBase64 ( uint8Array : ArrayBuffer | Uint8Array ) : string {
27- // A brief note on the state of base64 encoding in Javascript.
28- // As of 2023, there is still no common native impl between both browsers and
29- // node. Older Webpack provides an impl for Buffer and there is a polyfill class
30- // for it. There are also plenty of pure js impls, eg. base64-js which has 2336
31- // dependents at current count. Using this would probably be fine although it's
32- // a little under-docced and run by an individual. The node impl works fine,
33- // the browser impl works but predates Uint8Array and so only uses strings.
34- // Right now, switching between native (or polyfilled) impls like this feels
35- // like the least bad option, but... *shrugs*.
36- if ( typeof Buffer === "function" ) {
37- return Buffer . from ( uint8Array ) . toString ( "base64" ) ;
38- } else if ( typeof btoa === "function" && uint8Array instanceof Uint8Array ) {
39- // ArrayBuffer is a node concept so the param should always be a Uint8Array on
40- // the browser. We need to check because ArrayBuffers don't have reduce.
41- return btoa ( uint8Array . reduce ( ( acc , current ) => acc + String . fromCharCode ( current ) , "" ) ) ;
42- } else {
43- throw new Error ( "No base64 impl found!" ) ;
44- }
45+ export function encodeBase64 ( uint8Array : Uint8Array ) : string {
46+ return toBase64 ( uint8Array , { alphabet : "base64" , omitPadding : false } ) ;
4547}
4648
4749/**
4850 * Encode a typed array of uint8 as unpadded base64.
4951 * @param uint8Array - The data to encode.
5052 * @returns The unpadded base64.
5153 */
52- export function encodeUnpaddedBase64 ( uint8Array : ArrayBuffer | Uint8Array ) : string {
53- return encodeBase64 ( uint8Array ) . replace ( / = { 1 , 2 } $ / , "" ) ;
54+ export function encodeUnpaddedBase64 ( uint8Array : Uint8Array ) : string {
55+ return toBase64 ( uint8Array , { alphabet : "base64" , omitPadding : true } ) ;
5456}
5557
5658/**
5759 * Encode a typed array of uint8 as unpadded base64 using the URL-safe encoding.
5860 * @param uint8Array - The data to encode.
5961 * @returns The unpadded base64.
6062 */
61- export function encodeUnpaddedBase64Url ( uint8Array : ArrayBuffer | Uint8Array ) : string {
62- return encodeUnpaddedBase64 ( uint8Array ) . replace ( / \+ / g, "-" ) . replace ( / \/ / g, "_" ) ;
63+ export function encodeUnpaddedBase64Url ( uint8Array : Uint8Array ) : string {
64+ return toBase64 ( uint8Array , { alphabet : "base64url" , omitPadding : true } ) ;
65+ }
66+
67+ function fromBase64 ( base64 : string , options : Uint8ArrayFromBase64Options ) : Uint8Array {
68+ if ( typeof Uint8Array . fromBase64 === "function" ) {
69+ // Currently this is only supported in Firefox,
70+ // but we match the options in the hope in the future we can rely on it for all environments.
71+ // https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.frombase64
72+ return Uint8Array . fromBase64 ( base64 , options ) ;
73+ }
74+
75+ return Uint8Array . from ( atob ( base64 ) , ( c ) => c . charCodeAt ( 0 ) ) ;
6376}
6477
6578/**
@@ -68,21 +81,6 @@ export function encodeUnpaddedBase64Url(uint8Array: ArrayBuffer | Uint8Array): s
6881 * @returns The decoded data.
6982 */
7083export function decodeBase64 ( base64 : string ) : Uint8Array {
71- // See encodeBase64 for a short treatise on base64 en/decoding in JS
72- if ( typeof Buffer === "function" ) {
73- return Buffer . from ( base64 , "base64" ) ;
74- } else if ( typeof atob === "function" ) {
75- const itFunc = function * ( ) : Generator < number > {
76- const decoded = atob (
77- // built-in atob doesn't support base64url: convert so we support either
78- base64 . replace ( / - / g, "+" ) . replace ( / _ / g, "/" ) ,
79- ) ;
80- for ( let i = 0 ; i < decoded . length ; ++ i ) {
81- yield decoded . charCodeAt ( i ) ;
82- }
83- } ;
84- return Uint8Array . from ( itFunc ( ) ) ;
85- } else {
86- throw new Error ( "No base64 impl found!" ) ;
87- }
84+ // The function requires us to select an alphabet, but we don't know if base64url was used so we convert.
85+ return fromBase64 ( base64 . replace ( / - / g, "+" ) . replace ( / _ / g, "/" ) , { alphabet : "base64" , lastChunkHandling : "loose" } ) ;
8886}
0 commit comments