1"use strict";(self.webpackChunkelectronjs=self.webpackChunkelectronjs||[]).push([["2256"],{59792(e,n,t){t.r(n),t.d(n,{metadata:()=>s,default:()=>u,frontMatter:()=>o,contentTitle:()=>d,toc:()=>c,assets:()=>l});var s=JSON.parse('{"id":"latest/tutorial/devices","title":"Device Access","description":"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.","source":"@site/docs/latest/tutorial/devices.md","sourceDirName":"latest/tutorial","slug":"/latest/tutorial/devices","permalink":"/docs/latest/tutorial/devices","draft":false,"unlisted":false,"editUrl":"https://github.com/electron/electron/edit/main/docs/tutorial/devices.md","tags":[],"version":"current","frontMatter":{"title":"Device Access","description":"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.","slug":"devices","hide_title":false},"sidebar":"docs","previous":{"title":"Dark Mode","permalink":"/docs/latest/tutorial/dark-mode"},"next":{"title":"In-App Purchases","permalink":"/docs/latest/tutorial/in-app-purchases"}}'),i=t(74848),r=t(28453),a=t(78476);let o={title:"Device Access",description:"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.",slug:"devices",hide_title:!1},d="Device Access",l={},c=[{value:"Web Bluetooth API",id:"web-bluetooth-api",level:2},{value:"Example",id:"example",level:3},{value:"WebHID API",id:"webhid-api",level:2},{value:"Blocklist",id:"blocklist",level:3},{value:"Example",id:"example-1",level:3},{value:"Web Serial API",id:"web-serial-api",level:2},{value:"Blocklist",id:"blocklist-1",level:3},{value:"Example",id:"example-2",level:3},{value:"WebUSB API",id:"webusb-api",level:2},{value:"Blocklist",id:"blocklist-2",level:3},{value:"Example",id:"example-3",level:3}];function h(e){let n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,r.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.header,{children:(0,i.jsx)(n.h1,{id:"device-access",children:"Device Access"})}),"\n",(0,i.jsx)(n.p,{children:"Like Chromium based browsers, Electron provides access to device hardware\nthrough web APIs. For the most part these APIs work like they do in a browser,\nbut there are some differences that need to be taken into account. The primary\ndifference between Electron and browsers is what happens when device access is\nrequested. In a browser, users are presented with a popup where they can grant\naccess to an individual device. In Electron APIs are provided which can be\nused by a developer to either automatically pick a device or prompt users to\npick a device via a developer created interface."}),"\n",(0,i.jsx)(n.h2,{id:"web-bluetooth-api",children:"Web Bluetooth API"}),"\n",(0,i.jsxs)(n.p,{children:["The ",(0,i.jsx)(n.a,{href:"https://web.dev/bluetooth/",children:"Web Bluetooth API"})," can be used to communicate\nwith bluetooth devices. In order to use this API in Electron, developers will\nneed to handle the ",(0,i.jsxs)(n.a,{href:"/docs/latest/api/web-contents#event-select-bluetooth-device",children:[(0,i.jsx)(n.code,{children:"select-bluetooth-device"})," event on the webContents"]}),"\nassociated with the device request."]}),"\n",(0,i.jsxs)(n.p,{children:["Additionally, ",(0,i.jsx)(n.a,{href:"/docs/latest/api/session#sessetbluetoothpairinghandlerhandler-windows-linux",children:(0,i.jsx)(n.code,{children:"ses.setBluetoothPairingHandler(handler)"})}),"\ncan be used to handle pairing to bluetooth devices on Windows or Linux when\nadditional validation such as a pin is needed."]}),"\n",(0,i.jsx)(n.h3,{id:"example",children:"Example"}),"\n",(0,i.jsxs)(n.p,{children:["This example demonstrates an Electron application that automatically selects\nthe first available bluetooth device when the ",(0,i.jsx)(n.code,{children:"Test Bluetooth"})," button is\nclicked."]}),"\n",(0,i.jsx)(a.A,{files:{"index.html":'<!DOCTYPE html>\n<html>\n <head>\n <meta charset="UTF-8">\n <meta http-equiv="Content-Security-Policy" content="default-src \'self\'; script-src \'self\'">\n <title>Web Bluetooth API</title>\n </head>\n <body>\n <h1>Web Bluetooth API</h1>\n\n <button id="clickme">Test Bluetooth</button>\n <button id="cancel">Cancel Bluetooth Request</button>\n\n <p>Currently selected bluetooth device: <strong id="device-name"></strong></p>\n\n <script src="./renderer.js"><\/script>\n </body>\n</html>\n',"main.js":"const { app, BrowserWindow, ipcMain } = require('electron/main')\nconst path = require('node:path')\n\nlet bluetoothPinCallback\nlet selectBluetoothCallback\n\nfunction createWindow () {\n const mainWindow = new BrowserWindow({\n width: 800,\n height: 600,\n webPreferences: {\n preload: path.join(__dirname, 'preload.js')\n }\n })\n\n mainWindow.webContents.on('select-bluetooth-device', (event, deviceList, callback) =>
1 {\n event.preventDefault()\n selectBluetoothCallback = callback\n const result = deviceList.find((device) => {\n return device.deviceName === 'test'\n })\n if (result) {\n callback(result.deviceId)\n } else {\n // The device wasn't found so we need to either wait longer (eg until the\n // device is turned on) or until the user cancels the request\n }\n })\n\n ipcMain.on('cancel-bluetooth-request', () => {\n selectBluetoothCallback('')\n })\n\n // Listen for a message from the renderer to get the response for the Bluetooth pairing.\n ipcMain.on('bluetooth-pairing-response', (event, response) => {\n bluetoothPinCallback(response)\n })\n\n mainWindow.webContents.session.setBluetoothPairingHandler((details, callback) => {\n bluetoothPinCallback = callback\n // Send a message to the renderer to prompt the user to confirm the pairing.\n mainWindow.webContents.send('bluetooth-pairing-request', details)\n })\n\n mainWindow.loadFile('index.html')\n}\n\napp.whenReady().then(() => {\n createWindow()\n\n app.on('activate', function () {\n if (BrowserWindow.getAllWindows().length === 0) createWindow()\n })\n})\n\napp.on('window-all-closed', function () {\n if (process.platform !== 'darwin') app.quit()\n})\n","preload.js":"const { contextBridge, ipcRenderer } = require('electron/renderer')\n\ncontextBridge.exposeInMainWorld('electronAPI', {\n cancelBluetoothRequest: () => ipcRenderer.send('cancel-bluetooth-request'),\n bluetoothPairingRequest: (callback) => ipcRenderer.on('bluetooth-pairing-request', () => callback()),\n bluetoothPairingResponse: (response) => ipcRenderer.send('bluetooth-pairing-response', response)\n})\n","renderer.js":"async function testIt () {\n const device = await navigator.bluetooth.requestDevice({\n acceptAllDevices: true\n })\n document.getElementById('device-name').innerHTML = device.name || `ID: ${device.id}`\n}\n\ndocument.getElementById('clickme').addEventListener('click', testIt)\n\nfunction cancelRequest () {\n window.electronAPI.cancelBluetoothRequest()\n}\n\ndocument.getElementById('cancel').addEventListener('click', cancelRequest)\n\nwindow.electronAPI.bluetoothPairingRequest((event, details) => {\n const response = {}\n\n switch (details.pairingKind) {\n case 'confirm': {\n response.confirmed = window.confirm(`Do
1you want to connect to device ${details.deviceId}?`)\n break\n }\n case 'confirmPin': {\n response.confirmed = window.confirm(`Does the pin ${details.pin} match the pin displayed on device ${details.deviceId}?`)\n break\n }\n case 'providePin': {\n const pin = window.prompt(`Please provide a pin for ${details.deviceId}.`)\n if (pin) {\n response.pin = pin\n response.confirmed = true\n } else {\n response.confirmed = false\n }\n }\n }\n\n window.electronAPI.bluetoothPairingResponse(response)\n})\n"},dir:"docs/latest/fiddles/features/web-bluetooth",version:"44.4.5",focus:"main.js"}),"\n",(0,i.jsx)(n.h2,{id:"webhid-api",children:"WebHID API"}),"\n",(0,i.jsxs)(n.p,{children:["The ",(0,i.jsx)(n.a,{href:"https://web.dev/hid/",children:"WebHID API"})," can be used to access HID devices such\nas keyboards and gamepads. Electron provides several APIs for working with\nthe WebHID API:"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["The ",(0,i.jsxs)(n.a,{href:"/docs/latest/api/session#event-select-hid-device",children:[(0,i.jsx)(n.code,{children:"select-hid-device"})," event on the Session"]}),"\ncan be used to select a HID device when a call to\n",(0,i.jsx)(n.code,{children:"navigator.hid.requestDevice"})," is made. Additionally the ",(0,i.jsx)(n.a,{href:"/docs/latest/api/session#event-hid-device-added",children:(0,i.jsx)(n.code,{children:"hid-device-added"})}),"\nand ",(0,i.jsx)(n.a,{href:"/docs/latest/api/session#event-hid-device-removed",children:(0,i.jsx)(n.code,{children:"hid-device-removed"})})," events\non the Session can be used to handle devices being plugged in or unplugged\nwhen handling the ",(0,i.jsx)(n.code,{children:"select-hid-device"})," event.\n",(0,i.jsx)(n.strong,{children:"Note:"})," These events only fire until the callback from ",(0,i.jsx)(n.code,{children:"select-hid-device"}),"\nis called. They are not intended to be used as a generic hid device listener."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.a,{href:"/docs/latest/api/session#sessetdevicepermissionhandlerhandler",children:(0,i.jsx)(n.code,{children:"ses.setDevicePermissionHandler(handler)"})}),"\ncan be used to provide default permissioning to devices without first calling\nfor permission to devices via ",(0,i.jsx)(n.code,{children:"navigator.hid.requestDevice"}),". Additionally,\nthe default behavior of Electron is to store granted device permission through\nthe lifetime of the corresponding WebContents. If longer term storage is\nneeded, a developer can store granted device permissions (eg when handling\nthe ",(0,i.jsx)(n.code,{children:"select-hid-device"})," event) and then read from that storage with\n",(0,i.jsx)(n.code,{children:"setDevicePermissionHandler"}),"."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.a,{href:"/docs/latest/api/session#sessetpermissioncheckhandlerhandler",children:(0,i.jsx)(n.code,{children:"ses.setPermissionCheckHandler(handler)"})}),"\ncan be used to disable HID access for specific origins."]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"blocklist",children:"Blocklist"}),"\n",(0,i.jsxs)(n.p,{children:["By default Electron employs the same ",(0,i.jsx)(n.a,{href:"https://wicg.github.io/webhid/#blocklist",children:"blocklist"}),"\nused by Chromium. If you wish to override this behavior, you can do so by\nsetting the ",(0,i.jsx)(n.code,{children:"disable-hid-blocklist"})," flag:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-js",children:"app.commandLine.appendSwitch('disable-hid-blocklist')\n"})}),"\n",(0,i.jsx)(n.h3,{id:"example-1",children:"Example"}),"\n",(0,i.jsxs)(n.p,{children:["This example demonstrates an Electron application that automatically selects\nHID devices through ",(0,i.jsx)(n.a,{href:"/docs/latest/api/session#sessetdevicepermissionhandlerhandler",children:(0,i.jsx)(n.code,{children:"ses.setDevicePermissionHandler(handler)"})}),"\nand through ",(0,i.jsxs)(n.a,{href:"/docs/latest/api/session#event-select-hid-device",children:[(0,i.jsx)(n.code,{children:"select-hid-device"})," event on the Session"]}),"\nwhen the ",(0,i.jsx)(n.code,{children:"Test WebHID"})," button is clicked."]}),"\n",(0,i.jsx)(a.A,{files:{"index.html":'<!DOCTYPE html>\n<html>\n <head>\n <meta charset="UTF-8">\n <meta http-equiv="Content-Security-Policy" content="default-src \'self\'; script-src \'self\'">\n <title>WebHID API</title>\n </head>\n <body>\n <h1>WebHID API</h1>\n\n <button id="clickme">Test WebHID</button>\n\n <h3>HID devices automatically granted access via <i>setDevicePermissionHandler</i></h3>\n <div id="granted-devices"></div>\n\n <h3>HID devices automatically granted access via <i>select-hid-device</i></h3>\n <div id="granted-devices2"></div>\n\n <script src="./renderer.js"><\/script>\n </body>\n</html>\n',"main.js":"const { app, BrowserWindow } = require('electron/main')\n\nfunction createWindow () {\n const mainWindow = new BrowserWindow({\n width: 800,\n height: 600\n })\n\n mainWindow.webContents.session.on('select-hid-device', (event, details, callback) =>
1 {\n // Add events to handle devices being added or removed before the callback on\n // `select-hid-device` is called.\n mainWindow.webContents.session.on('hid-device-added', (event, device) => {\n console.log('hid-device-added FIRED WITH', device)\n // Optionally update details.deviceList\n })\n\n mainWindow.webContents.session.on('hid-device-removed', (event, device) => {\n console.log('hid-device-removed FIRED WITH', device)\n // Optionally update details.deviceList\n })\n\n event.preventDefault()\n if (details.deviceList && details.deviceList.length > 0) {\n callback(details.deviceList[0].deviceId)\n }\n })\n\n mainWindow.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin, details) => {\n if (permission === 'hid' && details.securityOrigin === 'file:///') {\n return true\n }\n })\n\n mainWindow.webContents.session.setDevicePermissionHandler((details) => {\n if (details.deviceType === 'hid' && details.origin === 'file://') {\n return true\n }\n })\n\n mainWindow.loadFile('index.html')\n}\n\napp.whenReady().then(() => {\n createWindow()\n\n app.on('activate', function () {\n if (BrowserWindow.getAllWindows().length === 0) createWindow()\n })\n})\n\napp.on('window-all-closed', function () {\n if (process.platform !== 'darwin') app.quit()\n})\n","renderer.js":"function formatDevices (devices) {\n return devices.map(device => device.productName).join('<hr>')\n}\n\nasync function testIt () {\n document.getElementById('granted-devices').innerHTML = formatDevices(await navigator.hid.getDevices())\n document.getElementById('granted-devices2').innerHTML = formatDevices(await navigator.hid.requestDevice({ filters: [] }))\n}\n\ndocument.getElementById('clickme').addEventListener('click', testIt)\n"},dir:"docs/latest/fiddles/features/web-hid",version:"44.4.5",focus:"main.js"}),"\n",(0,i.jsx)(n.h2,{id:"web-serial-api",children:"Web Serial API"}),"\n",(0,i.jsxs)(n.p,{children:["The ",(0,i.jsx)(n.a,{href:"https://web.dev/serial/",children:"Web Serial API"})," can be used to access serial\ndevices that are connected via serial port, USB, or Bluetooth. In order to use\nthis API in Electron, developers will need to handle the\n",(0,i.jsxs)(n.a,{href:"/docs/latest/api/session#event-select-serial-port",children:[(0,i.jsx)(n.code,{children:"select-serial-port"})," event on the Session"]}),"\nassociated with the serial port request."]}),"\n",(0,i.jsx)(n.p,{children:"There are several additional APIs for working with the Web Serial API:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["The ",(0,i.jsx)(n.a,{href:"/docs/latest/api/session#event-serial-port-added",children:(0,i.jsx)(n.code,{children:"serial-port-added"})}),"\nand ",(0,i.jsx)(n.a,{href:"/docs/latest/api/session#event-serial-port-removed",children:(0,i.jsx)(n.code,{children:"serial-port-removed"})})," events\non the Session can be used to handle devices being plugged in or unplugged\nwhen handling the ",(0,i.jsx)(n.code,{children:"select-serial-port"})," event.\n",(0,i.jsx)(n.strong,{children:"Note:"})," These events only fire until the callback from ",(0,i.jsx)(n.code,{children:"select-serial-port"}),"\nis called. They are not intended to be used as a generic serial port\nlistener."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.a,{href:"/docs/latest/api/session#sessetdevicepermissionhandlerhandler",children:(0,i.jsx)(n.code,{children:"ses.setDevicePermissionHandler(handler)"})}),"\ncan be used to provide default permissioning to devices without first calling\nfor permission to devices via ",(0,i.jsx)(n.code,{children:"navigator.serial.requestPort"}),". Additionally,\nthe default behavior of Electron is to store granted device permission through\nthe lifetime of the corresponding WebContents. If longer term storage is\nneeded, a developer can store granted device permissions (eg when handling\nthe ",(0,i.jsx)(n.code,{children:"select-serial-port"})," event) and then read from that storage with\n",(0,i.jsx)(n.code,{children:"setDevicePermissionHandler"}),"."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.a,{href:"/docs/latest/api/session#sessetpermissioncheckhandlerhandler",children:(0,i.jsx)(n.code,{children:"ses.setPermissionCheckHandler(handler)"})}),"\ncan be used to disable serial access for specific origins."]}),"\n"]}
1),"\n",(0,i.jsx)(n.h3,{id:"blocklist-1",children:"Blocklist"}),"\n",(0,i.jsxs)(n.p,{children:["By default Electron employs the same ",(0,i.jsx)(n.a,{href:"https://wicg.github.io/serial/#blocklist",children:"blocklist"}),"\nused by Chromium. If you wish to override this behavior, you can do so by\nsetting the ",(0,i.jsx)(n.code,{children:"disable-serial-blocklist"})," flag:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-js",children:"app.commandLine.appendSwitch('disable-serial-blocklist')\n"})}),"\n",(0,i.jsx)(n.h3,{id:"example-2",children:"Example"}),"\n",(0,i.jsxs)(n.p,{children:["This example demonstrates an Electron application that automatically selects\nserial devices through ",(0,i.jsx)(n.a,{href:"/docs/latest/api/session#sessetdevicepermissionhandlerhandler",children:(0,i.jsx)(n.code,{children:"ses.setDevicePermissionHandler(handler)"})}),"\nas well as demonstrating selecting the first available Arduino Uno serial device (if connected) through\n",(0,i.jsxs)(n.a,{href:"/docs/latest/api/session#event-select-serial-port",children:[(0,i.jsx)(n.code,{children:"select-serial-port"})," event on the Session"]}),"\nwhen the ",(0,i.jsx)(n.code,{children:"Test Web Serial"})," button is clicked."]}),"\n",(0,i.jsx)(a.A,{files:{"index.html":'<!DOCTYPE html>\n<html>\n <head>\n <meta charset="UTF-8">\n <meta http-equiv="Content-Security-Policy" content="default-src \'self\'; script-src \'self\'">\n <title>Web Serial API</title>\n <body>\n <h1>Web Serial API</h1>\n\n <button id="clickme">Test Web Serial API</button>\n\n <p>Matching Arduino Uno device: <strong id="device-name""></strong></p>\n\n <script src="./renderer.js"><\/script>\n </body>\n</html>',"main.js":"const { app, BrowserWindow } = require('electron/main')\n\nfunction createWindow () {\n const mainWindow = new BrowserWindow({\n width: 800,\n height: 600\n })\n\n mainWindow.webContents.session.on('select-serial-port', (event, portList, webContents, callback) => {\n // Add listeners to handle ports being added or removed before the callback for `select-serial-port`\n // is called.\n mainWindow.webContents.session.on('serial-port-added', (event, port) => {\n console.log('serial-port-added FIRED WITH', port)\n // Optionally update portList to add the new port\n })\n\n mainWindow.webContents.session.on('serial-port-removed', (event, port) => {\n console.log('serial-port-removed FIRED WITH', port)\n // Optionally update portList to remove the port\n })\n\n event.preventDefault()\n if (portList && portList.length > 0) {\n callback(portList[0].portId)\n } else {\n callback('') // Could not find any matching devices\n }\n })\n\n mainWindow.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin, details) => {\n if (permission === 'serial' && details.securityOrigin === 'file:///') {\n return true\n }\n\n return false\n })\n\n mainWindow.webContents.session.setDevicePermissionHandler((details) => {\n if (details.deviceType === 'serial' && details.origin === 'file://') {\n return true\n }\n\n return false\n })\n\n mainWindow.loadFile('index.html')\n\n mainWindow.webContents.openDevTools()\n}\n\napp.whenReady().then(() => {\n createWindow()\n\n app.on('activate', function () {\n if (BrowserWindow.getAllWindows().length === 0) createWindow()\n })\n})\n\napp.on('window-all-closed', function () {\n if (process.platform !== 'darwin') app.quit()\n})\n","renderer.js":"async function testIt () {\n const filters = [\n { usbVendorId: 0x2341, usbProductId: 0x0043 },\n { usbVendorId: 0x2341, usbProductId: 0x0001 }\n ]\n try {\n const port = await navigator.serial.requestPort({ filters })\n const portInfo = port.getInfo()\n document.getElementById('device-name').innerHTML = `vendorId: ${portInfo.usbVendorId} | productId: ${portInfo.usbProductId} `\n } catch (ex) {\n if (ex.name === 'NotFoundError') {\n document.getElementById('device-name').innerHTML = 'Device NOT found'\n } else {\n document.getElementById('device-name').innerHTML = ex\n }\n }\n}\n\ndocument.getElementById('clickme').addEventListener('click', testIt)\n"},dir:"docs/latest/fiddles/features/web-serial",version:"44.4.5",focus:"main.js"}),"\n",(0,i.jsx)(n.h2,{id:"webusb-api",children:"WebUSB API"}),"\n",(0,i.jsxs)(n.p,{children:["The ",(0,i.jsx)(n.a,{href:"https://web.dev/usb/",children:"WebUSB API"})," can be used to access USB devices.\nElectron provides several APIs for working with the WebUSB API:"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["The ",(0,i.jsxs)(n.a,{href:"/docs/latest/api/session#event-select-usb-device",children:[(0,i.jsx)(n.code,{children:"select-usb-device"})," event on the Session"]}),"\ncan be used to select a USB device when a call to\n",(0,i.jsx)(n.code,{children:"navigator.usb.requestDevice"})," is made. Additionally the ",(0,i.jsx)(n.a,{href:"/docs/latest/api/session#event-usb-device-added",children:(0,i.jsx)(n.code,{children:"usb-device-added"})}),"\nand ",(0,i.jsx)(n.a,{href:"/docs/latest/api/session#event-usb-device-removed",children:(0,i.jsx)(n.code,{children:"usb-device-removed"})})," events\non the Session can be used to handle devices being plugged in or unplugged\nwhen handling the ",(0,i.jsx)(n.code,{children:"select-usb-device"})," event.\n",(0,i.jsx)(n.strong,{children:"Note:"}
1)," These two events only fire until the callback from ",(0,i.jsx)(n.code,{children:"select-usb-device"}),"\nis called. They are not intended to be used as a generic usb device listener."]}),"\n",(0,i.jsxs)(n.li,{children:["The ",(0,i.jsxs)(n.a,{href:"/docs/latest/api/session#event-usb-device-revoked",children:[(0,i.jsx)(n.code,{children:"usb-device-revoked"})," event on the Session"]})," can\nbe used to respond when ",(0,i.jsx)(n.a,{href:"https://developer.chrome.com/articles/usb/#revoke-access",children:"device.forget()"}),"\nis called on a USB device."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.a,{href:"/docs/latest/api/session#sessetdevicepermissionhandlerhandler",children:(0,i.jsx)(n.code,{children:"ses.setDevicePermissionHandler(handler)"})}),"\ncan be used to provide default permissioning to devices without first calling\nfor permission to devices via ",(0,i.jsx)(n.code,{children:"navigator.usb.requestDevice"}),". Additionally,\nthe default behavior of Electron is to store granted device permission through\nthe lifetime of the corresponding WebContents. If longer term storage is\nneeded, a developer can store granted device permissions (eg when handling\nthe ",(0,i.jsx)(n.code,{children:"select-usb-device"})," event) and then read from that storage with\n",(0,i.jsx)(n.code,{children:"setDevicePermissionHandler"}),"."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.a,{href:"/docs/latest/api/session#sessetpermissioncheckhandlerhandler",children:(0,i.jsx)(n.code,{children:"ses.setPermissionCheckHandler(handler)"})}),"\ncan be used to disable USB access for specific origins."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.a,{href:"/docs/latest/api/session#sessetusbprotectedclasseshandlerhandler",children:"`ses.setUSBProtectedClassesHandler"}),"\ncan be used to allow usage of ",(0,i.jsx)(n.a,{href:"https://wicg.github.io/webusb/#usbinterface-interface",children:"protected USB classes"})," that are not available by default."]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"blocklist-2",children:"Blocklist"}),"\n",(0,i.jsxs)(n.p,{children:["By default Electron employs the same ",(0,i.jsx)(n.a,{href:"https://wicg.github.io/webusb/#blocklist",children:"blocklist"}),"\nused by Chromium. If you wish to override this behavior, you can do so by\nsetting the ",(0,i.jsx)(n.code,{children:"disable-usb-blocklist"})," flag:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-js",children:"app.commandLine.appendSwitch('disable-usb-blocklist')\n"})}),"\n",(0,i.jsx)(n.h3,{id:"example-3",children:"Example"}),"\n",(0,i.jsxs)(n.p,{children:["This example demonstrates an Electron application that automatically selects\nUSB devices (if they are attached) through ",(0,i.jsx)(n.a,{href:"/docs/latest/api/session#sessetdevicepermissionhandlerhandler",children:(0,i.jsx)(n.code,{children:"ses.setDevicePermissionHandler(handler)"})}),"\nand through ",(0,i.jsxs)(n.a,{href:"/docs/latest/api/session#event-select-usb-device",children:[(0,i.jsx)(n.code,{children:"select-usb-device"})," event on the Session"]}),"\nwhen the ",(0,i.jsx)(n.code,{children:"Test WebUSB"})," button is clicked."]}),"\n",(0,i.jsx)(a.A,{files:{"index.html":'<!DOCTYPE html>\n<html>\n <head>\n <meta charset="UTF-8">\n <meta http-equiv="Content-Security-Policy" content="default-src \'self\'; script-src \'self\'">\n <title>WebUSB API</title>\n </head>\n <body>\n <h1>WebUSB API</h1>\n\n <button id="clickme">Test WebUSB</button>\n\n <h3>USB devices automatically granted access via <i>setDevicePermissionHandler</i></h3>\n <div id="granted-devices"></div>\n\n <h3>USB devices automatically granted access via <i>select-usb-device</i></h3>\n <div id="granted-devices2"></div>\n\n <script src="./renderer.js"><\/script>\n </body>\n</html>\n',"main.js":"const { app, BrowserWindow } = require('electron/main')\n\nfunction createWindow () {\n const mainWindow = new BrowserWindow({\n width: 800,\n height: 600\n })\n\n let grantedDeviceThroughPermHandler\n\n mainWindow.webContents.session.on('select-usb-device', (event, details, callback) =>
1 {\n // Add events to handle devices being added or removed before the callback on\n // `select-usb-device` is called.\n mainWindow.webContents.session.on('usb-device-added', (event, device) => {\n console.log('usb-device-added FIRED WITH', device)\n // Optionally update details.deviceList\n })\n\n mainWindow.webContents.session.on('usb-device-removed', (event, device) => {\n console.log('usb-device-removed FIRED WITH', device)\n // Optionally update details.deviceList\n })\n\n event.preventDefault()\n if (details.deviceList && details.deviceList.length > 0) {\n const deviceToReturn = details.deviceList.find((device) => {\n return !grantedDeviceThroughPermHandler || (device.deviceId !== grantedDeviceThroughPermHandler.deviceId)\n })\n if (deviceToReturn) {\n callback(deviceToReturn.deviceId)\n } else {\n callback()\n }\n }\n })\n\n mainWindow.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin, details) => {\n if (permission === 'usb' && details.securityOrigin === 'file:///') {\n return true\n }\n })\n\n mainWindow.webContents.session.setDevicePermissionHandler((details) => {\n if (details.deviceType === 'usb' && details.origin === 'file://') {\n if (!grantedDeviceThroughPermHandler) {\n grantedDeviceThroughPermHandler = details.device\n return true\n } else {\n return false\n }\n }\n })\n\n mainWindow.webContents.session.setUSBProtectedClassesHandler((details) => {\n return details.protectedClasses.filter((usbClass) => {\n // Exclude classes except for audio classes\n return usbClass.indexOf('audio') === -1\n })\n })\n\n mainWindow.loadFile('index.html')\n}\n\napp.whenReady().then(() => {\n createWindow()\n\n app.on('activate', function () {\n if (BrowserWindow.getAllWindows().length === 0) createWindow()\n })\n})\n\napp.on('window-all-closed', function () {\n if (process.platform !== 'darwin') app.quit()\n})\n","renderer.js":"function getDeviceDetails (device) {\n return device.productName || `Unknown device ${device.deviceId}`\n}\n\nasync function testIt () {\n const noDevicesFoundMsg = 'No devices found'\n const grantedDevices = await navigator.usb.getDevices()\n let grantedDeviceList = ''\n if (grantedDevices.length > 0) {\n for (const device of grantedDevices) {\n grantedDeviceList += `<hr>${getDeviceDetails(device)}</hr>`\n }\n } else {\n grantedDeviceList = noDevicesFoundMsg\n }\n document.getElementById('granted-devices').innerHTML = grantedDeviceList\n\n grantedDeviceList = ''\n try {\n const grantedDevice = await navigator.usb.requestDevice({\n filters: []\n })\n grantedDeviceList += `<hr>${getDeviceDetails(grantedDevice)}</hr>`\n } catch (ex) {\n if (ex.name === 'NotFoundError') {\n grantedDeviceList = noDevicesFoundMsg\n }\n }\n document.getElementById('granted-devices2').innerHTML = grantedDeviceList\n}\n\ndocument.getElementById('clickme').addEventListener('click', testIt)\n"},dir:"docs/latest/fiddles/features/web-usb",version:"44.4.5",focus:"main.js"})]})}function u(e={}){let{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(h,{...e})}):h(e)}},57250(e,n,t){t.d(n,{A:()=>o});var s=t(74848);t(96540);var i=t(18215),r=t(57663);function a({children:e,className:n,hidden:t}){return(0,s.jsx)("div",{role:"tabpanel",className:(0,i.A)("tabItem_Ymn6",n),hidden:t,children:e})}function o({children:e,className:n,value:t}){let{selectedValue:i,lazy:d}=(0,r.uc)(),l=t===i;return!l&&d?null:(0,s.jsx)(a,{className:n,hidden:!l,children:e})}},50773(e,n,t){t.d(n,{A:()=>u});var s=t(74848);t(96540);var i=t(18215),r=t(88287),a=t(57663),o=t(28584),d=t(19863);function l({className:e}){let{selectedValue:n,selectValue:t,tabValues:r,block:d}=(0,a.uc)(),c=[],{blockElementScrollPositionUntilNextRender:h}=(0,o.a_)(),u=e=>{let s=e.currentTarget,i=r[c.indexOf(s)].value;i!==n&&(h(s),t(i))},v=e=>{let n=null;switch(e.key){case"Enter":u(e);break;case"ArrowRight":{let t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{let t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1]}}n?.focus()};return(0,s.jsx)("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":d},e),children:r.map(({value:e,label:t,attributes:r})=>(0,s.jsx)("li",{role:"tab",tabIndex:n===e?0:-1,"aria-selected":n===e,ref:e=>{c.push(e)},onKeyDown:v,onClick:u,...r,className:(0,i.A)("tabs__item","tabItem_LNqP",r?.className,{"tabs__item--active":n===e}),children:t??e},e))})}function c({children:e}){return(0,s.jsx)("div",{className:"margin-top--md",children:e})}function h({className:e,children:n}){return(0,s.jsxs)("div",{className:(0,i.A)(r.G.tabs.container,"tabs-container","tabList__CuJ"),children:[(0,s.jsx)(l,{className:e}),(0,s.jsx)(c,{children:n})]})}function u(e){let n=(0,d.A)(),t=(0,a.OC)(e);return(0,s.jsx)(a.O_,{value:t,children:(0,s.jsx)(h,{className:e.className,children:(0,a.vT)(e.children)})},String(n))}},57663(e,n,t){t.d(n,{OC:()=>u,O_:()=>b,uc:()=>p,vT:()=>c});var s=t(74848),i=t(96540),r=t(56347),a=t(99989),o=t(96629),d=t(80618),l=t(41367);function c(e){return i.Children.toArray(e).filter(e=>"\n"!==e)}function h({value:e,tabValues:n}){return n.some(n=>n.value===e)}function u(e){let n,{defaultValue:t,queryString:s=!1,groupId:c}=e,u=function(e){let{values:n,children:t}=e;return(0,i.useMemo)(()=>{let e=n??i.Children.toArray(t).flatMap(e=>{if(!e)return[];if((0,i.isValidElement)(e)&&function(e){let{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return[e];
1let n="string"==typeof e.type?e.type:e.type.name;throw Error(`Docusaurus error: Bad <Tabs> child <${n}>: all children of the <Tabs> component should be <TabItem>, and every <TabItem> should have a unique "value" prop. 2If you do not want to pass on a "value" prop to the direct children of <Tabs>, you can also pass an explicit <Tabs values={...}> prop.`)}).map(({props:{value:e,label:n,attributes:t,default:s}})=>({value:e,label:n,attributes:t,default:s})),s=(0,d.XI)(e,(e,n)=>e.value===n.value);if(s.length>0)throw Error(`Docusaurus error: Duplicate values "${s.map(e=>`'${e.value}'`).join(", ")}" found in <Tabs>. Every value needs to be unique.`);return e},[n,t])}(e),[v,p]=(0,i.useState)(()=>(function({defaultValue:e,tabValues:n}){if(0===n.length)throw Error("Docusaurus error: the <Tabs> component requires at least one <TabItem> children component");if(e){if(!h({value:e,tabValues:n}))throw Error(`Docusaurus error: The <Tabs> has a defaultValue "${e}" but none of its children has the corresponding value. Available values are: ${n.map(e=>e.value).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return e}let t=n.find(e=>e.default)??n[0];if(!t)throw Error("Unexpected error: 0 tabValues");return t.value})({defaultValue:t,tabValues:u})),[b,m]=function({queryString:e=!1,groupId:n}){let t=(0,r.W6)(),s=function({queryString:e=!1,groupId:n}){if("string"==typeof e)return e;if(!1===e)return null;if(!0===e&&!n)throw Error('Docusaurus error: The <Tabs> component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:e,groupId:n});return[(0,o.aZ)(s),(0,i.useCallback)(e=>{if(!s)return;let n=new URLSearchParams(t.location.search);n.set(s,e),t.replace({...t.location,search:n.toString()})},[s,t])]}({queryString:s,groupId:c}),[f,w]=function({groupId:e}){let n=e?`docusaurus.tab.${e}`:null,[t,s]=(0,l.Dv)(n);return[t,(0,i.useCallback)(e=>{n&&s.set(e)},[n,s])]}({groupId:c}),g=h({value:n=b??f,tabValues:u})?n:null;return(0,a.A)(()=>{g&&p(g)},[g]),{selectedValue:v,selectValue:(0,i.useCallback)(e=>{if(!h({value:e,tabValues:u}))throw Error(`Can't select invalid tab value=${e}`);p(e),m(e),w(e)},[m,w,u]),tabValues:u,lazy:e.lazy??!1,block:e.block??!1}}let v=(0,i.createContext)(null);function p(){let e=i.useContext(v);if(!e)throw Error("useTabsContext() must be used within a Tabs component");return e}function b(e){return(0,s.jsx)(v.Provider,{value:e.value,children:e.children})}},78476(e,n,t){t.d(n,{A:()=>c});var s=t(74848);t(96540);var i=t(50773),r=t(57250),a=t(1113);let o="dot_YdbR",d={"main.js":4,"preload.js":3,"index.html":2,"renderer.js":1};function l({children:e,version:n,gitDir:t}){return(0,s.jsxs)("div",{className:"editorWindow_ZQWz",children:[(0,s.jsxs)("div",{className:"editorWindowHeader_GqG7",children:[(0,s.jsxs)("div",{className:"buttons_msmS",children:[(0,s.jsx)("span",{className:o,style:{background:"#f25f58"}}),(0,s.jsx)("span",{className:o,style:{background:"#fbbe3c"}}),(0,s.jsx)("span",{className:o,style:{background:"#58cb42"}})]}),(0,s.jsxs)("a",{className:"editorTitle_buME",href:`https://github.com/electron/electron/tree/v${n}/${t}`,children:[t," (",n,")"]}),(0,s.jsx)("a",{target:"_blank",className:"button button--primary button--md",href:`https://fiddle.electronjs.org/launch?target=electron/v${n}/${t}`,rel:"noreferrer",children:"Open in Fiddle"})]}),(0,s.jsx)("div",{className:"editorWindowBody_R11m",children:e})]})}let c=e=>{let{files:n,focus:t,version:o,dir:c}=e,h=c.replace(/docs\/[a-zA-Z0-9-]+\/fiddles/,"docs/fiddles"),u=Object.keys(n).sort((e,n)=>d[n]-d[e]),v=u.map(e=>({label:e,value:e}));return(0,s.jsx)(l,{version:o,gitDir:h,children:(0,s.jsx)(i.A,{values:v,defaultValue:t||"main.js",children:u.map(e=>(0,s.jsx)(r.A,{value:e,children:(0,s.jsx)(a.A,{className:`language-${e.split(".").pop()}`,children:n[e]})},e))})})}},28453(e,n,t){t.d(n,{R:()=>a,x:()=>o});var s=t(96540);let i={},r=s.createContext(i);function a(e){let n=s.useContext(r);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:a(e.components),s.createElement(r.Provider,{value:n},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.