Device Access

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.

Web Bluetooth API

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.

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

This example demonstrates an Electron application that automatically selects the first available bluetooth device when the Test Bluetooth button is clicked.

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

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:

  • The select-hid-device event on the Session can be used to select a HID device when a call to navigator.hid.requestDevice is made. Additionally the hid-device-added and hid-device-removed events on the Session can be used to handle devices being plugged in or unplugged when handling the select-hid-device event. Note: These events only fire until the callback from select-hid-device is called. They are not intended to be used as a generic hid device listener.
  • ses.setDevicePermissionHandler(handler) can be used to provide default permissioning to devices without first calling for permission to devices via navigator.hid.requestDevice. Additionally, the default behavior of Electron is to store granted device permission through the lifetime of the corresponding WebContents. If longer term storage is needed, a developer can store granted device permissions (eg when handling the select-hid-device event) and then read from that storage with setDevicePermissionHandler.
  • ses.setPermissionCheckHandler(handler) can be used to disable HID access for specific origins.

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:

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

Example

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.

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

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.

There are several additional APIs for working with the Web Serial API:

  • The serial-port-added and serial-port-removed events on the Session can be used to handle devices being plugged in or unplugged when handling the select-serial-port event. Note: These events only fire until the callback from select-serial-port is called. They are not intended to be used as a generic serial port listener.
  • ses.setDevicePermissionHandler(handler) can be used to provide default permissioning to devices without first calling for permission to devices via navigator.serial.requestPort. Additionally, the default behavior of Electron is to store granted device permission through the lifetime of the corresponding WebContents. If longer term storage is needed, a developer can store granted device permissions (eg when handling the select-serial-port event) and then read from that storage with setDevicePermissionHandler.
  • ses.setPermissionCheckHandler(handler) can be used to disable serial access for specific origins.

Example

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.

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

The WebUSB API can be used to access USB devices. Electron provides several APIs for working with the WebUSB API:

  • The select-usb-device event on the Session can be used to select a USB device when a call to navigator.usb.requestDevice is made. Additionally the usb-device-added and usb-device-removed events on the Session can be used to handle devices being plugged in or unplugged when handling the select-usb-device event. Note: These two events only fire until the callback from select-usb-device is called. They are not intended to be used as a generic usb device listener.
  • The usb-device-revoked event on the Session can be used to respond when device.forget() is called on a USB device.
  • ses.setDevicePermissionHandler(handler) can be used to provide default permissioning to devices without first calling for permission to devices via navigator.usb.requestDevice. Additionally, the default behavior of Electron is to store granted device permission through the lifetime of the corresponding WebContents. If longer term storage is needed, a developer can store granted device permissions (eg when handling the select-usb-device event) and then read from that storage with setDevicePermissionHandler.
  • ses.setPermissionCheckHandler(handler) can be used to disable USB access for specific origins.
  • `ses.setUSBProtectedClassesHandler can be used to allow usage of protected USB classes that are not available by default.

Example

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)