Skip to main content

디바이스 접근

Chromium 기반 브라우저와 마찬가지로, Electron은 웹 API를 통해 디바이스 하드웨어에 접근할 수 있는 기능을 제공한다. 대부분의 경우 이 API들은 브라우저에서와 동일하게 작동하지만, 몇 가지 차이점이 존재하며 이를 고려해야 한다. Electron과 브라우저의 주요 차이점은 디바이스 접근 요청 시 발생하는 동작이다. 브라우저에서는 사용자에게 팝업이 표시되어 개별 디바이스에 대한 접근 권한을 부여할 수 있다. 반면, Electron에서는 개발자가 자동으로 디바이스를 선택하거나 개발자가 만든 인터페이스를 통해 사용자에게 디바이스 선택을 요청할 수 있는 API를 제공한다.

Web Bluetooth API

Web Bluetooth API를 사용하면 블루투스 장치와 통신할 수 있다. 이 API를 Electron에서 사용하려면, 개발자는 장치 요청과 관련된 webContents의 select-bluetooth-device 이벤트를 처리해야 한다.

또한, Windows나 Linux에서 블루투스 장치와 페어링할 때 PIN과 같은 추가 인증이 필요한 경우, ses.setBluetoothPairingHandler(handler)를 사용해 페어링을 처리할 수 있다.

예제

이 예제는 Test Bluetooth 버튼을 클릭했을 때 사용 가능한 첫 번째 블루투스 장치를 자동으로 선택하는 Electron 애플리케이션을 보여준다.

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(Human Interface Device) 장치에 접근할 때 사용한다. Electron은 WebHID API를 다루기 위해 여러 API를 제공한다:

  • select-hid-device 이벤트navigator.hid.requestDevice가 호출될 때 HID 장치를 선택하는 데 사용할 수 있다. 또한 hid-device-addedhid-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')

예제

이 예제는 Test WebHID 버튼을 클릭했을 때, ses.setDevicePermissionHandler(handler)select-hid-device 이벤트 on the Session을 통해 HID 장치를 자동으로 선택하는 Electron 애플리케이션을 보여준다.

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를 사용하려면 개발자는 시리얼 포트 요청과 관련된 Sessionselect-serial-port 이벤트를 처리해야 한다.

Web Serial API와 함께 사용할 수 있는 추가 API는 다음과 같다:

  • Session의 serial-port-addedserial-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 버튼을 클릭했을 때 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은 WebUSB API 작업을 위해 여러 API를 제공한다:

  • select-usb-device 이벤트navigator.usb.requestDevice 호출 시 USB 장치를 선택하는 데 사용할 수 있다. 또한 usb-device-addedusb-device-removed 이벤트는 select-usb-device 이벤트를 처리하는 동안 장치가 연결되거나 제거될 때 사용할 수 있다.
    참고: 이 두 이벤트는 select-usb-device의 콜백이 호출될 때까지만 발생한다. 일반적인 USB 장치 리스너로 사용하기 위한 목적이 아니다.
  • usb-device-revoked 이벤트는 USB 장치에서 device.forget()가 호출될 때 응답하는 데 사용할 수 있다.
  • ses.setDevicePermissionHandler(handler)navigator.usb.requestDevice를 통해 먼저 권한을 요청하지 않고도 장치에 기본 권한을 부여하는 데 사용할 수 있다. 또한 Electron의 기본 동작은 해당 WebContents의 수명 동안 부여된 장치 권한을 저장하는 것이다. 장기 저장이 필요한 경우, 개발자는 부여된 장치 권한을 저장하고(예: select-usb-device 이벤트 처리 시) setDevicePermissionHandler를 통해 해당 저장소에서 읽어올 수 있다.
  • ses.setPermissionCheckHandler(handler)는 특정 오리진에 대해 USB 접근을 비활성화하는 데 사용할 수 있다.
  • ses.setUSBProtectedClassesHandler는 기본적으로 사용할 수 없는 보호된 USB 클래스의 사용을 허용하는 데 사용할 수 있다.

예제

이 예제는 Electron 애플리케이션에서 Test WebUSB 버튼을 클릭했을 때, 연결된 USB 장치를 자동으로 선택하는 방법을 보여준다. 이를 위해 ses.setDevicePermissionHandler(handler)select-usb-device 이벤트를 사용한다.

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