设备访问

类似基于 Chromium 的浏览器一样, Electron 也提供了通过 web API 访问设备硬件的方法。 大部分接口就像在浏览器调用的 API 一样,但有一些差异需要考虑到。 Electron和浏览器之间的主要区别是请求访问设备时发生的情况。 在浏览器中,用户可以在弹出窗口中允许访问单独的设备。 在 Electron API中,提供了可供开发者自动选择设备或提示用户通过开发者创建的接口选择设备。

Web Bluetooth API

Web Bluetooth API 可以被用来连接蓝牙设备。 为了在 Electron 中使用此 API , 开发者将需要在 webContent 处理 select-bluetooth-device 事件 ,从而与设备请求相关联。

此外, ses.setBluetoothPairingHandler(handler) 方法可以被用来处理蓝牙设备配对, 这在 Windows 或 Linux 下进行额外的引脚校验时很有效.

示例

这个示例演示了一个 Electron 应用程序,当点击了 Test Bluetooth 按钮时,它会自动选择第一个可用的蓝牙设备。

docs/fiddles/features/web-bluetooth (27.0.1)Open in Fiddle

  • main.js
  • preload.js
  • index.html
  • renderer.js
  1. const { app, BrowserWindow, ipcMain } = require('electron')
  2. const path = require('node:path')
  3. let bluetoothPinCallback
  4. let selectBluetoothCallback
  5. function createWindow () {
  6. const mainWindow = new BrowserWindow({
  7. width: 800,
  8. height: 600,
  9. webPreferences: {
  10. preload: path.join(__dirname, 'preload.js')
  11. }
  12. })
  13. mainWindow.webContents.on('select-bluetooth-device', (event, deviceList, callback) => {
  14. event.preventDefault()
  15. selectBluetoothCallback = callback
  16. const result = deviceList.find((device) => {
  17. return device.deviceName === 'test'
  18. })
  19. if (result) {
  20. callback(result.deviceId)
  21. } else {
  22. // The device wasn't found so we need to either wait longer (eg until the
  23. // device is turned on) or until the user cancels the request
  24. }
  25. })
  26. ipcMain.on('cancel-bluetooth-request', (event) => {
  27. selectBluetoothCallback('')
  28. })
  29. // Listen for a message from the renderer to get the response for the Bluetooth pairing.
  30. ipcMain.on('bluetooth-pairing-response', (event, response) => {
  31. bluetoothPinCallback(response)
  32. })
  33. mainWindow.webContents.session.setBluetoothPairingHandler((details, callback) => {
  34. bluetoothPinCallback = callback
  35. // Send a message to the renderer to prompt the user to confirm the pairing.
  36. mainWindow.webContents.send('bluetooth-pairing-request', details)
  37. })
  38. mainWindow.loadFile('index.html')
  39. }
  40. app.whenReady().then(() => {
  41. createWindow()
  42. app.on('activate', function () {
  43. if (BrowserWindow.getAllWindows().length === 0) createWindow()
  44. })
  45. })
  46. app.on('window-all-closed', function () {
  47. if (process.platform !== 'darwin') app.quit()
  48. })
  1. const { contextBridge, ipcRenderer } = require('electron')
  2. contextBridge.exposeInMainWorld('electronAPI', {
  3. cancelBluetoothRequest: (callback) => ipcRenderer.send('cancel-bluetooth-request', callback),
  4. bluetoothPairingRequest: (callback) => ipcRenderer.on('bluetooth-pairing-request', callback),
  5. bluetoothPairingResponse: (response) => ipcRenderer.send('bluetooth-pairing-response', response)
  6. })
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
  6. <title>Web Bluetooth API</title>
  7. </head>
  8. <body>
  9. <h1>Web Bluetooth API</h1>
  10. <button id="clickme">Test Bluetooth</button>
  11. <button id="cancel">Cancel Bluetooth Request</button>
  12. <p>Currently selected bluetooth device: <strong id="device-name""></strong></p>
  13. <script src="./renderer.js"></script>
  14. </body>
  15. </html>
  1. async function testIt () {
  2. const device = await navigator.bluetooth.requestDevice({
  3. acceptAllDevices: true
  4. })
  5. document.getElementById('device-name').innerHTML = device.name || `ID: ${device.id}`
  6. }
  7. document.getElementById('clickme').addEventListener('click', testIt)
  8. function cancelRequest () {
  9. window.electronAPI.cancelBluetoothRequest()
  10. }
  11. document.getElementById('cancel').addEventListener('click', cancelRequest)
  12. window.electronAPI.bluetoothPairingRequest((event, details) => {
  13. const response = {}
  14. switch (details.pairingKind) {
  15. case 'confirm': {
  16. response.confirmed = window.confirm(`Do you want to connect to device ${details.deviceId}?`)
  17. break
  18. }
  19. case 'confirmPin': {
  20. response.confirmed = window.confirm(`Does the pin ${details.pin} match the pin displayed on device ${details.deviceId}?`)
  21. break
  22. }
  23. case 'providePin': {
  24. const pin = window.prompt(`Please provide a pin for ${details.deviceId}.`)
  25. if (pin) {
  26. response.pin = pin
  27. response.confirmed = true
  28. } else {
  29. response.confirmed = false
  30. }
  31. }
  32. }
  33. window.electronAPI.bluetoothPairingResponse(response)
  34. })

WebHID API

WebHID API 可以用于访问HID 设备,例如 键盘和游戏机。 Electron 提供了几个使用 WebHID API的接口:

  • 调用 navigator.hid.requestDevice 并选择高清设备,将触发会话内的 select-hid-device 事件 在处理 select-hid-device 事件期间, hid-device-addedhid-device-removed 两种 Session 事件可以被用来处理设备插拔. 注意: 这些事件仅会在 select-hid-device 的回调之后被触发。 它们不能作为通用HID设备监听器使用。
  • 在第一次调用 navigator.hid.requestDevice 前, 可以通过 ses.setDevicePermissionHandler(handler) 给予设备默认权限, 此外,Electron的默认行为是在相应的WebContents的生命周期内存储已授予的设备权限。 如果需要更长期的存储,开发人员可以存储设备许可信息(比如: 在处理 select-hid-device 事件时), 然后通过 setDevicePermissionHandler 从存储的信息中读取
  • ses.setPermissionCheckHandler(handler) 可以用于禁用特定来源的 HID 访问。

阻止列表

默认情况下,Electron 使用与 Chromium 相同的 blocklist 如果您想要覆盖此行为,您可以通过设置 disable-hid-blocklist 标志来做到这一点:

  1. app.commandLine.appendSwitch('disable-hid-blocklist')

示例

这个示例演示了,当 Test WebHID 按钮被点击后,一个Electron 应用将通过 ses.setDevicePermissionHandler(handler)select-hid-device 会话事件 自动选择 HID 设备

docs/fiddles/features/web-hid (27.0.1)Open in Fiddle

  • main.js
  • index.html
  • renderer.js
  1. const { app, BrowserWindow } = require('electron')
  2. function createWindow () {
  3. const mainWindow = new BrowserWindow({
  4. width: 800,
  5. height: 600
  6. })
  7. mainWindow.webContents.session.on('select-hid-device', (event, details, callback) => {
  8. // Add events to handle devices being added or removed before the callback on
  9. // `select-hid-device` is called.
  10. mainWindow.webContents.session.on('hid-device-added', (event, device) => {
  11. console.log('hid-device-added FIRED WITH', device)
  12. // Optionally update details.deviceList
  13. })
  14. mainWindow.webContents.session.on('hid-device-removed', (event, device) => {
  15. console.log('hid-device-removed FIRED WITH', device)
  16. // Optionally update details.deviceList
  17. })
  18. event.preventDefault()
  19. if (details.deviceList && details.deviceList.length > 0) {
  20. callback(details.deviceList[0].deviceId)
  21. }
  22. })
  23. mainWindow.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin, details) => {
  24. if (permission === 'hid' && details.securityOrigin === 'file:///') {
  25. return true
  26. }
  27. })
  28. mainWindow.webContents.session.setDevicePermissionHandler((details) => {
  29. if (details.deviceType === 'hid' && details.origin === 'file://') {
  30. return true
  31. }
  32. })
  33. mainWindow.loadFile('index.html')
  34. }
  35. app.whenReady().then(() => {
  36. createWindow()
  37. app.on('activate', function () {
  38. if (BrowserWindow.getAllWindows().length === 0) createWindow()
  39. })
  40. })
  41. app.on('window-all-closed', function () {
  42. if (process.platform !== 'darwin') app.quit()
  43. })
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
  6. <title>WebHID API</title>
  7. </head>
  8. <body>
  9. <h1>WebHID API</h1>
  10. <button id="clickme">Test WebHID</button>
  11. <h3>HID devices automatically granted access via <i>setDevicePermissionHandler</i></h3>
  12. <div id="granted-devices"></div>
  13. <h3>HID devices automatically granted access via <i>select-hid-device</i></h3>
  14. <div id="granted-devices2"></div>
  15. <script src="./renderer.js"></script>
  16. </body>
  17. </html>
  1. async function testIt () {
  2. const grantedDevices = await navigator.hid.getDevices()
  3. let grantedDeviceList = ''
  4. grantedDevices.forEach(device => {
  5. grantedDeviceList += `<hr>${device.productName}</hr>`
  6. })
  7. document.getElementById('granted-devices').innerHTML = grantedDeviceList
  8. const grantedDevices2 = await navigator.hid.requestDevice({
  9. filters: []
  10. })
  11. grantedDeviceList = ''
  12. grantedDevices2.forEach(device => {
  13. grantedDeviceList += `<hr>${device.productName}</hr>`
  14. })
  15. document.getElementById('granted-devices2').innerHTML = grantedDeviceList
  16. }
  17. document.getElementById('clickme').addEventListener('click', testIt)

Web Serial API

Web Serial API 可以被用来访问串口设备比如 USB 或蓝牙。 为了在 Electron 中使用这个 API, 开发者需要先定义关联在串口请求中的 select-serial-port 会话事件.

有几个额外的 API 用于与 Web Serial API 合作:

  • 在处理 select-serial-port 事件时, 可以使用会话中的serial-port-addedserial-port-removed 事件来处理设备的插拔。 注意: 这些事件仅会在 select-serial-port 的回调之后被触发。 它们不能作为通用串口监听器使用。
  • 在第一次调用 navigator.serial.requestPort 前, 可以通过 ses.setDevicePermissionHandler(handler) 给予设备默认权限, 此外,Electron的默认行为是在相应的WebContents的生命周期内存储已授予的设备权限。 如果需要更长期的存储,开发人员可以存储设备许可信息(比如: 在处理 select-serial-port 事件时), 然后通过 setDevicePermissionHandler 从存储的信息中读取
  • ses.setPermissionCheckHandler(handler) 可以用于禁用特定来源的串口访问。

示例

这个示例项目既演示了一个 Electron 应用通过 ses.setDevicePermissionHandler(handler) 自动选择串口设备,又演示了当 Test Web Serial 按钮被点击后,通过 select-serial-port event on the Session 选择第一个可用的(若已连接) Arduino Uno 串口设备的流程。

docs/fiddles/features/web-serial (27.0.1)Open in Fiddle

  • main.js
  • index.html
  • renderer.js
  1. const { app, BrowserWindow } = require('electron')
  2. function createWindow () {
  3. const mainWindow = new BrowserWindow({
  4. width: 800,
  5. height: 600
  6. })
  7. mainWindow.webContents.session.on('select-serial-port', (event, portList, webContents, callback) => {
  8. // Add listeners to handle ports being added or removed before the callback for `select-serial-port`
  9. // is called.
  10. mainWindow.webContents.session.on('serial-port-added', (event, port) => {
  11. console.log('serial-port-added FIRED WITH', port)
  12. // Optionally update portList to add the new port
  13. })
  14. mainWindow.webContents.session.on('serial-port-removed', (event, port) => {
  15. console.log('serial-port-removed FIRED WITH', port)
  16. // Optionally update portList to remove the port
  17. })
  18. event.preventDefault()
  19. if (portList && portList.length > 0) {
  20. callback(portList[0].portId)
  21. } else {
  22. // eslint-disable-next-line n/no-callback-literal
  23. callback('') // Could not find any matching devices
  24. }
  25. })
  26. mainWindow.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin, details) => {
  27. if (permission === 'serial' && details.securityOrigin === 'file:///') {
  28. return true
  29. }
  30. return false
  31. })
  32. mainWindow.webContents.session.setDevicePermissionHandler((details) => {
  33. if (details.deviceType === 'serial' && details.origin === 'file://') {
  34. return true
  35. }
  36. return false
  37. })
  38. mainWindow.loadFile('index.html')
  39. mainWindow.webContents.openDevTools()
  40. }
  41. app.whenReady().then(() => {
  42. createWindow()
  43. app.on('activate', function () {
  44. if (BrowserWindow.getAllWindows().length === 0) createWindow()
  45. })
  46. })
  47. app.on('window-all-closed', function () {
  48. if (process.platform !== 'darwin') app.quit()
  49. })
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
  6. <title>Web Serial API</title>
  7. <body>
  8. <h1>Web Serial API</h1>
  9. <button id="clickme">Test Web Serial API</button>
  10. <p>Matching Arduino Uno device: <strong id="device-name""></strong></p>
  11. <script src="./renderer.js"></script>
  12. </body>
  13. </html>
  1. async function testIt () {
  2. const filters = [
  3. { usbVendorId: 0x2341, usbProductId: 0x0043 },
  4. { usbVendorId: 0x2341, usbProductId: 0x0001 }
  5. ]
  6. try {
  7. const port = await navigator.serial.requestPort({ filters })
  8. const portInfo = port.getInfo()
  9. document.getElementById('device-name').innerHTML = `vendorId: ${portInfo.usbVendorId} | productId: ${portInfo.usbProductId} `
  10. } catch (ex) {
  11. if (ex.name === 'NotFoundError') {
  12. document.getElementById('device-name').innerHTML = 'Device NOT found'
  13. } else {
  14. document.getElementById('device-name').innerHTML = ex
  15. }
  16. }
  17. }
  18. document.getElementById('clickme').addEventListener('click', testIt)

WebUSB API

WebUSB API 可用于访问USB设备。 Electron提供了几个与WebUSB API配合使用的API:

示例

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.

docs/fiddles/features/web-usb (27.0.1)Open in Fiddle

  • main.js
  • index.html
  • renderer.js
  1. const { app, BrowserWindow } = require('electron')
  2. function createWindow () {
  3. const mainWindow = new BrowserWindow({
  4. width: 800,
  5. height: 600
  6. })
  7. let grantedDeviceThroughPermHandler
  8. mainWindow.webContents.session.on('select-usb-device', (event, details, callback) => {
  9. // Add events to handle devices being added or removed before the callback on
  10. // `select-usb-device` is called.
  11. mainWindow.webContents.session.on('usb-device-added', (event, device) => {
  12. console.log('usb-device-added FIRED WITH', device)
  13. // Optionally update details.deviceList
  14. })
  15. mainWindow.webContents.session.on('usb-device-removed', (event, device) => {
  16. console.log('usb-device-removed FIRED WITH', device)
  17. // Optionally update details.deviceList
  18. })
  19. event.preventDefault()
  20. if (details.deviceList && details.deviceList.length > 0) {
  21. const deviceToReturn = details.deviceList.find((device) => {
  22. return !grantedDeviceThroughPermHandler || (device.deviceId !== grantedDeviceThroughPermHandler.deviceId)
  23. })
  24. if (deviceToReturn) {
  25. callback(deviceToReturn.deviceId)
  26. } else {
  27. callback()
  28. }
  29. }
  30. })
  31. mainWindow.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin, details) => {
  32. if (permission === 'usb' && details.securityOrigin === 'file:///') {
  33. return true
  34. }
  35. })
  36. mainWindow.webContents.session.setDevicePermissionHandler((details) => {
  37. if (details.deviceType === 'usb' && details.origin === 'file://') {
  38. if (!grantedDeviceThroughPermHandler) {
  39. grantedDeviceThroughPermHandler = details.device
  40. return true
  41. } else {
  42. return false
  43. }
  44. }
  45. })
  46. mainWindow.webContents.session.setUSBProtectedClassesHandler((details) => {
  47. return details.protectedClasses.filter((usbClass) => {
  48. // Exclude classes except for audio classes
  49. return usbClass.indexOf('audio') === -1
  50. })
  51. })
  52. mainWindow.loadFile('index.html')
  53. }
  54. app.whenReady().then(() => {
  55. createWindow()
  56. app.on('activate', function () {
  57. if (BrowserWindow.getAllWindows().length === 0) createWindow()
  58. })
  59. })
  60. app.on('window-all-closed', function () {
  61. if (process.platform !== 'darwin') app.quit()
  62. })
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
  6. <title>WebUSB API</title>
  7. </head>
  8. <body>
  9. <h1>WebUSB API</h1>
  10. <button id="clickme">Test WebUSB</button>
  11. <h3>USB devices automatically granted access via <i>setDevicePermissionHandler</i></h3>
  12. <div id="granted-devices"></div>
  13. <h3>USB devices automatically granted access via <i>select-usb-device</i></h3>
  14. <div id="granted-devices2"></div>
  15. <script src="./renderer.js"></script>
  16. </body>
  17. </html>
  1. function getDeviceDetails (device) {
  2. return device.productName || `Unknown device ${device.deviceId}`
  3. }
  4. async function testIt () {
  5. const noDevicesFoundMsg = 'No devices found'
  6. const grantedDevices = await navigator.usb.getDevices()
  7. let grantedDeviceList = ''
  8. if (grantedDevices.length > 0) {
  9. grantedDevices.forEach(device => {
  10. grantedDeviceList += `<hr>${getDeviceDetails(device)}</hr>`
  11. })
  12. } else {
  13. grantedDeviceList = noDevicesFoundMsg
  14. }
  15. document.getElementById('granted-devices').innerHTML = grantedDeviceList
  16. grantedDeviceList = ''
  17. try {
  18. const grantedDevice = await navigator.usb.requestDevice({
  19. filters: []
  20. })
  21. grantedDeviceList += `<hr>${getDeviceDetails(grantedDevice)}</hr>`
  22. } catch (ex) {
  23. if (ex.name === 'NotFoundError') {
  24. grantedDeviceList = noDevicesFoundMsg
  25. }
  26. }
  27. document.getElementById('granted-devices2').innerHTML = grantedDeviceList
  28. }
  29. document.getElementById('clickme').addEventListener('click', testIt)