ה-WebHID API מאפשר לאתרים לגשת למקלדות עזר חלופיות ולג'ויסטיקים מיוחדים.
פורסם: 15 בספטמבר 2020
יש הרבה מכשירי ממשק אנושי (HID), כמו מקלדות חלופיות או ג'ויסטיקים לא שגרתיים, שהם חדשים מדי, ישנים מדי או לא נפוצים מספיק כדי שמנהלי ההתקנים של המערכות יוכלו לגשת אליהם. ה-WebHID API פותר את הבעיה הזו באמצעות מתן דרך להטמעת לוגיקה ספציפית למכשיר ב-JavaScript.
תרחישים לדוגמה
מכשיר HID מקבל קלט מבני אדם או מספק פלט לבני אדם. דוגמאות למכשירים: מקלדות, מכשירים להצבעה (עכברים, מסכי מגע וכו') וגיימפדים. פרוטוקול HID מאפשר גישה למכשירים האלה במחשבים נייחים באמצעות מנהלי התקנים של מערכת ההפעלה. פלטפורמת האינטרנט תומכת במכשירי HID באמצעות מנהלי ההתקנים האלה.
הבעיה של חוסר גישה למכשירי HID לא נפוצים בולטת במיוחד כשמדובר במקלדות עזר חלופיות (כמו Elgato Stream Deck, Jabra headsets, X-keys) ובשלטים למשחקים מסוגים לא שגרתיים. בקרים למחשבים שולחניים משתמשים בדרך כלל בתקשורת עם מכשירי ממשק אנושי כדי לקבל קלט מהבקר (לחצנים, ג'ויסטיקים, טריגרים) ולשלוח פלט (נוריות LED, רטט).
לצערנו, אין סטנדרטיזציה טובה של קלט ופלט של גיימפדים, ולכן דפדפני אינטרנט לרוב דורשים לוגיקה מותאמת אישית למכשירים ספציפיים. המצב הזה לא יכול להימשך לאורך זמן, והתוצאה היא תמיכה לא מספקת במכשירים ישנים ולא נפוצים. בנוסף, הדפדפן תלוי במוזרויות בהתנהגות של מכשירים ספציפיים.
הסברים על המונחים
מכשיר ממשק אנושי (HID) יכול לקבל קלט או להציע פלט לבני אדם. יש פרוטוקול HID, תקן לתקשורת דו-כיוונית בין מארח לבין מכשיר, שמיועד לפשט את תהליך ההתקנה.
ממשק HID מבוסס על שני מושגים בסיסיים: דוחות ותיאורי דוחות. הדוחות הם הנתונים שמועברים בין מכשיר לבין לקוח תוכנה. מתאר את הפורמט והמשמעות של הנתונים שהמכשיר תומך בהם.
אפליקציות ומכשירי HID מחליפים נתונים בינאריים באמצעות שלושה סוגי דוחות:
| סוג הדוח | תיאור |
|---|---|
| דוח קלט | נתונים שנשלחים מהמכשיר לאפליקציה (למשל, כשלוחצים על לחצן). |
| דוח פלט | נתונים שנשלחים מהאפליקציה למכשיר (למשל, בקשה להדליק את התאורה האחורית של המקלדת). |
| דוח תכונות | נתונים שעשויים להישלח לשני הכיוונים. הפורמט הוא ספציפי למכשיר. |
מתאר דוח מתאר את הפורמט הבינארי של דוחות שהמכשיר תומך בהם. המבנה שלו היררכי, ואפשר לקבץ דוחות יחד כאוספים נפרדים בתוך האוסף ברמה העליונה. הפורמט של המתאר מוגדר על ידי מפרט ה-HID.
שימוש ב-HID הוא ערך מספרי שמתייחס לקלט או לפלט סטנדרטיים. ערכי השימוש מאפשרים למכשיר לתאר את השימוש המיועד בו ואת המטרה של כל שדה בדוחות שלו. לדוגמה, מוגדר אחד ללחצן השמאלי בעכבר. השימושים מסודרים גם בדפי שימוש, שבהם מוצגת אינדיקציה לגבי הקטגוריה ברמה הגבוהה של המכשיר או הדוח.
שימוש ב-WebHID API
כדי לבדוק אם WebHID API נתמך, משתמשים בפקודה:
if ("hid" in navigator) {
// The WebHID API is supported.
}
פתיחת חיבור HID
ה-WebHID API הוא אסינכרוני כדי למנוע חסימה של ממשק המשתמש של האתר בזמן ההמתנה לקלט. זה חשוב כי אפשר לקבל נתוני HID בכל שלב, ולכן צריך דרך להאזין להם.
כדי לפתוח חיבור HID, קודם צריך לגשת לאובייקט HIDDevice. לשם כך, אפשר לבקש מהמשתמש לבחור מכשיר באמצעות קריאה ל-navigator.hid.requestDevice(), או לבחור מכשיר מתוך navigator.hid.getDevices(), שמחזירה רשימה של מכשירים שהאתר קיבל אליהם גישה בעבר.
הפונקציה navigator.hid.requestDevice() מקבלת אובייקט חובה שמגדיר מסננים. הערכים האלה משמשים להתאמה של כל מכשיר שמחובר באמצעות מזהה ספק USB (vendorId), מזהה מוצר USB (productId), ערך של דף שימוש (usagePage) וערך שימוש (usage). אפשר לקבל את הערכים האלה ממאגר מזהי ה-USB וממסמך טבלאות השימוש ב-HID.
אובייקטים מרובים של HIDDevice שמוחזרים על ידי הפונקציה הזו מייצגים ממשקי HID מרובים באותו מכשיר פיזי.
// Filter on devices with the Nintendo Switch Joy-Con USB Vendor/Product IDs.
const filters = [
{
vendorId: 0x057e, // Nintendo Co., Ltd
productId: 0x2006 // Joy-Con Left
},
{
vendorId: 0x057e, // Nintendo Co., Ltd
productId: 0x2007 // Joy-Con Right
}
];
// Prompt user to select a Joy-Con device.
const [device] = await navigator.hid.requestDevice({ filters });
// Get all devices the user has previously granted the website access to.
const devices = await navigator.hid.getDevices();
אפשר גם להשתמש במקש האופציונלי exclusionFilters ב-navigator.hid.requestDevice() כדי להחריג מהבורר של הדפדפן מכשירים מסוימים שידוע שהם לא תקינים.
// Request access to a device with vendor ID 0xABCD. The device must also have
// a collection with usage page Consumer (0x000C) and usage ID Consumer
// Control (0x0001). The device with product ID 0x1234 is malfunctioning.
const [device] = await navigator.hid.requestDevice({
filters: [{ vendorId: 0xabcd, usagePage: 0x000c, usage: 0x0001 }],
exclusionFilters: [{ vendorId: 0xabcd, productId: 0x1234 }],
});
אובייקט HIDDevice מכיל מזהי ספק ומזהי מוצר של USB לצורך זיהוי המכשיר. המאפיין collections שלו מאותחל עם תיאור היררכי של פורמטי הדוחות של המכשיר.
for (let collection of device.collections) {
// An HID collection includes usage, usage page, reports, and subcollections.
console.log(`Usage: ${collection.usage}`);
console.log(`Usage page: ${collection.usagePage}`);
for (let inputReport of collection.inputReports) {
console.log(`Input report: ${inputReport.reportId}`);
// Loop through inputReport.items
}
for (let outputReport of collection.outputReports) {
console.log(`Output report: ${outputReport.reportId}`);
// Loop through outputReport.items
}
for (let featureReport of collection.featureReports) {
console.log(`Feature report: ${featureReport.reportId}`);
// Loop through featureReport.items
}
// Loop through subcollections with collection.children
}
כברירת מחדל, מכשירי HIDDevice מוחזרים במצב 'סגור', וצריך לפתוח אותם באמצעות קריאה ל-open() לפני שאפשר לשלוח או לקבל נתונים.
// Wait for the HID connection to open before sending/receiving data.
await device.open();
קבלת דוחות על קלט

אחרי שנוצר חיבור HID, אפשר לטפל בדוחות קלט נכנסים על ידי האזנה לאירועי "inputreport" מהמכשיר. האירועים האלה מכילים את נתוני ה-HID כאובייקט DataView (data), את מכשיר ה-HID שאליו הם שייכים (device) ואת מזהה הדוח בן 8 הביטים שמשויך לדוח הקלט (reportId).
בהמשך לדוגמה הקודמת, הקוד הזה עוזר לכם לזהות איזה לחצן המשתמש לחץ במכשיר Joy-Con Right, כדי שתוכלו לנסות אותו בבית.
device.addEventListener("inputreport", event => {
const { data, device, reportId } = event;
// Handle only the Joy-Con Right device and a specific report ID.
if (device.productId !== 0x2007 && reportId !== 0x3f) return;
const value = data.getUint8(0);
if (value === 0) return;
const someButtons = { 1: "A", 2: "X", 4: "B", 8: "Y" };
console.log(`User pressed button ${someButtons[value]}.`);
});
אפשר לעיין בהדגמה ב-CodePen.
שליחת דוחות פלט
כדי לשלוח דוח פלט למכשיר HID, מעבירים את מזהה הדוח בן 8 הביטים שמשויך לדוח הפלט (reportId) ואת הבייטים כ-BufferSource (data) אל device.sendReport(). ההבטחה שמוחזרת נפתרת אחרי שהדוח נשלח. אם מכשיר ה-HID לא משתמש במזהי דוחות, צריך להגדיר את reportId ל-0.
הדוגמה הבאה מתייחסת למכשיר Joy-Con ומראה איך לגרום לו לרעוד באמצעות דוחות פלט.
// First, send a command to enable vibration.
// Magical bytes come from https://github.com/mzyy94/joycon-toolweb
const enableVibrationData = [1, 0, 1, 64, 64, 0, 1, 64, 64, 0x48, 0x01];
await device.sendReport(0x01, new Uint8Array(enableVibrationData));
// Then, send a command to make the Joy-Con device rumble.
// Actual bytes are available in the sample.
const rumbleData = [ /* ... */ ];
await device.sendReport(0x10, new Uint8Array(rumbleData));
אפשר לעיין בהדגמה ב-CodePen.
שליחה וקבלה של דוחות על תכונות

דוחות התכונות הם הסוג היחיד של דוחות נתוני HID שיכולים לנוע בשני הכיוונים. הם מאפשרים למכשירי HID ולאפליקציות HID להחליף נתוני HID לא סטנדרטיים. בניגוד לדוחות קלט ופלט, דוחות התכונות לא מתקבלים או נשלחים מהאפליקציה באופן קבוע.
כדי לשלוח דוח תכונות למכשיר HID, מעבירים את מזהה הדוח בן 8 הביטים שמשויך לדוח התכונות (reportId) ואת הבייטים כ-BufferSource (data) אל device.sendFeatureReport(). ההבטחה שמוחזרת נפתרת אחרי שהדוח נשלח. אם מכשיר ה-HID לא משתמש במזהי דוחות, צריך להגדיר את reportId ל-0.
בדוגמה הזו מוסבר איך להשתמש בדוחות תכונות. הדוגמה מראה איך לבקש מכשיר תאורה אחורית של מקלדת Apple, לפתוח אותו ולגרום לו להבהב.
const waitFor = duration => new Promise(r => setTimeout(r, duration));
// Prompt user to select an Apple Keyboard Backlight device.
const [device] = await navigator.hid.requestDevice({
filters: [{ vendorId: 0x05ac, usage: 0x0f, usagePage: 0xff00 }]
});
// Wait for the HID connection to open.
await device.open();
// Blink!
const reportId = 1;
for (let i = 0; i < 10; i++) {
// Turn off
await device.sendFeatureReport(reportId, Uint32Array.from([0, 0]));
await waitFor(100);
// Turn on
await device.sendFeatureReport(reportId, Uint32Array.from([512, 0]));
await waitFor(100);
}
אפשר לעיין בהדגמה ב-CodePen.
כדי לקבל דוח תכונות ממכשיר HID, צריך להעביר את מזהה הדוח בן 8 הביטים
שמשויך לדוח התכונות (reportId) אל
device.receiveFeatureReport(). ההבטחה שמוחזרת נפתרת עם אובייקט DataView שמכיל את התוכן של דוח התכונות. אם מכשיר ה-HID
לא משתמש במזהי דוחות, צריך להגדיר את reportId כ-0.
// Request feature report.
const dataView = await device.receiveFeatureReport(/* reportId= */ 1);
// Read feature report contents with dataView.getInt8(), getUint8(), etc...
האזנה לחיבור ולניתוק
לאחר שהאתר קיבל הרשאה לגשת להתקן HID, הוא יכול לקבל באופן פעיל אירועי חיבור וניתוק על ידי האזנה לאירועים "connect" ו-"disconnect".
navigator.hid.addEventListener("connect", event => {
// Automatically open event.device or warn user a device is available.
});
navigator.hid.addEventListener("disconnect", event => {
// Remove |event.device| from the UI.
});
ביטול הגישה למכשיר HID
האתר יכול לנקות את ההרשאות לגישה למכשיר HID שהוא כבר לא מעוניין לשמור, על ידי קריאה ל-forget() במופע HIDDevice. לדוגמה, באפליקציית אינטרנט חינוכית שמשמשת במחשב משותף עם מכשירים רבים, מספר גדול של הרשאות מצטברות שנוצרו על ידי משתמשים יוצר חוויית משתמש גרועה.
התקשרות אל forget() במופע יחיד של HIDDevice תבטל את הגישה לכל ממשקי ה-HID באותו מכשיר פיזי.
// Voluntarily revoke access to this HID device.
await device.forget();
התכונה forget() זמינה ב-Chrome 100 ומעלה, לכן צריך לבדוק אם היא נתמכת באמצעות הפעולות הבאות:
if ("hid" in navigator && "forget" in HIDDevice.prototype) {
// forget() is supported.
}
טיפים למפתחים

אפשר לנפות באגים ב-HID ב-Chrome באמצעות הדף הפנימי about://device-log, שבו אפשר לראות את כל האירועים שקשורים למכשירי HID ו-USB במקום אחד.
כדי להעביר מידע על מכשיר HID לפורמט קריא, אפשר להשתמש בכלי HID Explorer. הפונקציה ממפה מערכי שימוש לשמות לכל שימוש ב-HID.
ברוב מערכות Linux, מכשירי HID ממופים עם הרשאות לקריאה בלבד כברירת מחדל. כדי לאפשר ל-Chrome לפתוח מכשיר HID, צריך להוסיף כלל udev חדש. יוצרים קובץ במיקום /etc/udev/rules.d/50-yourdevicename.rules עם התוכן הבא:
KERNEL=="hidraw*", ATTRS{idVendor}=="[yourdevicevendor]", MODE="0664", GROUP="plugdev"
בדוגמה הזו, [yourdevicevendor] הוא 057e, למשל אם המכשיר הוא
Nintendo Switch Joy-Con. אפשר להוסיף ATTRS{idProduct} כדי ליצור כלל ספציפי יותר. מוודאים שחשבון user הוא חבר בקבוצה plugdev. אחר כך פשוט מחברים מחדש את המכשיר.
הדגמות
דוגמאות ל-WebHID מפורטות בכתובת web.dev/hid-examples.
אבטחה ופרטיות
מחברי המפרט תכננו והטמיעו את WebHID API על סמך העקרונות המרכזיים שמוגדרים במאמר שליטה בגישה לתכונות עוצמתיות של פלטפורמת האינטרנט, כולל שליטה של המשתמש, שקיפות וארגונומיה. היכולת להשתמש ב-API הזה מוגבלת בעיקר על ידי מודל הרשאות שמעניק גישה רק למכשיר HID אחד בכל פעם. בתגובה להנחיה של משתמש, המשתמש צריך לבצע פעולות אקטיביות כדי לבחור מכשיר HID מסוים.
כדי להבין את ההשפעות על האבטחה, כדאי לעיין בקטע שיקולים בנושא אבטחה ופרטיות במפרט WebHID.
בנוסף, Chrome בודק את השימוש בכל אוסף ברמה העליונה. אם השימוש באוסף ברמה העליונה מוגן (למשל, מקלדת כללית, עכבר), אתר לא יוכל לשלוח ולקבל דוחות שמוגדרים באוסף הזה. הרשימה המלאה של השימושים המוגנים זמינה לציבור.
שימו לב שמכשירי HID רגישים מבחינת אבטחה (כמו מכשירי FIDO HID שמשמשים לאימות חזק יותר) נחסמים גם ב-Chrome. מעיינים בקבצים USB blocklist ו-HID blocklist.
משוב
צוות Chrome ישמח לשמוע את דעתכם על WebHID API.
מהו עיצוב ה-API?
האם יש משהו ב-API שלא פועל כמצופה? או שיש שיטות או מאפיינים חסרים שצריך להטמיע כדי ליישם את הרעיון?
אפשר לפתוח בעיה במפרט במאגר WebHID API GitHub או להוסיף את המחשבות שלכם לבעיה קיימת.
דיווח על בעיה בהטמעה
מצאתם באג בהטמעה של Chrome? או שההטמעה שונה מהמפרט?
כאן מוסבר איך לדווח על באגים ב-WebHID. חשוב לכלול כמה שיותר פרטים, לספק הוראות לשחזור הבאג ולהגדיר את המרכיבים לערך Blink>HID.
קישורים שימושיים
- מפרט
- באג במעקב
- ערך ב-ChromeStatus.com
- רכיב Blink:
Blink>HID
תודות
תודה למאט ריינולדס ולג'ו מדלי על הביקורות שלהם.