Error al ejecutar 'btoa' en 'Ventana': la cadena a codificar contiene caracteres fuera del rango Latin1.

133

El error en el título se arroja solo en Google Chrome, según mis pruebas. Estoy en base64 codificando un gran archivo XML para que pueda descargarse:

this.loader.src = "data:application/x-forcedownload;base64,"+
                  btoa("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
                  +"<"+this.gamesave.tagName+">"
                  +this.xml.firstChild.innerHTML
                  +"</"+this.gamesave.tagName+">");

this.loader Está oculto iframe.

Este error es en realidad un gran cambio porque, normalmente, Google Chrome se bloquea al btoallamar. Mozilla Firefox no tiene problemas aquí, por lo que el problema está relacionado con el navegador. No conozco ningún personaje extraño en el archivo. En realidad, creo que no hay personajes que no sean ascii.

P: ¿Cómo encuentro los caracteres problemáticos y los reemplazo para que Chrome deje de quejarse?

He intentado usar Downloadify para iniciar la descarga, pero no funciona. No es confiable y no arroja errores para permitir la depuración.

Tomáš Zato - Restablece a Monica
fuente

Respuestas:

212

Si tiene UTF8, use esto (en realidad funciona con fuente SVG), como:

btoa(unescape(encodeURIComponent(str)))

ejemplo:

 var imgsrc = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(markup)));
 var img = new Image(1, 1); // width, height values are optional params 
 img.src = imgsrc;

Si necesita decodificar esa base64, use esto:

var str2 = decodeURIComponent(escape(window.atob(b64)));
console.log(str2);

Ejemplo:

var str = "äöüÄÖÜçéèñ";
var b64 = window.btoa(unescape(encodeURIComponent(str)))
console.log(b64);

var str2 = decodeURIComponent(escape(window.atob(b64)));
console.log(str2);

Nota: si necesita que esto funcione en el safari móvil, es posible que deba eliminar todo el espacio en blanco de los datos de base64 ...

function b64_to_utf8( str ) {
    str = str.replace(/\s/g, '');    
    return decodeURIComponent(escape(window.atob( str )));
}

Actualización 2017

Este problema me ha estado molestando de nuevo.
La simple verdad es que atob realmente no maneja cadenas UTF8, es solo ASCII.
Además, no usaría bloatware como js-base64.
Pero webtoolkit tiene una implementación pequeña, agradable y muy fácil de mantener:

/**
*
*  Base64 encode / decode
*  http://www.webtoolkit.info
*
**/
var Base64 = {

    // private property
    _keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="

    // public method for encoding
    , 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 +
                this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
                this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
        } // Whend 

        return output;
    } // End Function encode 


    // public method for decoding
    ,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 = this._keyStr.indexOf(input.charAt(i++));
            enc2 = this._keyStr.indexOf(input.charAt(i++));
            enc3 = this._keyStr.indexOf(input.charAt(i++));
            enc4 = this._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);
            }

        } // Whend 

        output = Base64._utf8_decode(output);

        return output;
    } // End Function decode 


    // private method for UTF-8 encoding
    ,_utf8_encode: function (string)
    {
        var utftext = "";
        string = string.replace(/\r\n/g, "\n");

        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);
            }

        } // Next n 

        return utftext;
    } // End Function _utf8_encode 

    // private method for UTF-8 decoding
    ,_utf8_decode: function (utftext)
    {
        var string = "";
        var i = 0;
        var c, c1, c2, c3;
        c = c1 = c2 = 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;
            }

        } // Whend 

        return string;
    } // End Function _utf8_decode 

}

https://www.fileformat.info/info/unicode/utf8.htm

  • Para cualquier carácter igual o inferior a 127 (hex 0x7F), la representación UTF-8 es un byte. Son solo los 7 bits más bajos del valor Unicode completo. Esto también es lo mismo que el valor ASCII.

  • Para caracteres iguales o inferiores a 2047 (hexadecimal 0x07FF), la representación UTF-8 se distribuye en dos bytes. El primer byte tendrá los dos bits altos establecidos y el tercer bit libre (es decir, 0xC2 a 0xDF). El segundo byte tendrá el bit superior establecido y el segundo bit libre (es decir, 0x80 a 0xBF).

  • Para todos los caracteres iguales o mayores que 2048 pero menores que 65535 (0xFFFF), la representación UTF-8 se distribuye en tres bytes.

Stefan Steiger
fuente
66
puedes explicar esto un poco más ... estoy totalmente perdido
Muhammad Umer
Simplemente ejecutaría el código si fuera tú. escapeconvierte una cadena en la que solo contiene caracteres válidos de URL. Eso evita los errores.
Tomáš Zato - Restablece a Monica el
66
escapey unescapefueron desaprobados en JavaScript 1.5 y uno debería usar encodeURIComponento decodeURIComponent, respectivamente, en su lugar. Estás utilizando las funciones obsoletas y nuevas juntas. ¿Por qué? Ver: w3schools.com/jsref/jsref_escape.asp
Leif
2
@Leif: Esto solo funciona precisamente porque escapar y no escapar tienen errores (de la misma manera);)
Stefan Steiger
8
¿Alguien más terminó aquí por usar webpack?
Avindra Goolcharan
18

Usar btoacon unescapey encodeURIComponentno funcionó para mí. Reemplazar todos los caracteres especiales con entidades XML / HTML y luego convertirlos a la representación base64 fue la única forma de resolver este problema para mí. Algún código:

base64 = btoa(str.replace(/[\u00A0-\u2666]/g, function(c) {
    return '&#' + c.charCodeAt(0) + ';';
}));
Italo Borssatto
fuente
1
Desde que publiqué esta pregunta, aprendí un poco sobre las API dedicadas a lo que estaba haciendo. Si la cadena que está convirtiendo es larga, use el Blobobjeto para manejar la conversión. Blobpuede manejar cualquier dato binario.
Tomáš Zato - Restablece a Monica el
1
No estoy seguro acerca de IE9. Pero creo que si está haciendo cosas como la conversión de base64 del lado del cliente, probablemente esté creando una aplicación web moderna que, tarde o temprano, necesitará características modernas de todos modos. Además, hay un blob polyfill.
Tomáš Zato - Restablece a Monica el
1
@ItaloBorssatto Eres una leyenda!
codeepic
1
@ItaloBorssatto Fue la única solución que funcionó para mí. Lo necesitaba para tomar el gráfico d3 svg, serializarlo usando XMLSerializer, pasarlo a btoa () (aquí es donde usé su solución) para crear una cadena ASCII codificada en base 64, luego pasarla al elemento de imagen que es luego se dibuja en el lienzo y luego se exporta para que pueda descargar una imagen en el front-end. Más bien una solución complicada y hacky, pero una que no requiere gráficos renderizados del lado del servidor cuando los usuarios desean descargar algunos gráficos. Si está interesado, puedo enviarle algunos ejemplos de código. El comentario es demasiado corto para ellos
codeepic
1
@ItaloBorssatto <svg xmlns = " w3.org/2000/svg " viewBox = "0 0 1060 105" width = "1060" height = "105"> <path class = "domain" stroke = "none" d = "M -6,0.5H0.5V35.5H-6 "> <line stroke =" none "x2 =" - 6 "y1 =" 0.5 "y2 =" 0.5 "fill =" none "stroke-width =" 1px "font- family = "sans-serif" font-size = "10px" /> <text fill = "rgb (196, 196, 196)" x = "- 9" y = "0.5" dy = "0.32em"> VogueEspana - Vogue España </text> <rect class = "first bar" fill = "rgb (25, 244, 71)" x = "0" y = "8" width = "790" height = "18" /> </ g> </svg> Corté piezas irrelevantes. El culpable es Vogue España -> ñ evitó que se cargara una imagen en el navegador.
codeepic
15

Use una biblioteca en su lugar

No tenemos que reinventar la rueda. Simplemente use una biblioteca para ahorrar tiempo y dolor de cabeza.

js-base64

https://github.com/dankogai/js-base64 es bueno y confirmo que admite muy bien unicode.

Base64.encode('dankogai');  // ZGFua29nYWk=
Base64.encode('小飼弾');    // 5bCP6aO85by+
Base64.encodeURI('小飼弾'); // 5bCP6aO85by-

Base64.decode('ZGFua29nYWk=');  // dankogai
Base64.decode('5bCP6aO85by+');  // 小飼弾
// note .decodeURI() is unnecessary since it accepts both flavors
Base64.decode('5bCP6aO85by-');  // 小飼弾
Tyler Long
fuente
Esta es una buena solución, aunque parece un descuido para que btoa se limite a ASCII (aunque la decodificación de atob parece funcionar bien). Esto funcionó para mí después de que varias de las otras respuestas no lo harían. ¡Gracias!
Para el nombre
9

Solo pensé que debería compartir cómo resolví el problema y por qué creo que esta es la solución correcta (siempre que no optimice para el navegador antiguo).

Convertir datos a dataURL ( data: ...)

var blob = new Blob(
              // I'm using page innerHTML as data
              // note that you can use the array
              // to concatenate many long strings EFFICIENTLY
              [document.body.innerHTML],
              // Mime type is important for data url
              {type : 'text/html'}
); 
// This FileReader works asynchronously, so it doesn't lag
// the web application
var a = new FileReader();
a.onload = function(e) {
     // Capture result here
     console.log(e.target.result);
};
a.readAsDataURL(blob);

Permitir al usuario guardar datos

Además de la solución obvia: abrir una nueva ventana con su dataURL como URL, puede hacer otras dos cosas.

1. Use fileSaver.js

El protector de archivos puede crear un diálogo real de guardar archivos con un nombre de archivo predefinido. También puede recurrir al enfoque normal de dataURL.

2. Uso (experimental) URL.createObjectURL

Esto es ideal para reutilizar datos codificados en base64. Crea una URL corta para su dataURL:

console.log(URL.createObjectURL(blob));
//Prints: blob:http://stackoverflow.com/7c18953f-f5f8-41d2-abf5-e9cbced9bc42

No olvides usar la URL que incluye el blobprefijo principal . Usé de document.bodynuevo:

descripción de la imagen

Puede usar esta URL corta como destino AJAX, <script>fuente o <a>ubicación href. Sin embargo, eres responsable de destruir la URL:

URL.revokeObjectURL('blob:http://stackoverflow.com/7c18953f-f5f8-41d2-abf5-e9cbced9bc42')
Tomáš Zato - Restablece a Monica
fuente
Gracias amigo, me salvaste el día :)
Sandeep Kumar
3

Como complemento a la respuesta de Stefan Steiger: (ya que no se ve bien como comentario)

Prototipo de cadena extensible:

String.prototype.b64encode = function() { 
    return btoa(unescape(encodeURIComponent(this))); 
};
String.prototype.b64decode = function() { 
    return decodeURIComponent(escape(atob(this))); 
};

Uso:

var str = "äöüÄÖÜçéèñ";
var encoded = str.b64encode();
console.log( encoded.b64decode() );

NOTA:

Como se indica en los comentarios, unescapeno se recomienda el uso, ya que puede eliminarse en el futuro:

Advertencia : aunque unescape () no está estrictamente en desuso (como en "eliminado de los estándares web"), se define en el Anexo B del estándar ECMA-262, cuya introducción establece: ... Todas las características y comportamientos del lenguaje especificados en este El anexo tiene una o más características indeseables y, en ausencia de uso heredado, se eliminaría de esta especificación.

Nota: No use unescape para decodificar URI, use decodeURI o decodeURIComponent en su lugar.

lepe
fuente
66
Las funciones se ven bien, pero extender los prototipos base es una mala práctica.
timemachine3030
44
Javascript es una mala práctica. ¿Cuál es un truco más, gracias.
rob5408
1
@ rob5408: Si bien estoy de acuerdo con su declaración en principio, pero realmente debería ser más cauteloso: la extensión de prototipos infringe jQuery (otra biblioteca que utiliza el principio de "solo un truco más")
Stefan Steiger
@StefanSteiger Es bueno saberlo, gracias por la información.
rob5408
unescapepronto será desaprobado según MDN developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Akansh
2

btoa () solo admite caracteres desde String.fromCodePoint (0) hasta String.fromCodePoint (255). Para los caracteres Base64 con un punto de código 256 o superior, debe codificarlos / decodificarlos antes y después.

Y en este punto se vuelve complicado ...

Cada signo posible se organiza en una tabla Unicode. La tabla Unicode se divide en diferentes planos (idiomas, símbolos matemáticos, etc.). Cada signo en un avión tiene un número de punto de código único. Teóricamente, el número puede llegar a ser arbitrariamente grande.

Una computadora almacena los datos en bytes (8 bits, hexadecimal 0x00 - 0xff, binario 00000000 - 11111111, decimal 0 - 255). Este rango normalmente se usa para guardar caracteres básicos (rango Latin1).

Para los caracteres con un punto de código más alto, 255 existen diferentes codificaciones. JavaScript usa 16 bits por signo (UTF-16), la cadena llamada DOMString. Unicode puede manejar puntos de código de hasta 0x10fffff. Eso significa que debe existir un método para almacenar varios bits en varias celdas de distancia.

String.fromCodePoint(0x10000).length == 2

UTF-16 usa pares sustitutos para almacenar 20 bits en dos celdas de 16 bits. El primer sustituto superior comienza con 110110xxxxxxxxxx , el segundo inferior con 110111xxxxxxxxxx . Unicode reservó sus propios planos para esto: https://unicode-table.com/de/#high-surrogates

Para almacenar caracteres en bytes (rango Latin1), los procedimientos estandarizados utilizan UTF-8 .

Lamento decir eso, pero creo que no hay otra forma de implementar esta función por sí mismo.

function stringToUTF8(str)
{
    let bytes = [];

    for(let character of str)
    {
        let code = character.codePointAt(0);

        if(code <= 127)
        {
            let byte1 = code;

            bytes.push(byte1);
        }
        else if(code <= 2047)
        {
            let byte1 = 0xC0 | (code >> 6);
            let byte2 = 0x80 | (code & 0x3F);

            bytes.push(byte1, byte2);
        }
        else if(code <= 65535)
        {
            let byte1 = 0xE0 | (code >> 12);
            let byte2 = 0x80 | ((code >> 6) & 0x3F);
            let byte3 = 0x80 | (code & 0x3F);

            bytes.push(byte1, byte2, byte3);
        }
        else if(code <= 2097151)
        {
            let byte1 = 0xF0 | (code >> 18);
            let byte2 = 0x80 | ((code >> 12) & 0x3F);
            let byte3 = 0x80 | ((code >> 6) & 0x3F);
            let byte4 = 0x80 | (code & 0x3F);

            bytes.push(byte1, byte2, byte3, byte4);
        }
    }

    return bytes;
}

function utf8ToString(bytes, fallback)
{
    let valid = undefined;
    let codePoint = undefined;
    let codeBlocks = [0, 0, 0, 0];

    let result = "";

    for(let offset = 0; offset < bytes.length; offset++)
    {
        let byte = bytes[offset];

        if((byte & 0x80) == 0x00)
        {
            codeBlocks[0] = byte & 0x7F;

            codePoint = codeBlocks[0];
        }
        else if((byte & 0xE0) == 0xC0)
        {
            codeBlocks[0] = byte & 0x1F;

            byte = bytes[++offset];
            if(offset >= bytes.length || (byte & 0xC0) != 0x80) { valid = false; break; }

            codeBlocks[1] = byte & 0x3F;

            codePoint = (codeBlocks[0] << 6) + codeBlocks[1];
        }
        else if((byte & 0xF0) == 0xE0)
        {
            codeBlocks[0] = byte & 0xF;

            for(let blockIndex = 1; blockIndex <= 2; blockIndex++)
            {
                byte = bytes[++offset];
                if(offset >= bytes.length || (byte & 0xC0) != 0x80) { valid = false; break; }

                codeBlocks[blockIndex] = byte & 0x3F;
            }
            if(valid === false) { break; }

            codePoint = (codeBlocks[0] << 12) + (codeBlocks[1] << 6) + codeBlocks[2];
        }
        else if((byte & 0xF8) == 0xF0)
        {
            codeBlocks[0] = byte & 0x7;

            for(let blockIndex = 1; blockIndex <= 3; blockIndex++)
            {
                byte = bytes[++offset];
                if(offset >= bytes.length || (byte & 0xC0) != 0x80) { valid = false; break; }

                codeBlocks[blockIndex] = byte & 0x3F;
            }
            if(valid === false) { break; }

            codePoint = (codeBlocks[0] << 18) + (codeBlocks[1] << 12) + (codeBlocks[2] << 6) + (codeBlocks[3]);
        }
        else
        {
            valid = false; break;
        }

        result += String.fromCodePoint(codePoint);
    }

    if(valid === false)
    {
        if(!fallback)
        {
            throw new TypeError("Malformed utf-8 encoding.");
        }

        result = "";

        for(let offset = 0; offset != bytes.length; offset++)
        {
            result += String.fromCharCode(bytes[offset] & 0xFF);
        }
    }

    return result;
}

function decodeBase64(text, binary)
{
    if(/[^0-9a-zA-Z\+\/\=]/.test(text)) { throw new TypeError("The string to be decoded contains characters outside of the valid base64 range."); }

    let codePointA = 'A'.codePointAt(0);
    let codePointZ = 'Z'.codePointAt(0);
    let codePointa = 'a'.codePointAt(0);
    let codePointz = 'z'.codePointAt(0);
    let codePointZero = '0'.codePointAt(0);
    let codePointNine = '9'.codePointAt(0);
    let codePointPlus = '+'.codePointAt(0);
    let codePointSlash = '/'.codePointAt(0);

    function getCodeFromKey(key)
    {
        let keyCode = key.codePointAt(0);

        if(keyCode >= codePointA && keyCode <= codePointZ)
        {
            return keyCode - codePointA;
        }
        else if(keyCode >= codePointa && keyCode <= codePointz)
        {
            return keyCode + 26 - codePointa;
        }
        else if(keyCode >= codePointZero && keyCode <= codePointNine)
        {
            return keyCode + 52 - codePointZero;
        }
        else if(keyCode == codePointPlus)
        {
            return 62;
        }
        else if(keyCode == codePointSlash)
        {
            return 63;
        }

        return undefined;
    }

    let codes = Array.from(text).map(character => getCodeFromKey(character));

    let bytesLength = Math.ceil(codes.length / 4) * 3;

    if(codes[codes.length - 2] == undefined) { bytesLength = bytesLength - 2; } else if(codes[codes.length - 1] == undefined) { bytesLength--; }

    let bytes = new Uint8Array(bytesLength);

    for(let offset = 0, index = 0; offset < bytes.length;)
    {
        let code1 = codes[index++];
        let code2 = codes[index++];
        let code3 = codes[index++];
        let code4 = codes[index++];

        let byte1 = (code1 << 2) | (code2 >> 4);
        let byte2 = ((code2 & 0xf) << 4) | (code3 >> 2);
        let byte3 = ((code3 & 0x3) << 6) | code4;

        bytes[offset++] = byte1;
        bytes[offset++] = byte2;
        bytes[offset++] = byte3;
    }

    if(binary) { return bytes; }

    return utf8ToString(bytes, true);
}

function encodeBase64(bytes) {
    if (bytes === undefined || bytes === null) {
        return '';
    }
    if (bytes instanceof Array) {
        bytes = bytes.filter(item => {
            return Number.isFinite(item) && item >= 0 && item <= 255;
        });
    }

    if (
        !(
            bytes instanceof Uint8Array ||
            bytes instanceof Uint8ClampedArray ||
            bytes instanceof Array
        )
    ) {
        if (typeof bytes === 'string') {
            const str = bytes;
            bytes = Array.from(unescape(encodeURIComponent(str))).map(ch =>
                ch.codePointAt(0)
            );
        } else {
            throw new TypeError('bytes must be of type Uint8Array or String.');
        }
    }

    const keys = [
        'A',
        'B',
        'C',
        'D',
        'E',
        'F',
        'G',
        'H',
        'I',
        'J',
        'K',
        'L',
        'M',
        'N',
        'O',
        'P',
        'Q',
        'R',
        'S',
        'T',
        'U',
        'V',
        'W',
        'X',
        'Y',
        'Z',
        'a',
        'b',
        'c',
        'd',
        'e',
        'f',
        'g',
        'h',
        'i',
        'j',
        'k',
        'l',
        'm',
        'n',
        'o',
        'p',
        'q',
        'r',
        's',
        't',
        'u',
        'v',
        'w',
        'x',
        'y',
        'z',
        '0',
        '1',
        '2',
        '3',
        '4',
        '5',
        '6',
        '7',
        '8',
        '9',
        '+',
        '/'
    ];
    const fillKey = '=';

    let byte1;
    let byte2;
    let byte3;
    let sign1 = ' ';
    let sign2 = ' ';
    let sign3 = ' ';
    let sign4 = ' ';

    let result = '';

    for (let index = 0; index < bytes.length; ) {
        let fillUpAt = 0;

        // tslint:disable:no-increment-decrement
        byte1 = bytes[index++];
        byte2 = bytes[index++];
        byte3 = bytes[index++];

        if (byte2 === undefined) {
            byte2 = 0;
            fillUpAt = 2;
        }

        if (byte3 === undefined) {
            byte3 = 0;
            if (!fillUpAt) {
                fillUpAt = 3;
            }
        }

        // tslint:disable:no-bitwise
        sign1 = keys[byte1 >> 2];
        sign2 = keys[((byte1 & 0x3) << 4) + (byte2 >> 4)];
        sign3 = keys[((byte2 & 0xf) << 2) + (byte3 >> 6)];
        sign4 = keys[byte3 & 0x3f];

        if (fillUpAt > 0) {
            if (fillUpAt <= 2) {
                sign3 = fillKey;
            }
            if (fillUpAt <= 3) {
                sign4 = fillKey;
            }
        }

        result += sign1 + sign2 + sign3 + sign4;

        if (fillUpAt) {
            break;
        }
    }

    return result;
}

let base64 = encodeBase64("\u{1F604}"); // unicode code point escapes for smiley
let str = decodeBase64(base64);

console.log("base64", base64);
console.log("str", str);

document.body.innerText = str;

cómo usarlo: decodeBase64(encodeBase64("\u{1F604}"))

demostración: https://jsfiddle.net/qrLadeb8/

Martin Wantke
fuente
¡Funciona genial! 🎉 No veo dónde lo necesitas stringToUTF8y, utf8ToStringsin embargo
Benjamin Toueg
1

Me encontré con este problema yo mismo.

Primero, modifique su código ligeramente:

var download = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
                  +"<"+this.gamesave.tagName+">"
                  +this.xml.firstChild.innerHTML
                  +"</"+this.gamesave.tagName+">";

this.loader.src = "data:application/x-forcedownload;base64,"+
                  btoa(download);

Luego use su inspector web favorito, coloque un punto de interrupción en la línea de código que asigna this.loader.src, luego ejecute este código:

for (var i = 0; i < download.length; i++) {
  if (download[i].charCodeAt(0) > 255) {
    console.warn('found character ' + download[i].charCodeAt(0) + ' "' + download[i] + '" at position ' + i);
  }
}

Dependiendo de su aplicación, reemplazar los caracteres que están fuera del rango puede o no funcionar, ya que modificará los datos. Consulte la nota en MDN sobre los caracteres unicode con el método btoa:

https://developer.mozilla.org/en-US/docs/Web/API/window.btoa

Mark Salisbury
fuente