设备访问
🌐 Device Access
像基于 Chromium 的浏览器一样,Electron 通过网络 API 提供对设备硬件的访问。在大多数情况下,这些 API 的工作方式与浏览器中相同,但有一些差异需要注意。Electron 与浏览器的主要区别在于设备访问请求时的处理方式。在浏览器中,用户会看到一个弹出窗口,可以授权访问某个特定设备。而在 Electron 中,提供了 API,开发者可以使用这些 API 自动选择设备,或者通过开发者创建的界面提示用户选择设备。
🌐 Like Chromium based browsers, Electron provides access to device hardware through web APIs. For the most part these APIs work like they do in a browser, but there are some differences that need to be taken into account. The primary difference between Electron and browsers is what happens when device access is requested. In a browser, users are presented with a popup where they can grant access to an individual device. In Electron APIs are provided which can be used by a developer to either automatically pick a device or prompt users to pick a device via a developer created interface.
网络蓝牙 API
🌐 Web Bluetooth API
Web Bluetooth API 可用于与蓝牙设备通信。为了在 Electron 中使用此 API,开发者需要处理与设备请求相关的 select-bluetooth-device 事件在 webContents 上。
🌐 The Web Bluetooth API can be used to communicate
with bluetooth devices. In order to use this API in Electron, developers will
need to handle the select-bluetooth-device event on the webContents
associated with the device request.
此外,ses.setBluetoothPairingHandler(handler) 可用于在 Windows 或 Linux 上处理与蓝牙设备的配对,当需要额外验证(例如 PIN 码)时也适用。
🌐 Additionally, ses.setBluetoothPairingHandler(handler)
can be used to handle pairing to bluetooth devices on Windows or Linux when
additional validation such as a pin is needed.
示例
🌐 Example
这个示例展示了一个 Electron 应用,当点击 Test Bluetooth 按钮时,会自动选择第一个可用的蓝牙设备。
🌐 This example demonstrates an Electron application that automatically selects
the first available bluetooth device when the Test Bluetooth button is
clicked.
- main.js
- preload.js
- index.html
- renderer.js
const { app, BrowserWindow, ipcMain } = require('electron/main')
const path = require('node:path')
let bluetoothPinCallback
let selectBluetoothCallback
function createWindow () {
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
mainWindow.webContents.on('select-bluetooth-device', (event, deviceList, callback) => {
event.preventDefault()
selectBluetoothCallback = callback
const result = deviceList.find((device) => {
return device.deviceName === 'test'
})
if (result) {
callback(result.deviceId)
} else {
// The device wasn't found so we need to either wait longer (eg until the
// device is turned on) or until the user cancels the request
}
})
ipcMain.on('cancel-bluetooth-request', (event) => {
selectBluetoothCallback('')
})
// Listen for a message from the renderer to get the response for the Bluetooth pairing.
ipcMain.on('bluetooth-pairing-response', (event, response) => {
bluetoothPinCallback(response)
})
mainWindow.webContents.session.setBluetoothPairingHandler((details, callback) => {
bluetoothPinCallback = callback
// Send a message to the renderer to prompt the user to confirm the pairing.
mainWindow.webContents.send('bluetooth-pairing-request', details)
})
mainWindow.loadFile('index.html')
}
app.whenReady().then(() => {
createWindow()
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
const { contextBridge, ipcRenderer } = require('electron/renderer')
contextBridge.exposeInMainWorld('electronAPI', {
cancelBluetoothRequest: () => ipcRenderer.send('cancel-bluetooth-request'),
bluetoothPairingRequest: (callback) => ipcRenderer.on('bluetooth-pairing-request', () => callback()),
bluetoothPairingResponse: (response) => ipcRenderer.send('bluetooth-pairing-response', response)
})
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<title>Web Bluetooth API</title>
</head>
<body>
<h1>Web Bluetooth API</h1>
<button id="clickme">Test Bluetooth</button>
<button id="cancel">Cancel Bluetooth Request</button>
<p>Currently selected bluetooth device: <strong id="device-name"></strong></p>
<script src="./renderer.js"></script>
</body>
</html>
async function testIt () {
const device = await navigator.bluetooth.requestDevice({
acceptAllDevices: true
})
document.getElementById('device-name').innerHTML = device.name || `ID: ${device.id}`
}
document.getElementById('clickme').addEventListener('click', testIt)
function cancelRequest () {
window.electronAPI.cancelBluetoothRequest()
}
document.getElementById('cancel').addEventListener('click', cancelRequest)
window.electronAPI.bluetoothPairingRequest((event, details) => {
const response = {}
switch (details.pairingKind) {
case 'confirm': {
response.confirmed = window.confirm(`Do you want to connect to device ${details.deviceId}?`)
break
}
case 'confirmPin': {
response.confirmed = window.confirm(`Does the pin ${details.pin} match the pin displayed on device ${details.deviceId}?`)
break
}
case 'providePin': {
const pin = window.prompt(`Please provide a pin for ${details.deviceId}.`)
if (pin) {
response.pin = pin
response.confirmed = true
} else {
response.confirmed = false
}
}
}
window.electronAPI.bluetoothPairingResponse(response)
})
WebHID API
WebHID API 可用于访问诸如键盘和游戏句柄等 HID 设备。Electron 提供了若干用于操作 WebHID API 的接口:
🌐 The WebHID API can be used to access HID devices such as keyboards and gamepads. Electron provides several APIs for working with the WebHID API:
- 可以使用会话上的
select-hid-device事件 在调用navigator.hid.requestDevice时选择 HID 设备。此外,会话上的hid-device-added和hid-device-removed事件可用于在处理select-hid-device事件时处理设备的插入或拔出。注意: 这些事件仅在调用select-hid-device的回调之前触发。它们并不用于作为通用 HID 设备监听器。 [ses.setDevicePermissionHandler(handler)](../api/session.md#sessetdevicepermissionhandlerhandler)可以用于在不先通过navigator.hid.requestDevice获取设备权限的情况下,为设备提供默认权限。此外,Electron 的默认行为是将授予的设备权限存储在相应的 WebContents 生命周期内。如果需要长期存储,开发者可以存储已授予的设备权限(例如在处理select-hid-device事件时),然后使用setDevicePermissionHandler从该存储中读取。ses.setPermissionCheckHandler(handler)可用于禁用特定来源的 HID 访问。
黑名单
🌐 Blocklist
默认情况下,Electron 使用 Chromium 使用的相同阻止列表。如果你希望覆盖此行为,可以通过设置 disable-hid-blocklist 标志来实现:
🌐 By default Electron employs the same blocklist
used by Chromium. If you wish to override this behavior, you can do so by
setting the disable-hid-blocklist flag:
app.commandLine.appendSwitch('disable-hid-blocklist')
示例
🌐 Example
此示例演示了一个 Electron 应用,它会通过 ses.setDevicePermissionHandler(handler) 自动选择 HID 设备,并且在点击 Test WebHID 按钮时,通过会话中的 select-hid-device 事件 进行选择。
🌐 This example demonstrates an Electron application that automatically selects
HID devices through ses.setDevicePermissionHandler(handler)
and through select-hid-device event on the Session
when the Test WebHID button is clicked.
- main.js
- index.html
- renderer.js
const { app, BrowserWindow } = require('electron/main')
function createWindow () {
const mainWindow = new BrowserWindow({
width: 800,
height: 600
})
mainWindow.webContents.session.on('select-hid-device', (event, details, callback) => {
// Add events to handle devices being added or removed before the callback on
// `select-hid-device` is called.
mainWindow.webContents.session.on('hid-device-added', (event, device) => {
console.log('hid-device-added FIRED WITH', device)
// Optionally update details.deviceList
})
mainWindow.webContents.session.on('hid-device-removed', (event, device) => {
console.log('hid-device-removed FIRED WITH', device)
// Optionally update details.deviceList
})
event.preventDefault()
if (details.deviceList && details.deviceList.length > 0) {
callback(details.deviceList[0].deviceId)
}
})
mainWindow.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin, details) => {
if (permission === 'hid' && details.securityOrigin === 'file:///') {
return true
}
})
mainWindow.webContents.session.setDevicePermissionHandler((details) => {
if (details.deviceType === 'hid' && details.origin === 'file://') {
return true
}
})
mainWindow.loadFile('index.html')
}
app.whenReady().then(() => {
createWindow()
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<title>WebHID API</title>
</head>
<body>
<h1>WebHID API</h1>
<button id="clickme">Test WebHID</button>
<h3>HID devices automatically granted access via <i>setDevicePermissionHandler</i></h3>
<div id="granted-devices"></div>
<h3>HID devices automatically granted access via <i>select-hid-device</i></h3>
<div id="granted-devices2"></div>
<script src="./renderer.js"></script>
</body>
</html>
function formatDevices (devices) {
return devices.map(device => device.productName).join('<hr>')
}
async function testIt () {
document.getElementById('granted-devices').innerHTML = formatDevices(await navigator.hid.getDevices())
document.getElementById('granted-devices2').innerHTML = formatDevices(await navigator.hid.requestDevice({ filters: [] }))
}
document.getElementById('clickme').addEventListener('click', testIt)
网络串行 API
🌐 Web Serial API
Web Serial API 可用于访问通过串口、USB 或蓝牙连接的串行设备。为了在 Electron 中使用此 API,开发者需要处理与串口请求相关的会话上的 select-serial-port 事件。
🌐 The Web Serial API can be used to access serial
devices that are connected via serial port, USB, or Bluetooth. In order to use
this API in Electron, developers will need to handle the
select-serial-port event on the Session
associated with the serial port request.
还有几个额外的 API 可用于使用 Web Serial API:
🌐 There are several additional APIs for working with the Web Serial API:
- 在处理
select-serial-port事件时,可以使用 Session 上的serial-port-added和serial-port-removed事件来处理设备的插入或拔出。**注意:**这些事件只会触发到select-serial-port的回调被调用为止。它们并不是作为通用串口监听器使用的。 ses.setDevicePermissionHandler(handler)可以用来为设备提供默认权限,而无需先通过navigator.serial.requestPort请求设备权限。此外,Electron 的默认行为是在相应的 WebContents 生命周期内存储已授予的设备权限。如果需要更长期的存储,开发者可以存储已授予的设备权限(例如在处理select-serial-port事件时),然后使用setDevicePermissionHandler从该存储中读取。ses.setPermissionCheckHandler(handler)可用于禁用特定来源的串行访问。
黑名单
🌐 Blocklist
默认情况下,Electron 使用 Chromium 使用的相同阻止列表。如果你希望覆盖此行为,可以通过设置 disable-serial-blocklist 标志来实现:
🌐 By default Electron employs the same blocklist
used by Chromium. If you wish to override this behavior, you can do so by
setting the disable-serial-blocklist flag:
app.commandLine.appendSwitch('disable-serial-blocklist')
示例
🌐 Example
此示例演示了一个 Electron 应用,它可以通过 ses.setDevicePermissionHandler(handler) 自动选择串行设备,以及在点击 Test Web Serial 按钮时,通过 Session 上的 select-serial-port 事件 选择第一个可用的 Arduino Uno 串行设备(如果已连接)。
🌐 This example demonstrates an Electron application that automatically selects
serial devices through ses.setDevicePermissionHandler(handler)
as well as demonstrating selecting the first available Arduino Uno serial device (if connected) through
select-serial-port event on the Session
when the Test Web Serial button is clicked.
- main.js
- index.html
- renderer.js
const { app, BrowserWindow } = require('electron/main')
function createWindow () {
const mainWindow = new BrowserWindow({
width: 800,
height: 600
})
mainWindow.webContents.session.on('select-serial-port', (event, portList, webContents, callback) => {
// Add listeners to handle ports being added or removed before the callback for `select-serial-port`
// is called.
mainWindow.webContents.session.on('serial-port-added', (event, port) => {
console.log('serial-port-added FIRED WITH', port)
// Optionally update portList to add the new port
})
mainWindow.webContents.session.on('serial-port-removed', (event, port) => {
console.log('serial-port-removed FIRED WITH', port)
// Optionally update portList to remove the port
})
event.preventDefault()
if (portList && portList.length > 0) {
callback(portList[0].portId)
} else {
// eslint-disable-next-line n/no-callback-literal
callback('') // Could not find any matching devices
}
})
mainWindow.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin, details) => {
if (permission === 'serial' && details.securityOrigin === 'file:///') {
return true
}
return false
})
mainWindow.webContents.session.setDevicePermissionHandler((details) => {
if (details.deviceType === 'serial' && details.origin === 'file://') {
return true
}
return false
})
mainWindow.loadFile('index.html')
mainWindow.webContents.openDevTools()
}
app.whenReady().then(() => {
createWindow()
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<title>Web Serial API</title>
<body>
<h1>Web Serial API</h1>
<button id="clickme">Test Web Serial API</button>
<p>Matching Arduino Uno device: <strong id="device-name""></strong></p>
<script src="./renderer.js"></script>
</body>
</html>
async function testIt () {
const filters = [
{ usbVendorId: 0x2341, usbProductId: 0x0043 },
{ usbVendorId: 0x2341, usbProductId: 0x0001 }
]
try {
const port = await navigator.serial.requestPort({ filters })
const portInfo = port.getInfo()
document.getElementById('device-name').innerHTML = `vendorId: ${portInfo.usbVendorId} | productId: ${portInfo.usbProductId} `
} catch (ex) {
if (ex.name === 'NotFoundError') {
document.getElementById('device-name').innerHTML = 'Device NOT found'
} else {
document.getElementById('device-name').innerHTML = ex
}
}
}
document.getElementById('clickme').addEventListener('click', testIt)
网络 USB API
🌐 WebUSB API
WebUSB API 可用于访问 USB 设备。Electron 提供了多个用于操作 WebUSB API 的接口:
🌐 The WebUSB API can be used to access USB devices. Electron provides several APIs for working with the WebUSB API:
- 在会话中的
select-usb-device事件 可以在调用navigator.usb.requestDevice时用于选择 USB 设备。此外,会话中的usb-device-added和usb-device-removed事件可以在处理select-usb-device事件时处理设备的插入或拔出。注意: 这两个事件仅在调用select-usb-device的回调之前触发。它们并不打算用作通用的 USB 设备监听器。 usb-device-revoked会话事件 可用于在对 USB 设备调用 device.forget() 时做出响应。ses.setDevicePermissionHandler(handler)可以用来为设备提供默认权限,而无需先通过navigator.usb.requestDevice请求设备权限。此外,Electron 的默认行为是在相应的 WebContents 生命周期内存储已授予的设备权限。如果需要更长期的存储,开发者可以存储已授予的设备权限(例如在处理select-usb-device事件时),然后通过setDevicePermissionHandler从该存储中读取。ses.setPermissionCheckHandler(handler)可用于禁用特定来源的 USB 访问。[ses.setUSBProtectedClassesHandler](../api/session.md#sessetusbprotectedclasseshandlerhandler)可用于允许使用默认情况下不可用的受保护的 USB 类。
黑名单
🌐 Blocklist
默认情况下,Electron 使用 Chromium 使用的相同阻止列表。如果你希望覆盖此行为,可以通过设置 disable-usb-blocklist 标志来实现:
🌐 By default Electron employs the same blocklist
used by Chromium. If you wish to override this behavior, you can do so by
setting the disable-usb-blocklist flag:
app.commandLine.appendSwitch('disable-usb-blocklist')
示例
🌐 Example
这个示例演示了一个 Electron 应用,它可以在点击 Test WebUSB 按钮时,自动选择 USB 设备(如果已连接),通过 ses.setDevicePermissionHandler(handler) 和会话上的 select-usb-device 事件 来实现。
🌐 This example demonstrates an Electron application that automatically selects
USB devices (if they are attached) through ses.setDevicePermissionHandler(handler)
and through select-usb-device event on the Session
when the Test WebUSB button is clicked.
- main.js
- index.html
- renderer.js
const { app, BrowserWindow } = require('electron/main')
function createWindow () {
const mainWindow = new BrowserWindow({
width: 800,
height: 600
})
let grantedDeviceThroughPermHandler
mainWindow.webContents.session.on('select-usb-device', (event, details, callback) => {
// Add events to handle devices being added or removed before the callback on
// `select-usb-device` is called.
mainWindow.webContents.session.on('usb-device-added', (event, device) => {
console.log('usb-device-added FIRED WITH', device)
// Optionally update details.deviceList
})
mainWindow.webContents.session.on('usb-device-removed', (event, device) => {
console.log('usb-device-removed FIRED WITH', device)
// Optionally update details.deviceList
})
event.preventDefault()
if (details.deviceList && details.deviceList.length > 0) {
const deviceToReturn = details.deviceList.find((device) => {
return !grantedDeviceThroughPermHandler || (device.deviceId !== grantedDeviceThroughPermHandler.deviceId)
})
if (deviceToReturn) {
callback(deviceToReturn.deviceId)
} else {
callback()
}
}
})
mainWindow.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin, details) => {
if (permission === 'usb' && details.securityOrigin === 'file:///') {
return true
}
})
mainWindow.webContents.session.setDevicePermissionHandler((details) => {
if (details.deviceType === 'usb' && details.origin === 'file://') {
if (!grantedDeviceThroughPermHandler) {
grantedDeviceThroughPermHandler = details.device
return true
} else {
return false
}
}
})
mainWindow.webContents.session.setUSBProtectedClassesHandler((details) => {
return details.protectedClasses.filter((usbClass) => {
// Exclude classes except for audio classes
return usbClass.indexOf('audio') === -1
})
})
mainWindow.loadFile('index.html')
}
app.whenReady().then(() => {
createWindow()
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<title>WebUSB API</title>
</head>
<body>
<h1>WebUSB API</h1>
<button id="clickme">Test WebUSB</button>
<h3>USB devices automatically granted access via <i>setDevicePermissionHandler</i></h3>
<div id="granted-devices"></div>
<h3>USB devices automatically granted access via <i>select-usb-device</i></h3>
<div id="granted-devices2"></div>
<script src="./renderer.js"></script>
</body>
</html>
function getDeviceDetails (device) {
return device.productName || `Unknown device ${device.deviceId}`
}
async function testIt () {
const noDevicesFoundMsg = 'No devices found'
const grantedDevices = await navigator.usb.getDevices()
let grantedDeviceList = ''
if (grantedDevices.length > 0) {
for (const device of grantedDevices) {
grantedDeviceList += `<hr>${getDeviceDetails(device)}</hr>`
}
} else {
grantedDeviceList = noDevicesFoundMsg
}
document.getElementById('granted-devices').innerHTML = grantedDeviceList
grantedDeviceList = ''
try {
const grantedDevice = await navigator.usb.requestDevice({
filters: []
})
grantedDeviceList += `<hr>${getDeviceDetails(grantedDevice)}</hr>`
} catch (ex) {
if (ex.name === 'NotFoundError') {
grantedDeviceList = noDevicesFoundMsg
}
}
document.getElementById('granted-devices2').innerHTML = grantedDeviceList
}
document.getElementById('clickme').addEventListener('click', testIt)