• Курсы Академии Кодебай, стартующие в мае - июне, от команды The Codeby

    1. Цифровая криминалистика и реагирование на инциденты
    2. ОС Linux (DFIR) Старт: 16 мая
    3. Анализ фишинговых атак Старт: 16 мая Устройства для тестирования на проникновение Старт: 16 мая

    Скидки до 10%

    Полный список ближайших курсов ...

Гостевая статья Async Clipboard API: Доступ к буферу обмена с помощью JavaScript

Доступ к буферу обмена пользователя долгое время не был приятным занятием. Нам пришлось использовать document.execCommand
API для копирования и вставки текста в буфер обмена пользователя и из него, что включает в себя следующие шаги:

JavaScript:
// #1. Используем input элемент

const input = document.querySelector('input');


// #2. Устанавливаем значение input на текст, который будем копировать в буфер

input.value = 'hello there!';


// #3. Выделяем значение input

input.select();


// #4. Копируем выделенное значение

document.execCommand('copy');

input элемент может быть динамически создан и удален во время процесса, или стилизован таким образом, чтобы он не был виден пользователю. В те времена, когда мне приходилось использовать этот подход раньше, я всегда думал, что он выглядит уродливо и не очень элегантно. К счастью, новый Web API здесь для того, чтобы сделать это намного проще!
Async API буфера обмена
предоставляет веб-приложениям возможность программного чтения и записи в системный буфер обмена. Несколько заметок о API:

  • Доступ к нему можно получить navigator.clipboard.
  • Сайт должен обслуживаться через HTTPS или локальный хост.
  • Работает только тогда, когда страница является активной вкладкой браузера.
Теперь посмотрим, насколько это просто, по сравнению со старым способом.

Запись в буфер обмена

JavaScript:
async function writeToClipboard(text) {
    try {
        await navigator.clipboard.writeText(text);
    } catch (error) {
        console.error(error);
    }
}

Этот метод возвращает Promise, чтобы разрешить его путем создания цепочки .then() или используя async/await. С помощью этой единственной короткой строчки кода мы только что вписали наш текст в буфер обмена!

Примечание: В Firefox текст попадает в буфер обмена только при вызове writeText() в ответ на разрешение пользователя, в противном случае он вызывает исключение. Chrome записывает текст в буфер обмена независимо от пользователя. Оба варианта позволяют писать в буфер обмена без необходимости запрашивать разрешение.
Чтение из буфера обмена

JavaScript:
async function readFromClipboard() {
    try {
        const text = await navigator.clipboard.readText();
        console.log(text);
    } catch (error) {
        console.error(error);
    }
}

Этот метод также возвращает Promise, и он так же прост, как и запись в буфер обмена. В первый раз, когда сайт пытается прочитать содержимое буфера обмена, браузер подсказывает пользователю, хочет ли он разрешить запрос или нет:

98d375f12a27ba422e346.jpg


Примечание: В Chrome разрешение на чтение буфера обмена автоматически запрещается, если пользователь несколько раз удалял его (~3 раза из моего наблюдения).
Примечание: На момент написания статьи Firefox пока не поддерживает метод readText(), а в MDN документах указано, что он поддерживается только в расширениях браузера.
Проверка прав доступа в буфер обмена
Мы можем проверить, есть ли у нас разрешение на доступ к буферу обмена с помощью

JavaScript:
await navigator.permissions.query({name: 'clipboard-read'});
// or 'clipboard-write' for permission to write

// sample result: {state: 'granted'}

Мы можем использовать этот результат, например, для отображения некоторого пользовательского интерфейса, сообщающего пользователю, есть ли у нас доступ к буферу обмена или нет.

События буфера обмена

Помимо возможности легко писать и читать из буфера обмена, API Async Clipboard также предоставляет нам события из буфера обмена. Мы можем знать, когда пользователь выполняет действия, связанные с буфером обмена, такие как копирование, вырезание или вставка, прослушивая события копирования, вырезания и вставки соответственно.

JavaScript:
document.addEventListener('copy', event => {});
document.addEventListener('cut', event => {});
document.addEventListener('paste', event => {});

Эти события не запускаются при обращении к буферу обмена с помощью API Async Clipboard (т.е. через writeText()илиreadText()), а запускаются при вызове соответствующих команд document.execCommand. Вызов event.preventDefault() отменяет действие и поддерживает текущее состояние буфера обмена.

Эти события запускаются только тогда, когда действие было выполнено на странице, а не когда оно выполняется на других страницах или в других приложениях.

Объекты event буфера обмена имеют свойство clipboardData, которое является объектом Это позволяет нам перезаписывать данные, которые будут записаны в буфер обмена, давая нам возможность записывать данные в другие форматы, такие как text/html:

JavaScript:
document.addEventListener('copy', event => {
    event.preventDefault();
    event.clipboardData.setData('text/plain', 'COPY ME!!!');
    event.clipboardData.setData('text/html', '<p>COPY ME!!!</p>');
});

При этом нам нужно вызвать event.preventDefault(), чтобы наши пользовательские данные были записаны в буфер обмена вместо оригинала. Для cut и paste событий, нам нужно обрабатывать удаление/вставку содержимого в документ самим.

Поддержка изображений
До сих пор мы видели только версию API Async Clipboard, которая поддерживает только чтение/запись текста, и она уже выглядит круто! Недавнее дополнение к API - поддержка изображений, позволяющая с легкостью программировать чтение и запись изображений в буфер обмена!
Примечание: Пока что поддерживаются только изображения в формате PNG, но в будущем будет добавлена поддержка других форматов изображений (и, возможно, файлов в целом).
Запишите изображение в буфер обмена
Прежде чем мы сможем записать изображение в буфер обмена, нам сначала нужно получить изображения. Есть несколько способов получить blob изображения:

  • Попросить пользователя выбрать изображение с помощью входного файла
  • fetch() изображение из сети в виде блока (с помощью response.blob()).
  • Нарисуйте изображение наcanvas и вызовите canvas.toBlob()
Как только у нас будет блок изображений (назовем его imageBlob), нам нужно создать экземпляр ClipboardItem, содержащий нашу картинку Blob:

JavaScript:
new ClipboardItem({
    'image/png': imageBlob
})

Конструктор ClipboardItem принимает объект, ключами которого являются MIME-типы, а значениями - сами blob. Мы можем предоставить несколько пар MIME-типов и blob-ов, давая различные представления данных, используя различные типы.

Теперь мы можем записать наше изображение в буфер обмена с помощью функции navigator.clipboard.write():

JavaScript:
async function writeToClipboard(imageBlob) {
    try {
        await navigator.clipboard.write([
            new ClipboardItem({
                'image/png': imageBlob
            })
        ]);
    } catch (error) {
        console.error(error);
    }
}

navigator.clipboard.write() принимает массив ClipboardItem s, но на момент написания статьи поддерживает только один элемент. Скорее всего, это изменится в будущем.

Чтение изображения из буфера обмена
Чтение элементов (не только текста) из буфера обмена можно выполнять с помощью функции navigator.clipboard.read():

JavaScript:
async function readFromClipboard() {
    try {
        const items = await navigator.clipboard.read();
    } catch (error) {
        console.error(error);
    }
}

Он возвращает массив ClipboardItem s, который зеркально отражает содержимое системного буфера обмена, хотя в настоящее время в Chrome он возвращает только последний элемент в буфере обмена.

Мы можем получить все доступные MIME типы в ClipboardItem через его свойство items, а также получить фактические данные blob-а для конкретного типа, используя его асинхронный метод getType():

JavaScript:
for (let item of items) {
    console.log(item.types); // e.g. ['image/png']

    for (let type of item.types) {
        const blob = await item.getType(type);
    }
}

После того, как мы получим blob, мы сможем делать с ним все, что захотим. Мы можем использовать API для преобразования blob-а в соответствующие форматы, которые мы хотим:

JavaScript:
const reader = new FileReader();
reader.onload = () => {
    const data = reader.result;
    // e.g. 'data:image/png;base64,...'
};

reader.readAsDataURL(blob);

Методы write() и read() API буфера обмена Async Clipboard предоставляют общие способы доступа к буферу обмена. На самом деле, методы writeText() и eadText(), рассмотренные ранее, являются просто удобными методами для них, и в противном случае могут быть выполнены с помощью write()/read() с помощью blobs с типом text/plain

JavaScript:
async function writeToClipboard(text) {
    try {
        await navigator.clipboard.write([
            new ClipboardItem({
                'text/plain': new Blob([text], {type: 'text/plain'})
            })
        ]);
    } catch (error) {
        console.error(error);
    }
}

async function readFromClipboard() {
    try {
        const items = await navigator.clipboard.read();
        for (let item of items) {
            const data = item.getType('text/plain');
            // convert `data` to string using FileReader API's
            // `.readAsText(data)` method
        }
    } catch (error) {
        console.error(error);
    }
}

Поддержка браузера и обнаружение функций

Async Clipboard API с поддержкой текста, поставляемый в Chrome 66 и FireFox 63 (с функцией readText() для веб-приложений пока недоступен). Для поддержки изображений в формате PNG на момент написания статьи поддерживается только Chrome, доставка осуществляется в Chrome 76. Дополнительные совместимости браузера.

Мы можем воспользоваться этим API уже в браузерах, которые его поддерживают, через обнаружение функций, проверив наличие буфера navigator.clipboard

JavaScript:
if (navigator.clipboard) {
    // Safe to use Async Clipboard API!
} else {
    // Use document.execCommand() instead
}

Ресурсы
Спасибо, что прочитали эту статью, надеюсь, вам понравилось и вы кое-чему научились.

 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!