Odblokowuję dostęp do schowka

Bezpieczniejszy i odblokowany dostęp do schowka na tekst i obrazy

Tradycyjny sposób uzyskiwania dostępu do schowka systemowego polegał na korzystaniu z document.execCommand()w przypadku interakcji ze schowkiem. Choć ta metoda cięcia, wklejanie było płatne: dostęp do schowka był synchroniczny i możliwy był tylko odczyt i zapisz w DOM.

Jest to w porządku w przypadku krótkich fragmentów tekstu, ale w wielu przypadkach blokowanie strony na potrzeby przenoszenia tekstu ze schowka nie jest wygodne. Czasochłonne dezynfekcję lub bezpieczne wklejanie treści może wymagać dekodowania obrazu. Przeglądarka może być konieczne ładowanie lub wstawianie linków z wklejonego dokumentu. To byłaby zablokuj stronę podczas oczekiwania na dysk lub sieć. Wyobraź sobie, że dodasz uprawnienia, które wymagają zablokowania strony podczas żądania dostępu do schowka. Jednocześnie uprawnienia dotyczące interakcji z obsługą schowka są luźno zdefiniowane i różnią się w zależności od przeglądarki.

Interfejs Async Clipboard API rozwiązuje te problemy, zapewniając dobrze zdefiniowany model uprawnień, który nie blokuje strony. Interfejs Async Clipboard API jest ograniczony do obsługi tekstu i obrazów w większości przeglądarek, ale ich obsługa jest różna. Uważnie sprawdź działanie przeglądarki ze zgodnością w każdej z poniższych sekcji.

Kopiowanie: zapisywanie danych na schowku

writeText()

Aby skopiować tekst do schowka, naciśnij writeText(). Ponieważ ten interfejs API jest asynchroniczny, funkcja writeText() zwraca obietnicę, która jest akceptowana lub odrzucana w zależności od tego, czy przekazany tekst został skopiowany:

async function copyPageUrl() {
  try {
    await navigator.clipboard.writeText(location.href);
    console.log('Page URL copied to clipboard');
  } catch (err) {
    console.error('Failed to copy: ', err);
  }
}

Obsługa przeglądarek

  • Chrome: 66.
  • Krawędź: 79.
  • Firefox: 63.
  • Safari: 13.1

Źródło

write()

writeText() to tylko udogodnienie dla ogólnej metody write(). która pozwala skopiować obrazy do schowka. Podobnie jak writeText(), jest asynchroniczna i zwraca obietnicę.

Aby zapisać obraz w schowku, musisz mieć obraz w formacie blob. Jednym ze sposobów jest wysłanie żądania obrazu z serwera za pomocą fetch(), a potem wywołanie blob() w odpowiedzi.

Żądanie grafiki od serwera może być niepożądane lub niemożliwe w przypadku z wielu różnych przyczyn. Na szczęście możesz to również narysować na płótnie, wywołaj płótno toBlob() .

Następnie prześlij tablicę obiektów ClipboardItem jako parametr do metody write(). Obecnie można przekazać tylko 1 obraz naraz, ale mamy nadzieję, że w przyszłości będziemy mogli dodać obsługę większej liczby obrazów. ClipboardItem pobiera obiekt z typu MIME obrazu jako klucza i obiektu blob jako wartości. W przypadku obiektów blob uzyskanych z fetch() lub canvas.toBlob() właściwość blob.type automatycznie zawiera prawidłowy typ MIME obrazu.

try {
  const imgURL = '/images/generic/file.png';
  const data = await fetch(imgURL);
  const blob = await data.blob();
  await navigator.clipboard.write([
    new ClipboardItem({
      // The key is determined dynamically based on the blob's type.
      [blob.type]: blob
    })
  ]);
  console.log('Image copied.');
} catch (err) {
  console.error(err.name, err.message);
}

Możesz też napisać obietnicę do obiektu ClipboardItem. W przypadku tego wzorca musisz wcześniej znać typ MIME danych.

try {
  const imgURL = '/images/generic/file.png';
  await navigator.clipboard.write([
    new ClipboardItem({
      // Set the key beforehand and write a promise as the value.
      'image/png': fetch(imgURL).then(response => response.blob()),
    })
  ]);
  console.log('Image copied.');
} catch (err) {
  console.error(err.name, err.message);
}

Obsługa przeglądarek

  • Chrome: 76.
  • Edge: 79.
  • Firefox: 127.
  • Safari: 13.1

Źródło

Zdarzenie kopiowania

Jeśli użytkownik inicjuje kopiowanie ze schowka i nie wywołuje funkcji preventDefault(), zdarzenie copy zawiera właściwość clipboardData z elementami w odpowiednim formacie. Jeśli chcesz wdrożyć własną logikę, musisz wywołać metodę preventDefault(), aby zapobiegać domyślnemu działaniu na korzyść Twojej implementacji. W tym przypadku pole clipboardData jest puste. Załóżmy, że mamy stronę z tekstem i obrazem, a użytkownik zaznaczy wszystkie, zainicjuje kopię schowka, niestandardowe rozwiązanie powinno odrzucać tekst i tylko skopiować obraz. Możesz to osiągnąć w sposób podany w przykładowym poniżej kodzie. W tym przykładzie nie omówiono sposobu powrotu do wcześniejszej wersji. Interfejsy API, gdy interfejs Clipboard API nie jest obsługiwany.

<!-- The image we want on the clipboard. -->
<img src="kitten.webp" alt="Cute kitten.">
<!-- Some text we're not interested in. -->
<p>Lorem ipsum</p>
document.addEventListener("copy", async (e) => {
  // Prevent the default behavior.
  e.preventDefault();
  try {
    // Prepare an array for the clipboard items.
    let clipboardItems = [];
    // Assume `blob` is the blob representation of `kitten.webp`.
    clipboardItems.push(
      new ClipboardItem({
        [blob.type]: blob,
      })
    );
    await navigator.clipboard.write(clipboardItems);
    console.log("Image copied, text ignored.");
  } catch (err) {
    console.error(err.name, err.message);
  }
});

W przypadku wydarzenia copy:

Obsługa przeglądarek

  • Chrome: 1.
  • Edge: 12.
  • Firefox: 22.
  • Safari: 3.

Źródło

ClipboardItem:

Obsługa przeglądarek

  • Chrome: 76.
  • Edge: 79.
  • Firefox: 127.
  • Safari: 13.1

Źródło

Wklej: odczytywanie danych ze schowka

readText()

Aby odczytać tekst ze schowka, zadzwoń pod numer navigator.clipboard.readText() i zaczekaj dla zwróconej obietnicy do rozwiązania:

async function getClipboardContents() {
  try {
    const text = await navigator.clipboard.readText();
    console.log('Pasted content: ', text);
  } catch (err) {
    console.error('Failed to read clipboard contents: ', err);
  }
}

Obsługa przeglądarek

  • Chrome: 66.
  • Krawędź: 79.
  • Firefox: 125.
  • Safari: 13.1

Źródło

read()

Metoda navigator.clipboard.read() jest również asynchroniczna i zwraca lub obiecywanie. Aby odczytać obraz ze schowka, pobierz listę obiektów ClipboardItem, a następnie przejdź przez nią.

Każdy plik ClipboardItem może przechowywać różne typy treści, dlatego musisz wykonać iterację na liście typów, ponownie za pomocą pętli for...of. W przypadku każdego typu wywołaj metodę getType(), podając jako argument bieżący typ, aby uzyskać odpowiedni blob. Tak jak wcześniej, ten kod nie jest powiązany z obrazami i będzie z innymi typami plików, które pojawią się w przyszłości.

async function getClipboardContents() {
  try {
    const clipboardItems = await navigator.clipboard.read();
    for (const clipboardItem of clipboardItems) {
      for (const type of clipboardItem.types) {
        const blob = await clipboardItem.getType(type);
        console.log(URL.createObjectURL(blob));
      }
    }
  } catch (err) {
    console.error(err.name, err.message);
  }
}

Obsługa przeglądarek

  • Chrome: 76.
  • Krawędź: 79.
  • Firefox: 127.
  • Safari: 13.1

Źródło

Praca z wklejonymi plikami

Użytkownicy mogą korzystać ze skrótów klawiszowych, takich jak Ctrl + C i Ctrl + V. Chromium ujawnia w schowku pliki tylko do odczytu, jak opisano poniżej. To się uruchamia, gdy użytkownik kliknie domyślny skrót wklejania systemu operacyjnego. lub gdy użytkownik kliknie Edytuj, a następnie Wklej na pasku menu przeglądarki. Nie musisz pisać dodatkowego kodu.

document.addEventListener("paste", async e => {
  e.preventDefault();
  if (!e.clipboardData.files.length) {
    return;
  }
  const file = e.clipboardData.files[0];
  // Read the file's contents, assuming it's a text file.
  // There is no way to write back to it.
  console.log(await file.text());
});

Obsługa przeglądarek

  • Chrome: 3.
  • Edge: 12.
  • Firefox: 3.6
  • Safari: 4.

Źródło

Zdarzenie wklejania

Jak już wspomnieliśmy, planujemy wprowadzić zdarzenia do pracy z interfejsem Clipboard API, ale na razie możesz używać obecnego zdarzenia paste. Dobrze współpracuje z nowym narzędziem asynchronicznych metod odczytywania tekstu ze schowka. Podobnie jak w przypadku wydarzenia copy, nie zapomnij zadzwonić na numer preventDefault().

document.addEventListener('paste', async (e) => {
  e.preventDefault();
  const text = await navigator.clipboard.readText();
  console.log('Pasted text: ', text);
});

Obsługa przeglądarek

  • Chrome: 1.
  • Krawędź: 12.
  • Firefox: 22.
  • Safari: 3.

Źródło

Obsługa wielu typów MIME

Większość implementacji umieszcza w schowku różne formaty danych, aby je zapisać w jednym cięciu. lub kopiowania. Jest to spowodowane 2 powodami: jako deweloper aplikacji nie masz możliwości poznania możliwości aplikacji, do której użytkownik chce skopiować tekst lub obrazy, a wiele aplikacji obsługuje wklejanie danych uporządkowanych jako zwykły tekst. Zwykle jest to prezentowane użytkownikom z opcją Edytuj o nazwie, np. Wklej i styl dopasowania lub Wklej bez formatowania.

Poniżej znajdziesz przykład, jak to zrobić. W tym przykładzie dane obrazu są pobierane za pomocą interfejsu fetch(), ale mogą też pochodzić z interfejsu <canvas> lub File System API.

async function copy() {
  const image = await fetch('kitten.png').then(response => response.blob());
  const text = new Blob(['Cute sleeping kitten'], {type: 'text/plain'});
  const item = new ClipboardItem({
    'text/plain': text,
    'image/png': image
  });
  await navigator.clipboard.write([item]);
}

Bezpieczeństwo i uprawnienia

Dostęp do schowka zawsze stanowił zagrożenie dla przeglądarek. Bez odpowiednich uprawnień, strona może dyskretnie skopiować wszelkie szkodliwe treści do schowka użytkownika, co po wklejeniu może mieć katastrofalne skutki. Wyobraź sobie stronę internetową, która po cichu kopiuje rm -rf / lub obraz z bombą dekompresyjną do schowka.

Prośba przeglądarki o przyznanie uprawnień do schowka
Prośba o uprawnienia dla interfejsu Clipboard API.

Zapewnianie stronom internetowym nieograniczony dostęp do odczytu do schowka to jeszcze więcej kłopotliwe. Użytkownicy często kopiują na pulpit informacje poufne, takie jak hasła i dane osobowe, które mogą być odczytane przez dowolną stronę bez wiedzy użytkownika.

Podobnie jak w przypadku wielu nowych interfejsów API, Clipboard API jest obsługiwany tylko w przypadku stron wyświetlanych przez HTTPS. Aby zapobiec nadużyciom, dostęp do schowka jest dozwolony tylko wtedy, gdy strona jest aktywnej karty. Strony na aktywnych kartach mogą zapisywać w schowku bez konieczności prosi o uprawnienia, ale odczyt ze schowka zawsze wymaga uprawnienia.

Uprawnienia do kopiowania i wklejania zostały dodane do sekcji Permissions API. Uprawnienie clipboard-write jest przyznawane automatycznie stronom, gdy są aktywnej karty. Musisz poprosić o uprawnienie clipboard-read, które możesz , próbując odczytać dane ze schowka. Poniżej przedstawiono kod, który pokazuje tę drugą opcję:

const queryOpts = { name: 'clipboard-read', allowWithoutGesture: false };
const permissionStatus = await navigator.permissions.query(queryOpts);
// Will be 'granted', 'denied' or 'prompt':
console.log(permissionStatus.state);

// Listen for changes to the permission state
permissionStatus.onchange = () => {
  console.log(permissionStatus.state);
};

Możesz też określić, czy do wywołania wycięcia lub wywołania wymagany jest gest użytkownika do wklejania za pomocą opcji allowWithoutGesture. Domyślna wartość dla tej wartości zależy od przeglądarki, więc należy go zawsze dodawać.

W tym miejscu asynchroniczny charakter interfejsu Clipboard API okazuje się naprawdę przydatny: próba odczytu lub zapisu danych w schowku systemowym automatycznie wyświetla użytkownikowi prośbę o pozwolenie, jeśli nie zostało ono jeszcze udzielone. Ponieważ interfejs API opiera się na obietnicach, jest to całkowicie przejrzyste, a użytkownik odmówił przyznania dostępu do schowka obietnicę odrzucenia, aby strona mogła odpowiednio zareagować.

Przeglądarki zezwalają na dostęp do schowka tylko wtedy, gdy strona jest aktywną kartą, niektóre z tych przykładów nie działają po wklejeniu konsoli przeglądarki, ponieważ narzędzia dla programistów są aktywną kartą. Jeszcze rada: odłóż dostęp do schowka za pomocą funkcji setTimeout(), a następnie szybko kliknij wewnątrz strony, aby go zaznacz przed wywołaniem funkcji:

setTimeout(async () => {
  const text = await navigator.clipboard.readText();
  console.log(text);
}, 2000);

Integracja zasad dotyczących uprawnień

Aby używać interfejsu API w ramkach iframe, musisz go włączyć za pomocą zasad dotyczących uprawnień, które definiują mechanizm umożliwiający selektywne włączanie i wyłączanie różnych funkcji przeglądarki oraz interfejsów API. W zależności od potrzeb aplikacji musisz przekazać clipboard-read lub clipboard-write (lub oba te parametry).

<iframe
    src="index.html"
    allow="clipboard-read; clipboard-write"
>
</iframe>

Wykrywanie cech

Aby używać interfejsu Async Clipboard API we wszystkich przeglądarkach, przetestuj interfejs navigator.clipboard i w razie potrzeby użyj starszych metod. Oto przykład implementacji wklejania, która obejmuje inne przeglądarki.

document.addEventListener('paste', async (e) => {
  e.preventDefault();
  let text;
  if (navigator.clipboard) {
    text = await navigator.clipboard.readText();
  }
  else {
    text = e.clipboardData.getData('text/plain');
  }
  console.log('Got pasted text: ', text);
});

To nie wszystko. Przed wprowadzeniem interfejsu Async Clipboard API w różnych przeglądarkach stosowano różne implementacje kopiowania i wklejania. Większość przeglądarek umożliwia kopiowanie i wklejanie za pomocą skrótów document.execCommand('copy') i document.execCommand('paste'). Jeśli tekst, który ma zostać skopiowany, jest ciągiem znaków, który nie występuje w DOM, musi zostać wstrzyknięty do DOM i wybrany:

button.addEventListener('click', (e) => {
  const input = document.createElement('input');
  input.style.display = 'none';
  document.body.appendChild(input);
  input.value = text;
  input.focus();
  input.select();
  const result = document.execCommand('copy');
  if (result === 'unsuccessful') {
    console.error('Failed to copy text.');
  }
  input.remove();
});

Prezentacje

Z interfejsu Async Clipboard API możesz poeksperymentować w poniższych wersjach demonstracyjnych. W skrócie Może zremiksować wersję demonstracyjną tekstu lub prezentację obrazu, z nimi eksperymentować.

Pierwszy przykład pokazuje przenoszenie tekstu ze schowka i z niego.

Aby wypróbować interfejs API na przykładzie obrazów, skorzystaj z tej wersji demonstracyjnej. Pamiętaj, że obsługiwane są tylko pliki PNG i tylko w kilku przeglądarkach.

Podziękowania

Interfejs Asynchronous Clipboard API został zaimplementowany przez Darwina Huanga i Gary’ego Kačmarčíka. Darwin również zaprezentował prezentację. Dziękujemy Kyarik i ponownie Gary’emu Kačmarčíkowi za sprawdzenie części tego artykułu.

Baner powitalny autorstwa Markus Winkler na Unsplash.