העברה ל-User-Agent Client Hints

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

המחרוזת של סוכן המשתמש היא שטח משמעותי ליצירה של טביעת אצבע דיגיטלית (fingerprinting) בדפדפנים, בנוסף לקשה לעיבוד. עם זאת, יש כל מיני סיבות חוקיות לאיסוף ולעיבוד של נתונים של סוכן משתמש, ולכן מה שנחוץ הוא נתיב לפתרון טוב יותר. הכלי User-Agent Client Hints מספק דרך מפורשת להצהיר על הצורך שלכם בנתונים של סוכן משתמש ובשיטות להחזרת הנתונים בפורמט קל לשימוש.

במאמר הזה נסביר איך לבדוק את הגישה שלכם לנתוני סוכן-משתמש ולהעביר את השימוש במחרוזות סוכן-משתמש ל-User-Agent Client Hints.

ביקורת על איסוף נתונים של סוכן משתמש ושימוש בהם

כמו בכל צורה של איסוף נתונים, חשוב תמיד להבין למה אתם אוספים אותם. בתור התחלה, לא משנה אם אתם מתכוונים לבצע פעולה כלשהי, הוא להבין איפה ולמה אתם משתמשים בנתוני סוכן המשתמש.

אם אתם לא יודעים אם או איפה נעשה שימוש בנתוני סוכן המשתמש, כדאי לחפש בקוד של ממשק הקצה את השימוש ב-navigator.userAgent ובקוד של הקצה העורפי לשימוש בכותרת ה-HTTP User-Agent. עליכם לבדוק גם את הקוד של ממשק הקצה כדי להשתמש בתכונות שכבר הוצאו משימוש, כמו navigator.platform ו-navigator.appVersion.

מנקודת מבט פונקציונלית, חשבו על מקום כלשהו בקוד שבו אתם מקליטים או מעבדים:

  • שם או גרסה של דפדפן
  • שם או גרסה של מערכת הפעלה
  • יצרן או דגם המכשיר
  • סוג המעבד (CPU), ארכיטקטורה או סיביות (לדוגמה: 64 ביט)

סביר גם להניח שאתם משתמשים בספרייה או בשירות של צד שלישי כדי לעבד את הסוכן המשתמש. במקרה כזה, צריך לבדוק אם הם מעדכנים כדי לתמוך ב-User-Agent Client Hints.

האם נעשה שימוש רק בנתונים בסיסיים של סוכן משתמש?

קבוצת ברירת המחדל של רמזים על הלקוח (Client Hints) לגבי הסוכן המשתמש כוללת:

  • Sec-CH-UA: שם הדפדפן והגרסה הראשית/המשמעותית
  • Sec-CH-UA-Mobile: ערך בוליאני שמציין מכשיר נייד
  • Sec-CH-UA-Platform: שם מערכת ההפעלה

הגרסה המוקטנת של מחרוזת הסוכן המשתמש המוצעת תשמור גם את המידע הבסיסי הזה באופן שתואם לאחור. לדוגמה, במקום Chrome/90.0.4430.85, המחרוזת תכלול Chrome/90.0.0.0.

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

אפשר וכדאי לעבור ל-User-Agent Client Hints, אבל יכול להיות שיש לכם קוד קודם או אילוצי משאבים מדור קודם שמונעים זאת. צמצום המידע במחרוזת של סוכן המשתמש באופן הזה שתואם לאחור נועד להבטיח שהקוד הקיים יקבל מידע פחות מפורט, אבל הוא עדיין צריך לשמור על פונקציונליות בסיסית.

אסטרטגיה: API של JavaScript בצד הלקוח על פי דרישה

אם אתם משתמשים כרגע ב-navigator.userAgent, כדאי לעבור להעדפה של navigator.userAgentData לפני שחוזרים לניתוח המחרוזת של סוכן המשתמש.

if (navigator.userAgentData) {
  // use new hints
} else {
  // fall back to user-agent string parsing
}

אם אתם בודקים אם מדובר בנייד או במחשב, השתמשו בערך mobile הבוליאני:

const isMobile = navigator.userAgentData.mobile;

userAgentData.brands הוא מערך של אובייקטים עם המאפיינים brand ו-version שבהם הדפדפן יכול לציין את התאימות שלו למותגים האלה. אפשר לגשת אליה ישירות כמערך או להשתמש בקריאת some() כדי לבדוק אם קיימת רשומה ספציפית:

function isCompatible(item) {
  // In real life you most likely have more complex rules here
  return ['Chromium', 'Google Chrome', 'NewBrowser'].includes(item.brand);
}
if (navigator.userAgentData.brands.some(isCompatible)) {
  // browser reports as compatible
}

אם אתם זקוקים לאחד מהערכים של סוכן משתמש בעל אנטרופיה גבוהה יותר, עליכם לציין אותו ולחפש את התוצאה ב-Promise שהוחזר:

navigator.userAgentData.getHighEntropyValues(['model'])
  .then(ua => {
    // requested hints available as attributes
    const model = ua.model
  });

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

אסטרטגיה: כותרת סטטית בצד השרת

אם אתם משתמשים בכותרת הבקשה User-Agent בשרת והצרכים שלכם לנתונים האלה עקביים יחסית בכל האתר, אפשר לציין את הרמזים הרצויים של הלקוח כקבוצה סטטית בתגובות. הגישה הזו פשוטה יחסית, כי בדרך כלל צריך להגדיר אותה רק במיקום אחד. לדוגמה, אם אתם כבר מוסיפים לשם כותרות, את הגדרות האירוח או את התצורה ברמה העליונה של ה-framework או הפלטפורמה שבהם אתם משתמשים באתר, זה יכול להיות בתצורה של שרת האינטרנט.

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

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

לדוגמה, ברירות המחדל הנוכחיות של Chrome מיוצגות כך:

🎂️ כותרות תגובה

Accept-CH: Sec-CH-UA-Mobile, Sec-CH-UA-Platform, Sec-CH-UA

אם רוצים לקבל בתשובות גם את דגם המכשיר, צריך לשלוח:

🎂️ כותרות תגובה

Accept-CH: Sec-CH-UA-Mobile, Sec-CH-UA-Model, Sec-CH-UA-Platform, Sec-CH-UA

כשמבצעים עיבוד בצד השרת, קודם צריך לבדוק אם הכותרת Sec-CH-UA הרצויה נשלחה ואז לחזור לניתוח הכותרת User-Agent אם היא לא זמינה.

אסטרטגיה: האצלת רמזים לבקשות ממקורות שונים

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

לדוגמה, נניח שהדומיין https://blog.site מארח משאבים ב-https://cdn.site, שיכולים להחזיר משאבים שעברו אופטימיזציה למכשיר ספציפי. https://blog.site יכול לבקש את הרמז Sec-CH-UA-Model, אבל צריך להעניק לו גישה באופן מפורש ל-https://cdn.site באמצעות הכותרת Permissions-Policy. רשימת הרמזים שבשליטת המדיניות זמינה בטיוטת התשתית ללקוחות

🎂️ תשובה של blog.site בהאצלת הרמז

Accept-CH: Sec-CH-UA-Model
Permissions-Policy: ch-ua-model=(self "https://cdn.site")

⬆️ בקשות למשאבי משנה ב-cdn.site כוללות את הרמז שהוקצה

Sec-CH-UA-Model: "Pixel 5"

אפשר לציין כמה רמזים לכמה מקורות, ולא רק מהטווח ch-ua:

✓️ תגובה של blog.site, שנותנת גישה למספר רמזים לכמה מקורות

Accept-CH: Sec-CH-UA-Model, DPR
Permissions-Policy: ch-ua-model=(self "https://cdn.site"),
                    ch-dpr=(self "https://cdn.site" "https://img.site")

אסטרטגיה: האצלת רמזים ל-iframes

מסגרות iframe ממקורות שונים פועלים באותו אופן כמו משאבים ממקורות שונים, אבל במאפיין allow מציינים את הרמזים שרוצים להעניק להם גישה.

❗️ תשובה של blog.site

Accept-CH: Sec-CH-UA-Model

↪️ HTML בחשבון blog.site

<iframe src="https://widget.site" allow="ch-ua-model"></iframe>

⬆️ בקשה אל widget.site

Sec-CH-UA-Model: "Pixel 5"

המאפיין allow ב-iframe יחליף כל כותרת Accept-CH ש-widget.site עשוי לשלוח מעצמו, לכן חשוב לוודא שציינתם את כל מה שצריך לאתר ב-iframe.

אסטרטגיה: רמזים דינמיים בצד השרת

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

מה שחשוב לזכור כאן הוא שכל מופע של הכותרת Accept-CH יחליף בפועל את הקבוצה הקיימת. לכן, במקרה שמגדירים את הכותרת באופן דינמי, צריך לבקש בכל דף את כל הרמזים הנדרשים.

לדוגמה, יכול להיות שיש לכם חלק באתר שבו אתם רוצים לספק סמלים ואמצעי בקרה שתואמים למערכת ההפעלה של המשתמש. כדי לעשות זאת, יכול להיות שתרצו להשתמש גם ב-Sec-CH-UA-Platform-Version כדי להציג את משאבי המשנה המתאימים.

❗️ כותרות תגובה עבור /blog

Accept-CH: Sec-CH-UA-Mobile, Sec-CH-UA-Platform, Sec-CH-UA

❗️ כותרות תגובה עבור /app

Accept-CH: Sec-CH-UA-Mobile, Sec-CH-UA-Platform, Sec-CH-UA-Platform-Version, Sec-CH-UA

אסטרטגיה: רמזים בצד השרת שנדרשים בבקשה הראשונה

יכולים להיות מקרים שבהם יהיה צורך ביותר מקבוצת הרמזים המוגדרת כברירת מחדל בבקשה הראשונה, אבל סביר להניח שזה יהיה נדיר, לכן חשוב לבדוק את הסיבות.

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

לרמזים נוספים לגבי הבקשה הראשונה, יש שתי אפשרויות. קודם כול, אפשר להשתמש בכותרת Critical-CH. שימוש באותו פורמט של Accept-CH, אבל הוא אומר לדפדפן שעליו לנסות ומיד אחר כך את הבקשה אם הבקשה הראשונה נשלחה ללא הרמז הקריטי.

⬆️ בקשה ראשונית

[With default headers]

🎂️ כותרות תגובה

Accept-CH: Sec-CH-UA-Model
Critical-CH: Sec-CH-UA-Model

🔃 הדפדפן מנסה שוב לשלוח בקשה ראשונית עם הכותרת הנוספת

[With default headers + …]
Sec-CH-UA-Model: Pixel 5

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

במצבים שבהם נדרש רמזים נוספים בזמן טעינת הדף הראשונה, ההצעה של Client Hints Reliability היא פריסת המסלול לציון רמזים בהגדרות של רמת החיבור. כך משתמשים בתוסף Application-Layer Protocol Settings(ALPS) ל-TLS (אבטחת שכבת התעבורה) ל-TLS 1.3, כדי לאפשר העברה מוקדמת של רמזים בחיבורי HTTP/2 ו-HTTP/3. זהו עדיין בשלב מוקדם מאוד, אבל אם אתם מנהלים באופן פעיל את הגדרות ה-TLS והחיבור שלכם, זה הזמן האידיאלי לתרום תוכן.

אסטרטגיה: תמיכה בגרסה הקודמת

יכול להיות שבאתר שלך יש קוד מדור קודם או קוד של צד שלישי שתלוי ב-navigator.userAgent, כולל חלקים במחרוזת הסוכן המשתמש שיופחתו. בטווח הארוך, כדאי לתכנן לעבור לקריאות navigator.userAgentData המקבילות, אבל יש פתרון זמני.

המאגר של UA-CH הוא ספרייה קטנה שמאפשרת להחליף את navigator.userAgent במחרוזת חדשה שנוצרה על סמך הערכים המבוקשים של navigator.userAgentData.

לדוגמה, הקוד הזה יפיק מחרוזת של סוכן משתמש שכוללת בנוסף את הרמז ל "מודל":

import { overrideUserAgentUsingClientHints } from './uach-retrofill.js';
overrideUserAgentUsingClientHints(['model'])
  .then(() => { console.log(navigator.userAgent); });

המחרוזת שתתקבל תציג את המודל Pixel 5, אבל עדיין להציג את הערך המצומצם 92.0.0.0 כי לא נשלחה בקשה לרמז uaFullVersion:

Mozilla/5.0 (Linux; Android 10.0; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.0.0 Mobile Safari/537.36

תמיכה נוספת

אם האסטרטגיות האלה לא עוסקות בתרחיש לדוגמה שלכם, תוכלו להתחיל דיון במאגר התמיכה של privacy-sandbox-dev-support כדי שנוכל לבדוק יחד את הבעיה.

צילום של Ricardo Rocha ב-UnFlood