# WebHID API：用 navigator.hid 访问设备

> 用 navigator.hid.requestDevice() 配对 HID 设备，处理 connect/disconnect 事件，以及安全上下文与用户激活的要求。

**一句话：** WebHID 让网页可以通过权限提示请求访问一个人机接口设备
（HID，Human Interface Device）——"a type of device that takes input from or provides output
to humans"（一种接受人类输入或向人类提供输出的设备），它使用最初为 USB 设计、此后也被实现在
其他传输方式（包括蓝牙）之上的 HID 协议。MDN 将其标记为**实验性（experimental）**且
**非 Baseline**，"because it does not work in some of the most widely-used browsers"（因为它
在一些广泛使用的浏览器中无法工作）。

## 安全上下文与用户激活

`navigator.hid` 仅在安全上下文中暴露：规范中为其添加该属性的 `Navigator` 与
`WorkerNavigator` 局部接口都标注了 `[SecureContext]`。调用 `requestDevice()` 还需要
**用户激活（transient activation）**——如果"the relevant global object of this does not
have transient activation"（this 的相关全局对象没有用户激活），规范会以 `SecurityError`
`DOMException` 拒绝返回的 Promise；如果调用方不是 `window` 上下文，则以 `NotSupportedError`
拒绝。

## 请求并复用设备

```js
const devices = await navigator.hid.requestDevice({ filters: [] });
```

`requestDevice()` 会显示一个列出候选设备的权限提示（空的 `filters` 数组匹配所有设备）；用户
选择一个设备并点击 Connect。设备一旦被授权，`getDevices()` 就可以再次获取它，而无需再次弹出
提示：

```js
const devices = await navigator.hid.getDevices();
devices.forEach((device) => {
  console.log(`HID: ${device.productName}`);
});
```

这两个方法的访问还受到一个名为 `"hid"` 的 Permissions Policy 特性的限制：如果调用文档不被
"allowed to use"（允许使用）该特性，规范会以 `SecurityError` 拒绝。

## connect 与 disconnect 事件

```js
navigator.hid.addEventListener("disconnect", (event) => {
  console.log(`HID disconnected: ${event.device.productName}`);
});
```

当一个先前已获授权的设备被接入或移除时，`HID` 接口会触发相应的 `connect` 与 `disconnect`
事件，每个事件都携带一个带有 `device` 属性的 `HIDConnectionEvent`。

## 打开设备并交换报告

`HIDDevice.open()` 在设备状态不是 `"closed"` 时会以 `InvalidStateError` 拒绝；否则它会请求
操作系统打开该设备，成功打开后 resolve（失败则以 `NetworkError` 拒绝）。`close()` 会以
`AbortError` 拒绝所有挂起的报告 Promise，关闭操作系统句柄，并将设备状态重置为 `"closed"`。
`sendReport()`、`sendFeatureReport()` 与 `receiveFeatureReport()` 在设备状态不是
`"opened"` 时都会以 `InvalidStateError` 拒绝；当报告 ID 与设备接口是否使用报告 ID 不匹配时，
会以 `TypeError` 拒绝。

## 被阻止的报告

规范定义了"blocked report"（被阻止的报告）这一概念：`sendReport()`、
`sendFeatureReport()` 与 `receiveFeatureReport()` 都会检查目标报告是否被阻止，如果是，则以
`NotAllowedError` 拒绝；被阻止的输入报告则根本不会触发 `inputreport` 事件。规范还分别描述了
基于设备与基于用途（usage）的阻止方式——例如按厂商/产品 ID 阻止，或通过检查设备集合上分配的
HID usage 值来阻止（规范以键盘作为基于用途阻止的示例）。

## 在 Worker 中的可用性

MDN 指出该特性在 Web Worker 中可用，"except for Shared Web Workers"（共享 Worker 除外）——
`WorkerNavigator.hid` 在专用 Worker 中暴露该特性，与主线程上的 `Navigator.hid` 相对应。

## 特性检测与回退

```js
async function connectHidDevice() {
  if (!("hid" in navigator)) {
    // 此环境不支持 WebHID —— 回退到手动配对界面
    // 或其他传输方式，而不是调用 navigator.hid。
    return null;
  }
  const [device] = await navigator.hid.requestDevice({ filters: [] });
  return device ?? null;
}
```

## 实用清单

- [ ] 在调用任何 WebHID 方法之前先检测 `navigator.hid`——根据 MDN，它是实验性且非
      Baseline 的特性，在部分浏览器中不存在是预期情况。
- [ ] 通过 HTTPS 提供页面；`navigator.hid` 仅在安全上下文中暴露。
- [ ] 只在具有用户激活（用户手势）的情况下调用 `requestDevice()`，否则会以
      `SecurityError` 拒绝。
- [ ] 在后续访问时调用 `getDevices()`，以复用先前已授权的设备而无需新的提示。
- [ ] 在调用 `sendReport()`、`sendFeatureReport()` 或 `receiveFeatureReport()` 之前，检查
      `HIDDevice.opened`/状态——在未打开的设备上调用都会以 `InvalidStateError` 拒绝。
- [ ] 不要仅因设备已打开就认为报告一定会成功：规范允许已打开设备上的"blocked report"
      （被阻止的报告）以 `NotAllowedError` 被拒绝。
- [ ] 记住一台物理设备可能对应多个 `HIDDevice` 对象。

## 相关参考

- [WebUSB API](/zh/reference/capabilities/web-usb/)
- [Web Bluetooth](/zh/reference/capabilities/web-bluetooth/)