API WebHID cho phép các trang web truy cập vào bàn phím phụ thay thế và tay cầm chơi game đặc biệt.
Xuất bản: Ngày 15 tháng 9 năm 2020
Có nhiều thiết bị có giao diện dành cho người dùng (HID), chẳng hạn như bàn phím thay thế hoặc tay cầm chơi game lạ, quá mới, quá cũ hoặc quá hiếm để trình điều khiển thiết bị của hệ thống có thể truy cập. WebHID API giải quyết vấn đề này bằng cách cung cấp một cách để triển khai logic dành riêng cho thiết bị trong JavaScript.
Các trường hợp sử dụng được đề xuất
Thiết bị HID nhận dữ liệu đầu vào hoặc cung cấp dữ liệu đầu ra cho người dùng. Ví dụ về thiết bị bao gồm bàn phím, thiết bị trỏ (chuột, màn hình cảm ứng, v.v.) và tay cầm chơi game. Giao thức HID cho phép truy cập vào các thiết bị này trên máy tính để bàn bằng cách sử dụng trình điều khiển hệ điều hành. Nền tảng web hỗ trợ các thiết bị HID bằng cách dựa vào những trình điều khiển này.
Việc không truy cập được vào các thiết bị HID không phổ biến gây ra nhiều bất tiện, đặc biệt là khi nói đến các bàn phím phụ thay thế (chẳng hạn như Elgato Stream Deck, tai nghe Jabra, X-keys) và khả năng hỗ trợ tay cầm chơi game đặc biệt. Tay cầm chơi game được thiết kế cho máy tính thường dùng HID cho đầu vào (nút, cần điều khiển, cò súng) và đầu ra (đèn LED, chế độ rung) của tay cầm chơi game.
Rất tiếc, các đầu vào và đầu ra của tay cầm chơi game không được chuẩn hoá đúng cách và trình duyệt web thường yêu cầu logic tuỳ chỉnh cho các thiết bị cụ thể. Điều này không bền vững và dẫn đến việc hỗ trợ kém cho các thiết bị cũ và không phổ biến. Điều này cũng khiến trình duyệt phụ thuộc vào các điểm bất thường trong hành vi của các thiết bị cụ thể.
Thuật ngữ
Thiết bị có giao diện dành cho người dùng (HID) có thể nhận dữ liệu đầu vào hoặc cung cấp dữ liệu đầu ra cho người dùng. Có một giao thức HID, một tiêu chuẩn để giao tiếp hai chiều giữa máy chủ lưu trữ và thiết bị được thiết kế để đơn giản hoá quy trình cài đặt.
HID bao gồm 2 khái niệm cơ bản: báo cáo và bộ mô tả báo cáo. Báo cáo là dữ liệu được trao đổi giữa một thiết bị và một ứng dụng phần mềm. Nội dung mô tả báo cáo mô tả định dạng và ý nghĩa của dữ liệu mà thiết bị hỗ trợ.
Các ứng dụng và thiết bị HID trao đổi dữ liệu nhị phân thông qua 3 loại báo cáo:
| Loại báo cáo | Mô tả |
|---|---|
| Báo cáo đầu vào | Dữ liệu được gửi từ thiết bị đến ứng dụng (ví dụ: người dùng nhấn một nút). |
| Báo cáo đầu ra | Dữ liệu được gửi từ ứng dụng đến thiết bị (ví dụ: yêu cầu bật đèn nền bàn phím). |
| Báo cáo về tính năng | Dữ liệu có thể được gửi theo cả hai hướng. Định dạng này dành riêng cho từng thiết bị. |
Một bộ mô tả báo cáo mô tả định dạng nhị phân của các báo cáo mà thiết bị hỗ trợ. Cấu trúc của báo cáo này có tính phân cấp và có thể nhóm các báo cáo lại với nhau thành các bộ sưu tập riêng biệt trong bộ sưu tập cấp cao nhất. Định dạng của chỉ số mô tả được xác định theo quy cách HID.
Mục đích sử dụng HID là một giá trị số đề cập đến một đầu vào hoặc đầu ra tiêu chuẩn. Giá trị sử dụng cho phép một thiết bị mô tả mục đích sử dụng dự kiến của thiết bị và mục đích của từng trường trong báo cáo. Ví dụ: một nút được xác định cho nút bên trái của chuột. Mức sử dụng cũng được sắp xếp thành các trang sử dụng, cho biết danh mục cấp cao của thiết bị hoặc báo cáo.
Sử dụng WebHID API
Để kiểm tra xem API WebHID có được hỗ trợ hay không, hãy sử dụng:
if ("hid" in navigator) {
// The WebHID API is supported.
}
Mở một kết nối HID
API WebHID được thiết kế không đồng bộ để ngăn chặn giao diện người dùng trang web chặn khi chờ dữ liệu đầu vào. Điều này rất quan trọng vì dữ liệu HID có thể được nhận bất cứ lúc nào, đòi hỏi phải có cách để lắng nghe dữ liệu đó.
Để mở một kết nối HID, trước tiên, hãy truy cập vào một đối tượng HIDDevice. Để làm việc này, bạn có thể nhắc người dùng chọn một thiết bị bằng cách gọi navigator.hid.requestDevice() hoặc chọn một thiết bị trong navigator.hid.getDevices(). Thao tác này sẽ trả về danh sách các thiết bị mà trang web đã được cấp quyền truy cập trước đó.
Hàm navigator.hid.requestDevice() nhận một đối tượng bắt buộc xác định các bộ lọc. Những giá trị này được dùng để so khớp mọi thiết bị được kết nối với giá trị nhận dạng nhà cung cấp USB (vendorId), giá trị nhận dạng sản phẩm USB (productId), giá trị trang sử dụng (usagePage) và giá trị sử dụng (usage). Bạn có thể lấy những giá trị này từ Kho lưu trữ mã nhận dạng USB và tài liệu bảng sử dụng HID.
Nhiều đối tượng HIDDevice do hàm này trả về đại diện cho nhiều giao diện HID trên cùng một thiết bị thực.
// 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();
Bạn cũng có thể dùng khoá exclusionFilters (không bắt buộc) trong navigator.hid.requestDevice() để loại trừ một số thiết bị bị lỗi khỏi trình chọn trình duyệt.
// 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 }],
});
Đối tượng HIDDevice chứa mã nhận dạng sản phẩm và nhà cung cấp USB để nhận dạng thiết bị. Thuộc tính collections của nó được khởi tạo bằng một nội dung mô tả phân cấp về các định dạng báo cáo của thiết bị.
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
}
Theo mặc định, các thiết bị HIDDevice sẽ được trả về ở trạng thái "đóng" và phải được mở bằng cách gọi open() trước khi có thể gửi hoặc nhận dữ liệu.
// Wait for the HID connection to open before sending/receiving data.
await device.open();
Nhận báo cáo đầu vào

Sau khi thiết lập kết nối HID, bạn có thể xử lý các báo cáo đầu vào đến bằng cách theo dõi các sự kiện "inputreport" từ thiết bị. Những sự kiện đó chứa dữ liệu HID dưới dạng đối tượng DataView (data), thiết bị HID mà đối tượng đó thuộc về (device) và mã báo cáo 8 bit được liên kết với báo cáo đầu vào (reportId).
Tiếp tục với ví dụ trước, mã này giúp bạn phát hiện nút mà người dùng đã nhấn trên thiết bị Joy-Con Right để bạn có thể thử tại nhà.
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]}.`);
});
Hãy tham khảo bản minh hoạ trên CodePen.
Gửi báo cáo đầu ra
Để gửi báo cáo đầu ra đến một thiết bị HID, hãy truyền mã nhận dạng báo cáo 8 bit được liên kết với báo cáo đầu ra (reportId) và các byte dưới dạng BufferSource (data) đến device.sendReport(). Lệnh hứa hẹn được trả về sẽ giải quyết sau khi báo cáo được gửi. Nếu thiết bị HID không sử dụng mã báo cáo, hãy đặt reportId thành 0.
Ví dụ tiếp theo áp dụng cho thiết bị Joy-Con và cho bạn biết cách làm cho thiết bị này rung bằng báo cáo đầu ra.
// 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));
Hãy tham khảo bản minh hoạ trên CodePen.
Gửi và nhận báo cáo về tính năng

Báo cáo về tính năng là loại báo cáo dữ liệu HID duy nhất có thể di chuyển theo cả hai hướng. Chúng cho phép các thiết bị và ứng dụng HID trao đổi dữ liệu HID không được chuẩn hoá. Không giống như báo cáo đầu vào và đầu ra, ứng dụng không nhận hoặc gửi báo cáo về tính năng một cách thường xuyên.
Để gửi báo cáo về tính năng đến một thiết bị HID, hãy truyền mã nhận dạng báo cáo 8 bit được liên kết với báo cáo về tính năng (reportId) và các byte dưới dạng BufferSource (data) đến device.sendFeatureReport(). Lời hứa được trả về sẽ được thực hiện sau khi báo cáo đã được gửi. Nếu thiết bị HID không sử dụng mã báo cáo, hãy đặt reportId thành 0.
Ví dụ này minh hoạ việc sử dụng báo cáo về tính năng bằng cách cho bạn biết cách yêu cầu một thiết bị đèn nền bàn phím Apple, mở thiết bị đó và làm cho thiết bị nhấp nháy.
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);
}
Hãy tham khảo bản minh hoạ trên CodePen.
Để nhận báo cáo về tính năng từ một thiết bị HID, hãy truyền mã nhận dạng báo cáo 8 bit được liên kết với báo cáo về tính năng (reportId) đến device.receiveFeatureReport(). Lời hứa được trả về sẽ phân giải bằng một đối tượng DataView chứa nội dung của báo cáo tính năng. Nếu thiết bị HID không sử dụng mã nhận dạng báo cáo, hãy đặt reportId thành 0.
// Request feature report.
const dataView = await device.receiveFeatureReport(/* reportId= */ 1);
// Read feature report contents with dataView.getInt8(), getUint8(), etc...
Nghe sự kiện kết nối và ngắt kết nối
Khi được cấp quyền truy cập vào một thiết bị HID, trang web có thể chủ động nhận các sự kiện kết nối và ngắt kết nối bằng cách theo dõi các sự kiện "connect" và "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.
});
Thu hồi quyền truy cập vào thiết bị HID
Trang web có thể dọn dẹp các quyền truy cập vào thiết bị HID mà trang web không còn muốn giữ lại bằng cách gọi forget() trên phiên bản HIDDevice. Ví dụ: đối với một ứng dụng web giáo dục được dùng trên máy tính dùng chung với nhiều thiết bị, một số lượng lớn các quyền do người dùng tạo được tích luỹ sẽ tạo ra trải nghiệm người dùng kém.
Việc gọi forget() trên một phiên bản HIDDevice duy nhất sẽ thu hồi quyền truy cập vào tất cả các giao diện HID trên cùng một thiết bị thực.
// Voluntarily revoke access to this HID device.
await device.forget();
Vì forget() có trong Chrome 100 trở lên, hãy kiểm tra xem tính năng này có được hỗ trợ hay không bằng cách làm như sau:
if ("hid" in navigator && "forget" in HIDDevice.prototype) {
// forget() is supported.
}
Mẹo cho nhà phát triển

Gỡ lỗi HID trong Chrome bằng trang nội bộ about://device-log. Tại đây, bạn có thể xem tất cả các sự kiện liên quan đến thiết bị HID và USB ở cùng một nơi.
Hãy xem trình khám phá HID để kết xuất thông tin thiết bị HID ở định dạng mà người dùng có thể đọc được. Nó ánh xạ từ các giá trị sử dụng đến tên cho từng mục đích sử dụng HID.
Trên hầu hết các hệ thống Linux, theo mặc định, các thiết bị HID được liên kết với các quyền chỉ đọc. Để cho phép Chrome mở một thiết bị HID, bạn cần thêm một quy tắc udev mới. Tạo một tệp tại /etc/udev/rules.d/50-yourdevicename.rules có nội dung sau:
KERNEL=="hidraw*", ATTRS{idVendor}=="[yourdevicevendor]", MODE="0664", GROUP="plugdev"
Trong mã này, [yourdevicevendor] là 057e, chẳng hạn như nếu thiết bị của bạn là Nintendo Switch Joy-Con. Bạn có thể thêm ATTRS{idProduct} để có một quy tắc cụ thể hơn. Đảm bảo rằng user là thành viên của nhóm plugdev. Sau đó, bạn chỉ cần kết nối lại thiết bị.
Bản minh hoạ
Một số bản minh hoạ WebHID được liệt kê tại web.dev/hid-examples.
Bảo mật và quyền riêng tư
Các tác giả của thông số kỹ thuật đã thiết kế và triển khai WebHID API bằng các nguyên tắc cốt lõi được xác định trong Kiểm soát quyền truy cập vào các tính năng mạnh mẽ của nền tảng web, bao gồm quyền kiểm soát của người dùng, tính minh bạch và tính công thái học. Khả năng sử dụng API này chủ yếu được kiểm soát bằng một mô hình quyền chỉ cấp quyền truy cập vào một thiết bị HID duy nhất tại một thời điểm. Để phản hồi lời nhắc của người dùng, người dùng phải thực hiện các bước chủ động để chọn một thiết bị HID cụ thể.
Để hiểu rõ những điểm đánh đổi về bảo mật, hãy xem phần Cân nhắc về bảo mật và quyền riêng tư trong quy cách WebHID.
Ngoài ra, Chrome sẽ kiểm tra việc sử dụng từng tập hợp cấp cao nhất. Nếu một tập hợp cấp cao nhất có mức sử dụng được bảo vệ (ví dụ: bàn phím, chuột chung), thì một trang web sẽ không thể gửi và nhận bất kỳ báo cáo nào được xác định trong tập hợp đó. Danh sách đầy đủ các trường hợp sử dụng được bảo vệ là công khai.
Xin lưu ý rằng các thiết bị HID nhạy cảm về bảo mật (chẳng hạn như thiết bị HID FIDO dùng để xác thực mạnh hơn) cũng bị chặn trong Chrome. Xem các tệp danh sách chặn USB và danh sách chặn HID.
Phản hồi
Nhóm Chrome rất mong nhận được ý kiến và trải nghiệm của bạn về WebHID API.
Cho chúng tôi biết về thiết kế API
Có vấn đề gì về API không hoạt động như mong đợi không? Hay có phương thức hoặc thuộc tính nào bị thiếu mà bạn cần triển khai ý tưởng của mình không?
Báo cáo vấn đề về thông số kỹ thuật trên kho lưu trữ WebHID API GitHub hoặc thêm ý kiến của bạn vào một vấn đề hiện có.
Báo cáo vấn đề về việc triển khai
Bạn có phát hiện thấy lỗi trong quá trình triển khai của Chrome không? Hay việc triển khai có khác với quy cách không?
Hãy xem bài viết Cách báo cáo lỗi WebHID. Hãy nhớ cung cấp càng nhiều thông tin chi tiết càng tốt, hướng dẫn cách tái tạo lỗi và đặt Thành phần thành Blink>HID.
Đường liên kết hữu ích
- Quy cách kỹ thuật
- Lỗi theo dõi
- Mục nhập trên ChromeStatus.com
- Thành phần Blink:
Blink>HID
Lời cảm ơn
Cảm ơn Matt Reynolds và Joe Medley đã đánh giá.