fix: allow cancelling of bluetooth requests (#37601)

* fix: allow cancelling of bluetooth requests

allows cancelling of bluetooth requests when no devices present

* docs: update docs to reflect how bluetooth works.
This commit is contained in:
John Kleinschmidt 2023-03-27 09:31:15 -04:00 committed by GitHub
parent 42e7cd9b3f
commit 6a6908c4c8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 56 additions and 31 deletions

View file

@ -777,20 +777,24 @@ Returns:
* `callback` Function * `callback` Function
* `deviceId` string * `deviceId` string
Emitted when bluetooth device needs to be selected on call to Emitted when a bluetooth device needs to be selected when a call to
`navigator.bluetooth.requestDevice`. To use `navigator.bluetooth` api `navigator.bluetooth.requestDevice` is made. `callback` should be called with
`webBluetooth` should be enabled. If `event.preventDefault` is not called, the `deviceId` of the device to be selected. Passing an empty string to
first available device will be selected. `callback` should be called with `callback` will cancel the request.
`deviceId` to be selected, passing empty string to `callback` will
cancel the request.
If no event listener is added for this event, all bluetooth requests will be cancelled. If an event listener is not added for this event, or if `event.preventDefault`
is not called when handling this event, the first available device will be
automatically selected.
Due to the nature of bluetooth, scanning for devices when
`navigator.bluetooth.requestDevice` is called may take time and will cause
`select-bluetooth-device` to fire multiple times until `callback` is called
with either a device id or an empty string to cancel the request.
```javascript title='main.js' ```javascript title='main.js'
const { app, BrowserWindow } = require('electron') const { app, BrowserWindow } = require('electron')
let win = null let win = null
app.commandLine.appendSwitch('enable-experimental-web-platform-features')
app.whenReady().then(() => { app.whenReady().then(() => {
win = new BrowserWindow({ width: 800, height: 600 }) win = new BrowserWindow({ width: 800, height: 600 })
@ -800,6 +804,9 @@ app.whenReady().then(() => {
return device.deviceName === 'test' return device.deviceName === 'test'
}) })
if (!result) { if (!result) {
// The device wasn't found so we need to either wait longer (eg until the
// device is turned on) or cancel the request by calling the callback
// with an empty string.
callback('') callback('')
} else { } else {
callback(result.deviceId) callback(result.deviceId)

View file

@ -9,6 +9,7 @@
<h1>Web Bluetooth API</h1> <h1>Web Bluetooth API</h1>
<button id="clickme">Test Bluetooth</button> <button id="clickme">Test Bluetooth</button>
<button id="cancel">Cancel Bluetooth Request</button>
<p>Currently selected bluetooth device: <strong id="device-name""></strong></p> <p>Currently selected bluetooth device: <strong id="device-name""></strong></p>

View file

@ -1,6 +1,9 @@
const {app, BrowserWindow, ipcMain} = require('electron') const {app, BrowserWindow, ipcMain} = require('electron')
const path = require('path') const path = require('path')
let bluetoothPinCallback
let selectBluetoothCallback
function createWindow () { function createWindow () {
const mainWindow = new BrowserWindow({ const mainWindow = new BrowserWindow({
width: 800, width: 800,
@ -12,11 +15,23 @@ function createWindow () {
mainWindow.webContents.on('select-bluetooth-device', (event, deviceList, callback) => { mainWindow.webContents.on('select-bluetooth-device', (event, deviceList, callback) => {
event.preventDefault() event.preventDefault()
if (deviceList && deviceList.length > 0) { selectBluetoothCallback = callback
callback(deviceList[0].deviceId) 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. // Listen for a message from the renderer to get the response for the Bluetooth pairing.
ipcMain.on('bluetooth-pairing-response', (event, response) => { ipcMain.on('bluetooth-pairing-response', (event, response) => {
bluetoothPinCallback(response) bluetoothPinCallback(response)

View file

@ -1,6 +1,7 @@
const { contextBridge, ipcRenderer } = require('electron') const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI', { contextBridge.exposeInMainWorld('electronAPI', {
cancelBluetoothRequest: (callback) => ipcRenderer.send('cancel-bluetooth-request', callback),
bluetoothPairingRequest: (callback) => ipcRenderer.on('bluetooth-pairing-request', callback), bluetoothPairingRequest: (callback) => ipcRenderer.on('bluetooth-pairing-request', callback),
bluetoothPairingResponse: (response) => ipcRenderer.send('bluetooth-pairing-response', response) bluetoothPairingResponse: (response) => ipcRenderer.send('bluetooth-pairing-response', response)
}) })

View file

@ -7,6 +7,12 @@ async function testIt() {
document.getElementById('clickme').addEventListener('click',testIt) document.getElementById('clickme').addEventListener('click',testIt)
function cancelRequest() {
window.electronAPI.cancelBluetoothRequest()
}
document.getElementById('cancel').addEventListener('click', cancelRequest)
window.electronAPI.bluetoothPairingRequest((event, details) => { window.electronAPI.bluetoothPairingRequest((event, details) => {
const response = {} const response = {}

View file

@ -27,8 +27,6 @@ namespace electron {
namespace { namespace {
const int kMaxScanRetries = 5;
void OnDeviceChosen(const content::BluetoothChooser::EventHandler& handler, void OnDeviceChosen(const content::BluetoothChooser::EventHandler& handler,
const std::string& device_id) { const std::string& device_id) {
if (device_id.empty()) { if (device_id.empty()) {
@ -66,29 +64,15 @@ void BluetoothChooser::SetAdapterPresence(AdapterPresence presence) {
} }
void BluetoothChooser::ShowDiscoveryState(DiscoveryState state) { void BluetoothChooser::ShowDiscoveryState(DiscoveryState state) {
bool idle_state = false;
switch (state) { switch (state) {
case DiscoveryState::FAILED_TO_START: case DiscoveryState::FAILED_TO_START:
refreshing_ = false; refreshing_ = false;
event_handler_.Run(content::BluetoothChooserEvent::CANCELLED, ""); event_handler_.Run(content::BluetoothChooserEvent::CANCELLED, "");
break; return;
case DiscoveryState::IDLE: case DiscoveryState::IDLE:
refreshing_ = false; refreshing_ = false;
if (device_map_.empty()) { idle_state = true;
auto event = ++num_retries_ > kMaxScanRetries
? content::BluetoothChooserEvent::CANCELLED
: content::BluetoothChooserEvent::RESCAN;
event_handler_.Run(event, "");
} else {
bool prevent_default = api_web_contents_->Emit(
"select-bluetooth-device", GetDeviceList(),
base::BindOnce(&OnDeviceChosen, event_handler_));
if (!prevent_default) {
auto it = device_map_.begin();
auto device_id = it->first;
event_handler_.Run(content::BluetoothChooserEvent::SELECTED,
device_id);
}
}
break; break;
case DiscoveryState::DISCOVERING: case DiscoveryState::DISCOVERING:
// The first time this state fires is due to a rescan triggering so set a // The first time this state fires is due to a rescan triggering so set a
@ -101,6 +85,18 @@ void BluetoothChooser::ShowDiscoveryState(DiscoveryState state) {
} }
break; break;
} }
bool prevent_default =
api_web_contents_->Emit("select-bluetooth-device", GetDeviceList(),
base::BindOnce(&OnDeviceChosen, event_handler_));
if (!prevent_default && idle_state) {
if (device_map_.empty()) {
event_handler_.Run(content::BluetoothChooserEvent::CANCELLED, "");
} else {
auto it = device_map_.begin();
auto device_id = it->first;
event_handler_.Run(content::BluetoothChooserEvent::SELECTED, device_id);
}
}
} }
void BluetoothChooser::AddOrUpdateDevice(const std::string& device_id, void BluetoothChooser::AddOrUpdateDevice(const std::string& device_id,

View file

@ -44,7 +44,6 @@ class BluetoothChooser : public content::BluetoothChooser {
std::map<std::string, std::u16string> device_map_; std::map<std::string, std::u16string> device_map_;
api::WebContents* api_web_contents_; api::WebContents* api_web_contents_;
EventHandler event_handler_; EventHandler event_handler_;
int num_retries_ = 0;
bool refreshing_ = false; bool refreshing_ = false;
bool rescan_ = false; bool rescan_ = false;
}; };