Accès aux périphériques
Comme les navigateurs basés sur Chromium, Electron fournit un accès au matériel du périphérique via des API Web. Ces API fonctionnent pour la plupart, comme dans un navigateur, mais il y a quelques différences qui doivent être prises en compte. La principale différence entre Electron et les navigateurs est ce qui se passe lorsque l'accès à l'appareil est demandé . Dans un navigateur, les utilisateurs se voient présenter une popup où ils peuvent accorder l' accès à un appareil individuel. Dans Electron des API sont fournies pouvant être utilisées par un développeur pour choisir automatiquement un périphérique ou demander aux utilisateurs de choisir un périphérique via une interface créée par un développeur.
API Web Bluetooth
L’API Web Bluetooth peut être utilisée pour communiquer avec des périphériques Bluetooth. Afin d'utiliser cette API dans Electron, les développeurs devront gérer l'événement select-bluetooth-device
sur le webContents associé à la demande de périphérique.
De plus, ses.setBluetoothPairingHandler(handler)
peut être utilisé pour gérer l'appairage aux périphériques bluetooth sous Windows ou Linux lorsque une validation supplémentaire comme un code personnel est nécessaire.
Exemple
Cet exemple montre une application Electron qui sélectionne automatiquement le premier périphérique Bluetooth disponible lorsque le bouton Test Bluetooth
est cliqué.
- 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)
})
API WebHID
L' API WebHID peut être utilisée pour accéder aux périphériques HID tels que les claviers et les manettes. Electron fournit plusieurs API pour travailler avec l'API WebHID:
- L'événement
select-hid-device
de la Session peut être utilisé pour sélectionner un périphérique HID lorsqu'un appel ànavigator.hid.requestDevice
est effectué. De plus, les événementshid-device-added
ethid-device-removed
de la Session peuvent être utilisés pour gérer les appareils branchés ou débranchés lors de la gestion de l'événementselect-hid-device
. Note: Ces événements pourront se déclencher tant que la fonction de callback deselect-hid-device
n'a pas pas encore été appelée. Ils ne sont pas, d'ailleurs, destinés à être utilisés comme écouteurs génériques de port série. ses.setDevicePermissionHandler(handler)
peut être utilisé pour fournir des autorisations par défaut aux appareils sans devoir demander au préalable l'autorisation aux appareils vianavigator.hid.requestDevice
. De plus, le comportement par défaut d'Electron est de stocker les autorisations accordées à l'appareil pendant la durée de vie du WebContents. Si un stockage à plus long terme est nécessaire, le développeur peut stocker les autorisations accordées à l'appareil (par exemple lors de la gestion de l'événementselect-hid-device
) puis les lire à partir de ce stockage avecsetDevicePermissionHandler
.ses.setPermissionCheckHandler(handler)
peut être utilisé pour désactiver l'accès HID pour des origines spécifiques.
Blocklist
Par défaut, Electron utilise la même liste noire que celle utilisée par Chromium. Si vous souhaitez outrepasser ce comportement, vous pouvez le faire en définissant le drapeau disable-hid-blocklist
:
app.commandLine.appendSwitch('disable-hid-blocklist')
Exemple
Cet exemple présente une application Electron qui sélectionne automatiquement les périphériques HID à travers ses.setDevicePermissionHandler(handler)
et à travers select-hid-device
événement sur la session lorsque le bouton Test WebHID
est cliqué.
- 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
L' API Web Serial peut être utilisée pour accéder aux périphériques série qui sont connectés par port série, USB, ou Bluetooth. Afin d'utiliser cette API dans Electron, les développeurs devront gérer l'événement select-serial-port
sur la session associée à la requête de port série.
Il existe plusieurs API supplémentaires pour travailler avec l'API Web Serial :
- Les évènements
serial-port-added
etserial-port-removed
de la session peuvent être utilisés pour gérer les périphériques branchés ou débranchés lors de la gestion de l'événementselect-serial-port
. Note: Ces événements pourront se déclencher tant que la fonction de callback deselect-serial-port
n'a pas pas encore étét appelée. Ils ne sont pas d'ailleurs destinés à être utilisés comme écouteurs génériques de port série. ses.setDevicePermissionHandler(handler)
peut être utilisé pour fournir des autorisations par défaut aux appareils sans devoir demander au préalable l'autorisation aux appareils vianavigator.serial.requestPort
. De plus, le comportement par défaut d'Electron est de stocker les autorisations accordées à l'appareil pendant la durée de vie du WebContents. Si un stockage à plus long terme est nécessaire, le développeur peut stocker les autorisations accordées à l'appareil (par exemple lors de la gestion de l'événementselect-serial-port
) puis les lire à partir de ce stockage avecsetDevicePermissionHandler
.ses.setPermissionCheckHandler(handler)
peut être utilisé pour désactiver l'accès série pour des origines spécifiques.
Exemple
Cet exemple présente une application Electron qui sélectionne automatiquement les périphériques série à travers ses.setDevicePermissionHandler(handler)
et illustre la sélection du premier périphérique série Arduino Uno disponible (si connecté) via select-serial-port
événement sur la session lorsque le bouton Test Web Serial
est cliqué.
- 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)
API WebUSB
L' API WebUSB peut être utilisée pour accéder aux périphériques USB. Electron fournit plusieurs API pour travailler avec l'API WebUSB :
- L’événement
select-usb-device
de la Session peut être utilisé pour sélectionner un périphérique USB lorsqu’un appel ànavigator.usb.requestDevice
est effectué. En outre, les événementsusb-device-added
andusb-device-removed
de la session peuvent être utilisés pour gérer les appareils branchés ou débranchés lors de la gestion de l’événementselect-usb-device
. Note: Ces deux événements ne se déclenchent qu'après l'appel à la callback deselect-usb-device
. Ils ne sont pas destinés à être utilisés comme un écouteur de périphérique Usb générique. - L’événement
usb-device-revoked
de la session peut être utilisé pour réagir lorsque device.forget() est appelé sur un périphérique USB. ses.setDevicePermissionHandler(handler)
peut être utilisé pour fournir une autorisation par défaut aux périphériques sans avoir à en faire la demande préalablement vianavigator.usb.requestDevice
. De plus, le comportement par défaut d'Electron est de stocker les autorisations accordées à l'appareil pendant la durée de vie du WebContents. Si un stockage à plus long terme est nécessaire, un développeur peut stocker les autorisations de périphérique accordées (par exemple lors de la gestion l’événementselect-usb-device
) afin de pouvoir ensuite relire à partir de ce stockage avecsetDevicePermissionHandler
.ses.setPermissionCheckHandler(handler)
peut être utilisé afin de désactiver l'accès à USB piour certaines origines.ses.setUSBProtectedClassesHandler
peut être utilisé pour autoriser l'usage de classes USB protégées qui ne sont pas disponibles par défaut.
Exemple
Cet exemple montre une application Electron qui sélectionne automatiquement les périphériques USB (s’ils sont attachés) via ses.setDevicePermissionHandler(gestionnaire)
et l'événement select-usb-device
de la Session lorsque l'on click sur le bouton Test WebUSB
.
- 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)