Clipboard API

הסקריפטים שלכם יכולים לשנות את ה-clipboard בקלות בדרך מודרנית, אסינכרונית ופשוטה - או באמצעות API או באמצעות אירועים.

אני מאמין שכל מי שקורא את השורות האלו מכיר את ה-Clipboard. מדובר בזכרון קטן שיכול להכיל תמונות וטקסטים והוא קיים בכל מערכת הפעלה שהיא. כמתכנתי אתרי אינטרנט אנחנו יכולים להתערב לא מעט במה שהמשתמש מעתיק ואפילו לקרוא את התוכן (לאחר קבלת הרשאה מהמשתמש). והכל עם ג'אווהסקריפט פשוט ביותר שעובד בכל הדפדפנים חוץ מאקספלורר.

עד לא מזמן, עבדנו עם document.execCommand כדי לעשות את זה. אבל בקרוב גם כרום וגם פיירפוקס יחסמו את document.execCommand – זה הזמן לגלות את Clipboard API! וגם לגלות כל מיני סכנות אבטחה מאוד מגניבות שבאות איתו.

אז ראשית, ל-Clipboard API הבסיסי:

כתיבה וקריאת מידע מה-Clipboard

הכנסת המידע היא פשוטה ביותר ונעשית באמצעות navigator.clipboard. בהוצאת המידע אנו נדרשים לבצע בדיקת הרשאות בדומה לפיצ'רים אחרים.

קודם כל – דוגמה קטנה שעובדת גם להעתקה וגם להדבקה. שימו לב שכשתדביקו – אתם תקבלו בקשה מהאתר לגישה למידע הזה.

הקוד נראה כך:

<div>
    <input type="text" class="to-copy" placeholder="כתבו פה משהו">
    <button class="write-button">העתיקו</button>
  </div>

  <div>
    <h3 class="clipboard-results"></h3>
    <button class="read-button">הדביקו</button>
  </div>
  <script>
const readButton = document.querySelector('.read-button');
const writeButton = document.querySelector('.write-button');

const resultsElement = document.querySelector('.clipboard-results');
const inputEl = document.querySelector('.to-copy');

const readHandler = async () => {
  try {
    const text = await navigator.clipboard.readText();
    resultsElement.innerText = text;
  } catch(e) {
    console.log('Error', e);
  }
};

readButton.addEventListener('click', readHandler);

const writeHandler = async () => {
  const inputValue = inputEl.value.trim() || '';
  try {
    await navigator.clipboard.writeText(inputValue);
    // The below code is just a UI indication
    const buttonText = 'הועתק, נסו להדביק';
    const originalText = writeButton.innerText;
    writeButton.innerText = buttonText;
    setTimeout(() => {
      writeButton.innerText = originalText;
    }, 2000);
  } catch(e) {
    console.log('Error', e);
  }
}

writeButton.addEventListener('click', writeHandler);
  </script>

הקוד הזה מדבר בעד עצמו אבל בואו נעשה סריקה מהירה. יש כאן שני כפתורים שהצמדתי אירוע לכל אחד מהם. אחד לקריאה (כלומר לקרוא מה שיש ב-Clipboard) והשני הוא כתיבה (כלומר לכתוב ב-Clipboard).

כתיבה

הכתיבה – כלומר למחוק (בלי לראות) את כל מה שיש ב-clipboard, לזרוק אותו ולשים שם את מה אנחנו רוצים זה פשוט. אם אני רוצה לכתוב טקסט, אני משתמש במתודת writeText. זו מתודה שמחזירה פרומיס:

navigator.clipboard.writeText(inputValue);

אין פה יותר מדי כאב ראש בטקסט. אבל המתודה הזו היא writeText. אם אנו רוצים לבצע העתקה של כל דבר אחר, זה מעט יותר מסובך ואנו צריכים להשתמש במתודת write שהמידע שהיא מקבלת הוא אובייקט מסוג ClipboardItem שהוא אובייקט מובנה בג'אווהסקריפט שאנחנו יכולים להצהיר על הסוג שלו. למשל:

function setClipboard(text) {
  let data = [new ClipboardItem({ "text/plain": text })];

  navigator.clipboard.write(data).then(function() {
    /* success */
  }, function() {
    /* failure */
  });
}

ואם אנו רוצים לבצע העתקה של תמונה או canvas, אני אצטרך לבצע המרה ל-blob ואז הדבקה. למשל:

function copyCanvasContentsToClipboard(canvas, onDone, onError) {
  canvas.toBlob(function (blob) {
    let data = [new ClipboardItem({ [blob.type]: blob })];

    navigator.clipboard.write(data).then(function () {
      onDone();
    }, function (err) {
      onError(err);
    })
  });
}

את הדוגמאות המאוד פשוטות האלו לקחתי מ-MDN. רוב הזמן השתמשתי בקוד הפשוט של העתקת הטקסט כי זה בד"כ מה שמשתמשים בו – כדי לסייע למשתמשים להעתיק קודים מסובכים למשל (כמו UUID או קישורים).מדובר ב-promise אז או שאנו עוטפים אותה ב-async-await או שאנו משתמשים ב-then. במקרה הזה כתבתי ג׳אווהסקריפט מודרני ופשוט של async-await.

קריאה

זו בעצם קריאה מה-clipboard. ממש לראות מה יש שם. אם מדובר בטקסט, אז יש לנו את מתודת readText שגם היא מחזירה פרומיס.

navigator.clipboard.readText();

וכמובן שיש לנו במקרה שמדובר בתמונה או סוג מידע אחר, את מתודת read שעליה לא ארחיב.

ברוב המקרים אתם תרצו להשתמש ב-readText וב-writeText. מסיבות אבטחה, המשתמש יקבל אינדיקציה לכך שאתם הולכים להשתמש ביכולת הקריאה – כלומר לראות מה יש לו ב-clipboard. למה? כדי שלא תוכלו לפשפש בסודותיו. זה מידע שיכול לבוא מחוץ לאתר ולסקריפט שלכם. האזהרה תבוא ככה:

בקשת אישור של האתר ברגע שיש כתיבה

לכתוב אל ה-clipboard לא מחייב הרשאה. רק קריאה תקפיץ את החלון הזה למשתמשים.

אם יש לכם feature-toggle או Permissions-Policy, אתם יכולים לאסור על סקריפטים אפילו לבקש את ההרשאה הזו (זו הסיבה שהקוד לעיל לא יעבוד ב-codepn.io, כי הם כיבו את היכולת של סקריפטים לגשת ל-clipboard).

אירועי Clipboard

מה שלא מחייב הרשאות משתמש הוא עבודה עם האירועים הקשורים ל-clipboard. כאשר משתמש מבצע העתקה (קונטרול C או קונטרול X או כפתור ימני והעתקה או גזירה), אנו יכולים להתפס על האירוע הזה. מדובר באירוע כמו כל אירוע. הוא אירוע שאנו יכולים להאזין לו גלובלית או להצמיד אותו לאלמנט מסוים שנמצא בפוקוס. בד״כ שמים אותו גלובלית.

ההסבר יותר מסובך מהקוד. הקוד נראה בפועל ככה:

document.addEventListener('copy', (event) => {
    const selection = document.getSelection();
    event.clipboardData.setData('text/plain', selection.toString().toUpperCase());
    event.preventDefault();
});

document.addEventListener('cut', (event) => {
    const selection = document.getSelection();
    event.clipboardData.setData('text/plain', selection.toString().toUpperCase());
    event.preventDefault();
});

והקוד הזה מתלבש על אירועי העתקה, לוקח את מה שהעתקנו באמצעות document.getSelection ואז משנה את המידע שנכנס לתוך ה-clipboard, משנה את האותיות לאותיות גדולות וזהו.

הנה הדמו שיעבוד יפה מאוד ב-codepen כי האירועים עובדים בלי קשר ל feature-toggle:

See the Pen clipboard events by Ran Bar-Zik (@barzik-the-vuer) on CodePen.

פינת אבטחת המידע

אם אתם ממש רוצים לעצבן את המשתמשים שלכם, אתם יכולים להשתמש באירוע הזה כדי לשנות את הטקסט (למשל להוסיף את המשפט: ״הועתק מהאתר של רן בר-זיק החתיך וההורס!״) אבל אם אתם יותר זדוניים – בהחלט אפשר להשתמש בזה להתקפות מעניינות.

למשל, לקחת את הטקסט ולהוסיף לו תוספת קטנה:

<img src="a" onerror="alert(1)" />

זו בעצם התקפת XSS שאפשר למשל ליצור על אנשים שמשתמשים ב WYSIWYG כמו ג׳ימייל. לגרום להם להעתיק טקסטים ולהדביק אותם וברגע שהם מדביקים, הפיילואוד שיש ב-onerror יעבוד. במקרה הזה alert ובמקרה אחר לא. והמשתמש לא ידע כלום. נכון, גם דפדפנים וגם עורכי WYSIWYG מכירים את הפרצה הזו, אבל חוקר אבטחה בשם Michał Bentkowski כתב מחקר מעמיק שמראה איך הוא מצליח לעקוף את ההגנות האלו. מאוד מעניין לדעתי. האם צריך להכנס ללחץ? ובכן – אין התקפות כאלו באוויר הפתוח אבל ללא ספק מדובר פה בהתקפה מעניינת.

מה שעוד אפשר לעשות כדי לבצע ניטור זה להוסיף zero width characters לטקסט המועתק וכך – אם מדובר בטקסט מסווג, לעלות על מי שיוציא אותו החוצה. כתבתי על הטכניקה הזו לא מעט פעמים (אני גם משתמש בה כאמצעי הגנה על ספר הג׳אווהסקריפט שלי). ועוד מיני תעלולים שונים. כמו למשל לשלוח טלמטריה (בקשת AJAX ללוגר) בכל פעם שהמשתמש החצוף מבצע העתקה מהאתר שלכם. ומסתבר שטיק-טוק עושים את זה באפליקציה שלהם (יחד עם עוד נבלות) אבל אני כבר חורג.

הכרת ה-API של ה-clipboard היא שימושית במקרה ויש לכם קוד או טקסט שהמשתמשים שלכם צריכים להעתיק ואתם רוצים להקל עליהם. היא פותחת פתח לצרות מאוד מעניינות שכרגע תיאורטיות אבל לכו תדעו.

וכמובן פותחת לכם את הדרך להוסיף ״כל הזכויות שמורות לרן בר-זיק החתיך״ אם אתם ממש דבילים.

פוסטים נוספים שכדאי לקרוא

פתרונות ומאמרים על פיתוח אינטרנט

נגישות טכנית – פודקאסט ומבוא

פרק בפודקאסטעל נגישות בעברית שצולל לכלים האוטומטיים ולפן המאד מאד טכני של הנגישות.

גלילה לראש העמוד