Представим, что есть реализованная функция для копирования какого-то текста (например, промокода) в буфер обмена:
export async function copyToClipboard(textToCopy: string): Promise<string> {
await navigator.clipboard.writeText(textToCopy);
return textToCopy;
}
Казалось бы, всё хорошо, на локальном развёртывании функция работает, а, значит, фича готова к отгрузке. Версия фронтенда с этой функцией ставится на стенд, доступ к которому, например, осуществляется только по протоколу HTTP. Довольные, мы накатываем изменения, открываем наше веб-приложение, нажимаем на заветную кнопку, использующую функцию копирования, — и ничего не происходит.
Почему?
Первая причина (и самая частая) — браузер не даёт доступ к буферу обмена в небезопасном контексте. Разработчиков браузеров можно понять, так как такая операция на сайтах без SSL-сертификатов может быть потенциально опасной и позволить скопировать массу вредоносных вещей.
Почему это работало на локалке?
Моё предположение заключается в смягчении ограничений на доступ к буферу обмена для веб-страниц на ряде зарезервированных хостов на ПК разработчика / пользователя (например, localhost
, 127.0.0.1
итд).
Как решить проблему
- Главное решение — стараться использовать HTTPS на вашем стенде или боевом сайте. Здесь всё очевидно, не даём браузеру повод думать, что мы скамеры или мошенники;
- Если нет нужды/возможности положить сайт под HTTPS: есть 2 способа обойти ограничение:
- Первый способ предполагает включение экспериментального флага, позволяющего давать больше доступов для сайтов с небезопасным контекстом. Этим можно воспользоваться если браузер хромообразный, на Firefox и форках такая функция может отсутствовать;
- Самый верный способ — обход использования
navigator.clipboard
в качестве fallback-действия. В таком случае нужно иметь скрытый элементinput
илиtextarea
, для которого можно будет применить выделение для последующего копирования и использовать более глубинный механизм (да, тот самый, что работает при выделении текста, нажатии ПКМ и в появившемся контекстном меню «Копировать»). Переписав функцию с учётом всех нюансов, мы бы получили что-то вроде:
export async function copyToClipboard(textToCopy: string): Promise<string> {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(textToCopy);
return textToCopy;
}
// Creating textarea
const textarea = document.createElement('textarea');
textarea.value = textToCopy;
// Hide textarea
textarea.style.position = 'absolute';
textarea.style.left = '-99999999px';
document.body.prepend(textarea);
// Highlight contents of the textarea
textarea.select();
// Now copy
try {
document.execCommand('copy');
} catch (err) {
console.warn('Copy error', err);
} finally {
textarea.remove();
}
return textToCopy;
}