Клиент браузера и служба Node.js должны создавать один и тот же Base64 для одних и тех же байтов. Для этого им не обязательно использовать один и тот же API. Важным соглашением является байтовое представление текста, алфавита, а также обработка заполнения и недопустимого ввода.
Браузер: сделайте шаг UTF-8 явным.
Традиционные функции браузера работают с байтовыми строками. Преобразование текста Unicode с помощью TextEncoder, затем закодируйте эти байты. На обратном пути восстановите массив байтов и явно декодируйте UTF-8. В этом примере используется цикл, чтобы избежать расширения большого массива в аргументы функции, хотя кодировщик рабочего файла все равно должен использовать ограниченные фрагменты или соответствующий байт API.
const text = '中文😊';
const bytes = new TextEncoder().encode(text);
let binary = '';
for (const byte of bytes) binary += String.fromCharCode(byte);
const encoded = btoa(binary);
console.log(encoded); // 5Lit5paH8J+Yig==
const decodedBytes = Uint8Array.from(atob(encoded), c => c.charCodeAt(0));
const decoded = new TextDecoder('utf-8', { fatal: true }).decode(decodedBytes);
console.log(decoded); // 中文😊
Node.js: оставить Buffer на границе байта.
Buffer.from(text, 'utf8') делает кодировку текста видимой. Вызов toString('base64') затем представляет эти байты. Декодирование в Buffer сохраняет двоичные данные; немедленное преобразование в строку UTF-8 подходит только в том случае, если известно, что полезная нагрузка является текстом.
import { Buffer } from 'node:buffer';
const encoded = Buffer.from('中文😊', 'utf8').toString('base64');
console.log(encoded); // 5Lit5paH8J+Yig==
const bytes = Buffer.from(encoded, 'base64');
console.log(new TextDecoder('utf-8', { fatal: true }).decode(bytes)); // 中文😊
Фатальный код TextDecoder в этом примере является намеренным. Обычное преобразование строк Buffer UTF-8 может вставлять символы замены для недопустимого ввода. В этом случае успешное отображение текста скроет, что исходные байты не могут быть представлены достоверно.
О валидации договоритесь отдельно
Декодер Node Base64 принимает некоторые входные формы, которые более строгие приложения могут отклонить. Не используйте успешное преобразование Buffer в качестве доказательства того, что было предоставлено каноническое представление протокола. Если точный синтаксис имеет значение для подписей, ключей кэша или совместимой сериализации, проверьте его перед декодированием.
Для общего регрессионного теста включите ASCII, китайский язык, эмодзи, нулевой байт, байт FF и все длины вокруг трехбайтовой границы. Сравнивайте байты во время выполнения. Также проверьте вывод, безопасный для URL, с помощью приспособления, которое фактически содержит замененные символы; ввод, вывод которого состоит только из букв, не может доказать, что опция варианта работает.
При чтении файлов в Node сохраняйте возвращаемый Buffer вместо применения текстовой кодировки к прочитанному файлу. В браузере используйте File.arrayBuffer(). Такой выбор небольших границ позволяет избежать большинства случайных повреждений. См. Node.js Buffer для поведения узла и МДН: btoa() для контракта байтовой строки браузера.