跳至主要內容

裝置存取

如同基於 Chromium 的瀏覽器,Electron 透過 Web API 提供對裝置硬體的存取。在大多數情況下,這些 API 的運作方式與在瀏覽器中相同,但有些差異需要考慮。Electron 和瀏覽器之間的主要差異在於請求裝置存取時會發生的情況。在瀏覽器中,使用者會看到一個彈出視窗,他們可以在其中授與對個別裝置的存取權。在 Electron 中,提供了開發人員可以使用的 API,以自動選擇裝置或提示使用者透過開發人員建立的介面選擇裝置。

Web Bluetooth API

Web Bluetooth API 可用於與藍牙裝置通訊。為了在 Electron 中使用此 API,開發人員需要處理與裝置請求相關聯的 webContents 上的 select-bluetooth-device 事件

此外,當需要額外的驗證(例如 PIN 碼)時,可以使用 ses.setBluetoothPairingHandler(handler) 來處理 Windows 或 Linux 上與藍牙裝置的配對。

範例

此範例示範了一個 Electron 應用程式,當按下 Test Bluetooth 按鈕時,會自動選擇第一個可用的藍牙裝置。

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()
})

WebHID API

WebHID API 可用於存取 HID 裝置,例如鍵盤和遊戲手把。Electron 提供了多個 API 用於處理 WebHID API。

  • 當呼叫 navigator.hid.requestDevice 時,可以使用 Session 上的 select-hid-device 事件 來選擇 HID 裝置。此外,當處理 select-hid-device 事件時,可以使用 Session 上的 hid-device-addedhid-device-removed 事件來處理裝置的插入或拔除。注意:這些事件只會在呼叫 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')

範例

此範例示範了一個 Electron 應用程式,該應用程式透過 ses.setDevicePermissionHandler(handler) 和當按下 Test WebHID 按鈕時,透過 Session 上的 select-hid-device 事件 自動選擇 HID 裝置。

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()
})

Web Serial API

Web Serial API 可用於存取透過串列埠、USB 或藍牙連線的串列裝置。為了在 Electron 中使用此 API,開發人員需要處理與串列埠請求相關聯的 Session 上的 select-serial-port 事件

還有其他幾個 API 用於處理 Web Serial API。

  • 當處理 select-serial-port 事件時,可以使用 Session 上的 serial-port-addedserial-port-removed 事件來處理裝置的插入或拔除。注意:這些事件只會在呼叫 select-serial-port 的回呼函數之前觸發。它們不打算用作通用的串列埠監聽器。
  • 可以使用 ses.setDevicePermissionHandler(handler) 為裝置提供預設權限,而無需先透過 navigator.serial.requestPort 請求裝置的權限。此外,Electron 的預設行為是在對應的 WebContents 的生命週期內儲存授與的裝置權限。如果需要長期儲存,開發人員可以儲存授與的裝置權限(例如,在處理 select-serial-port 事件時),然後使用 setDevicePermissionHandler 從該儲存讀取。
  • 可以使用 ses.setPermissionCheckHandler(handler) 來停用特定來源的串列存取。

範例

此範例示範了一個 Electron 應用程式,該應用程式透過 ses.setDevicePermissionHandler(handler) 自動選擇串列裝置,並示範了當按下 Test Web Serial 按鈕時,透過 Session 上的 select-serial-port 事件 選擇第一個可用的 Arduino Uno 串列裝置(如果已連線)。

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()
})

WebUSB API

WebUSB API 可用於存取 USB 裝置。Electron 提供了多個 API 用於處理 WebUSB API。

範例

這個範例展示了一個 Electron 應用程式,當點擊「測試 WebUSB」按鈕時,它會透過 ses.setDevicePermissionHandler(handler) 和 Session 上的 select-usb-device 事件,自動選擇 USB 裝置(如果已連接)。

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()
})