デバイスアクセス
Chromium ベースのブラウザのように、Electron はウェブ API を介してデバイスハードウェアへのアクセスを提供します。 ほとんどの場合これらの API はブラウザと同じように動作しますが、いくつかの違いを考慮しなければなりません。 Electronとブラウザの主な違いは、デバイスアクセスが要求されたときに起きることです。 ブラウザでは、ユーザーにポップアップが表示され、ユーザーは個々のデバイスにアクセスを許可できます。 Electron API では、デバイスを自動選択したり開発者が作成したインターフェースを介してユーザーにデバイス選択を促したりするために、開発者が利用できる API を提供しています。
ウェブ Bluetooth API
ウェブ Bluetooth API は、Bluetooth デバイスとの通信に利用できます。 この API を Electron で使用するには、開発者がデバイスリクエストに関連する webContents の select-bluetooth-device
イベント をハンドリングする必要があります。
加えて、ses.setBluetoothPairingHandler(handler)
を使うと、Windows や Linux で Bluetooth デバイスをペアリングするときに、PIN などの追加の検証が必要な場合をハンドリングできます。
サンプル
この例では、Test Bluetooth
ボタンがクリックされたときに最初に利用可能な Bluetooth デバイスを自動的に選択する、Electron のアプリケーションを示 しています。
- main.js
- preload.js
- index.html
- renderer.js
const { app, BrowserWindow, ipcMain } = require('electron/main')
const path = require('node:path')
let bluetoothPinCallback
let selectBluetoothCallback
function createWindow () {
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
mainWindow.webContents.on('select-bluetooth-device', (event, deviceList, callback) => {
event.preventDefault()
selectBluetoothCallback = callback
const result = deviceList.find((device) => {
return device.deviceName === 'test'
})
if (result) {
callback(result.deviceId)
} else {
// The device wasn't found so we need to either wait longer (eg until the
// device is turned on) or until the user cancels the request
}
})
ipcMain.on('cancel-bluetooth-request', (event) => {
selectBluetoothCallback('')
})
// Listen for a message from the renderer to get the response for the Bluetooth pairing.
ipcMain.on('bluetooth-pairing-response', (event, response) => {
bluetoothPinCallback(response)
})
mainWindow.webContents.session.setBluetoothPairingHandler((details, callback) => {
bluetoothPinCallback = callback
// Send a message to the renderer to prompt the user to confirm the pairing.
mainWindow.webContents.send('bluetooth-pairing-request', details)
})
mainWindow.loadFile('index.html')
}
app.whenReady().then(() => {
createWindow()
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
const { contextBridge, ipcRenderer } = require('electron/renderer')
contextBridge.exposeInMainWorld('electronAPI', {
cancelBluetoothRequest: () => ipcRenderer.send('cancel-bluetooth-request'),
bluetoothPairingRequest: (callback) => ipcRenderer.on('bluetooth-pairing-request', () => callback()),
bluetoothPairingResponse: (response) => ipcRenderer.send('bluetooth-pairing-response', response)
})
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<title>Web Bluetooth API</title>
</head>
<body>
<h1>Web Bluetooth API</h1>
<button id="clickme">Test Bluetooth</button>
<button id="cancel">Cancel Bluetooth Request</button>
<p>Currently selected bluetooth device: <strong id="device-name""></strong></p>
<script src="./renderer.js"></script>
</body>
</html>
async function testIt () {
const device = await navigator.bluetooth.requestDevice({
acceptAllDevices: true
})
document.getElementById('device-name').innerHTML = device.name || `ID: ${device.id}`
}
document.getElementById('clickme').addEventListener('click', testIt)
function cancelRequest () {
window.electronAPI.cancelBluetoothRequest()
}
document.getElementById('cancel').addEventListener('click', cancelRequest)
window.electronAPI.bluetoothPairingRequest((event, details) => {
const response = {}
switch (details.pairingKind) {
case 'confirm': {
response.confirmed = window.confirm(`Do you want to connect to device ${details.deviceId}?`)
break
}
case 'confirmPin': {
response.confirmed = window.confirm(`Does the pin ${details.pin} match the pin displayed on device ${details.deviceId}?`)
break
}
case 'providePin': {
const pin = window.prompt(`Please provide a pin for ${details.deviceId}.`)
if (pin) {
response.pin = pin
response.confirmed = true
} else {
response.confirmed = false
}
}
}
window.electronAPI.bluetoothPairingResponse(response)
})
WebHID API
WebHID API は、キーボードやゲームパッドなどの HID デバイスのアクセスに利用できます。 Electron は、WebHID API と連携するためにいくつかの API を提供しています。
- Session の
select-hid-device
イベント は、navigator.hid.requestDevice
が呼び出された時に HID デバイスを選択するために利用できます。 さらに Session のhid-device-added
とhid-device-removed
イベントは、select-hid-device
の処理中におけるデバイスの接続や接続解除のハンドリングに使用できます。 注意: これらのイベントは、select-hid-device
からのコールバックが呼び出されるまででのみ発生します。 これらは、一般的な HID デバイスのリスナーとして使用されることを意図していません。 ses.setDevicePermissionHandler(handler)
は、先にnavigator.hid.requestDevice
を介して許可の呼び出しをせずとも、デバイスへのデフォルトの権限を提供するために利用できます。 また、Electron のデフォルトの動作では、付与されたデバイスの権限を対応する WebContents が有効の間だけ保存します。 より長期間の保存が必要な場合、開発者は付与されたデバイスのパーミッションを保存し (select-hid-device
イベントを処理するときなど)、setDevicePermissionHandler
でそのストレージから読み出しできます。ses.setPermissionCheckHandler(handler)
を使うと、特定オリジンの HID アクセスを無効にできます。
ブロックリスト
デフォルトでは、Electron は Chromium が使用する ブロックリスト と同じものを採用しています。 この動作を無効にしたい場合は、以下のように disable-hid-blocklist
フラグで設定できます。
app.commandLine.appendSwitch('disable-hid-blocklist')
サンプル
この例では、ses.setDevicePermissionHandler(handler)
を通じて HID デバイスを自動選択する Electron アプリケーションを示しています。Test WebHID
ボタンがクリックされると、Session の select-hid-device
イベントを通じて HID デバイスを自動選択します。
- 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)
Web シリアル API
Web シリアル API を使用すると、シリアルポートや USB、Bluetooth で接続されたシリアルデバイスにアクセスできます。 この API を Electron で使用するには、開発者がシリアルポートのリクエストに関する Session の select-serial-port
イベント をハンドリングする必要があります。
Web シリアル API の利用にあたって更にいくつかの API があります。
- Session の
serial-port-added
とserial-port-removed
イベントは、select-serial-port
の処理中におけるデバイスの接続や接続解除のハンドリングに使用できます。 注意: これらのイベントは、select-serial-port
からのコールバックが呼び出されるまででのみ発生します。 これらは、一般的なシリアルポートのリスナーとして使用されることを意図していません。 ses.setDevicePermissionHandler(handler)
は、先にnavigator.serial.requestPort
を介して許可の呼び出しをせずとも、デバイスへのデフォルトの権限を提供するために利用できます。 また、Electron のデフォルトの動作では、付与されたデバイスの権限を対応する WebContents が有効の間だけ保存します。 より長期間の保存が必要な場合、開発者は付与されたデバイスのパーミッションを保存し (select-serial-port
イベントを処理するときなど)、setDevicePermissionHandler
でそのストレージから読み出しできます。ses.setPermissionCheckHandler(handler)
を使うと、特定オリジンのシリアルアクセスを無効にできます。
サンプル
この例では、ses.setDevicePermissionHandler(handler)
を通じてシリアルデバイスを自動的に選択する Electron アプリケーションを示しています。また、Test Web Serial
ボタンがクリックされたときに、Session の select-serial-port
イベント を通じて最初に利用可能な Arduino Uno シリアルデバイスを (接続していれば) 選択するデモも行っています。
- 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)
WebUSB API
WebUSB API は、USB デバイスへのアクセスに利用できます。 Electron は、WebUSB API と連携するためにいくつかの API を提供しています。
- Session の
select-usb-device
イベント は、navigator.usb.requestDevice
が呼び出された時に USB デバイスを選択するために利用できます。 さらに Session のusb-device-added
とusb-device-removed
イベントは、select-usb-device
の処理中におけるデバイスの接続や接続解除のハンドリングに使用できます。 注意: これら 2 つのイベントは、select-usb-device
からのコールバックが呼び出されるまででのみ発生します。 これらは、一般的な USB デバイスのリスナーとして使用されることを意図していません。 - Session の
usb-device-revoked
イベント は、USB デバイスで device.forget() が呼ばれたときの応答に使用されます。 ses.setDevicePermissionHandler(handler)
は、最初にnavigator.usb.requestDevice
を介してデバイス許可を呼び出さずとも、デフォルト許可の提供に使用できます。 また、Electron のデフォルトの動作では、付与されたデバイスの権限を対応する WebContents が有効の間だけ保存します。 より長期間の保存が必要な場合、開発者は許可されたデバイスのパーミッションを (例えばselect-usb-device
イベントのハンドリングで) 保存し、setDevicePermissionHandler
でそ のストレージから読み出せます。ses.setPermissionCheckHandler(handler)
を使うと、特定オリジンの USB アクセスを無効にできます。- デフォルトでは利用できない 保護された USB クラス の使用許可を得るには、`ses.setUSBProtectedClassesHandler が利用できます。
サンプル
この例では、ses.setDevicePermissionHandler(handler)
を通じて USB デバイスを (接続されていれば) 自動選択する Electron アプリケーションを示しています。Test WebUSB
ボタンがクリックされると、Session の select-usb-device
イベントを通じて自動選択します。
- 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)