Skip to content

Commit

Permalink
fix: allow cancelling of bluetooth requests (#37601)
Browse files Browse the repository at this point in the history
* fix: allow cancelling of bluetooth requests

allows cancelling of bluetooth requests when no devices present

* docs: update docs to reflect how bluetooth works.
  • Loading branch information
jkleinsc committed Mar 27, 2023
1 parent 42e7cd9 commit 6a6908c
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 31 deletions.
23 changes: 15 additions & 8 deletions docs/api/web-contents.md
Original file line number Diff line number Diff line change
Expand Up @@ -777,20 +777,24 @@ Returns:
* `callback` Function
* `deviceId` string

Emitted when bluetooth device needs to be selected on call to
`navigator.bluetooth.requestDevice`. To use `navigator.bluetooth` api
`webBluetooth` should be enabled. If `event.preventDefault` is not called,
first available device will be selected. `callback` should be called with
`deviceId` to be selected, passing empty string to `callback` will
cancel the request.
Emitted when a bluetooth device needs to be selected when a call to
`navigator.bluetooth.requestDevice` is made. `callback` should be called with
the `deviceId` of the device to be selected. Passing an 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'
const { app, BrowserWindow } = require('electron')

let win = null
app.commandLine.appendSwitch('enable-experimental-web-platform-features')

app.whenReady().then(() => {
win = new BrowserWindow({ width: 800, height: 600 })
Expand All @@ -800,6 +804,9 @@ app.whenReady().then(() => {
return device.deviceName === 'test'
})
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('')
} else {
callback(result.deviceId)
Expand Down
1 change: 1 addition & 0 deletions docs/fiddles/features/web-bluetooth/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<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>

Expand Down
21 changes: 18 additions & 3 deletions docs/fiddles/features/web-bluetooth/main.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
const {app, BrowserWindow, ipcMain} = require('electron')
const path = require('path')

let bluetoothPinCallback
let selectBluetoothCallback

function createWindow () {
const mainWindow = new BrowserWindow({
width: 800,
Expand All @@ -12,10 +15,22 @@ function createWindow () {

mainWindow.webContents.on('select-bluetooth-device', (event, deviceList, callback) => {
event.preventDefault()
if (deviceList && deviceList.length > 0) {
callback(deviceList[0].deviceId)
}
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) => {
Expand Down
1 change: 1 addition & 0 deletions docs/fiddles/features/web-bluetooth/preload.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI', {
cancelBluetoothRequest: (callback) => ipcRenderer.send('cancel-bluetooth-request', callback),
bluetoothPairingRequest: (callback) => ipcRenderer.on('bluetooth-pairing-request', callback),
bluetoothPairingResponse: (response) => ipcRenderer.send('bluetooth-pairing-response', response)
})
6 changes: 6 additions & 0 deletions docs/fiddles/features/web-bluetooth/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ async function testIt() {

document.getElementById('clickme').addEventListener('click',testIt)

function cancelRequest() {
window.electronAPI.cancelBluetoothRequest()
}

document.getElementById('cancel').addEventListener('click', cancelRequest)

window.electronAPI.bluetoothPairingRequest((event, details) => {
const response = {}

Expand Down
34 changes: 15 additions & 19 deletions shell/browser/lib/bluetooth_chooser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ namespace electron {

namespace {

const int kMaxScanRetries = 5;

void OnDeviceChosen(const content::BluetoothChooser::EventHandler& handler,
const std::string& device_id) {
if (device_id.empty()) {
Expand Down Expand Up @@ -66,29 +64,15 @@ void BluetoothChooser::SetAdapterPresence(AdapterPresence presence) {
}

void BluetoothChooser::ShowDiscoveryState(DiscoveryState state) {
bool idle_state = false;
switch (state) {
case DiscoveryState::FAILED_TO_START:
refreshing_ = false;
event_handler_.Run(content::BluetoothChooserEvent::CANCELLED, "");
break;
return;
case DiscoveryState::IDLE:
refreshing_ = false;
if (device_map_.empty()) {
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);
}
}
idle_state = true;
break;
case DiscoveryState::DISCOVERING:
// The first time this state fires is due to a rescan triggering so set a
Expand All @@ -101,6 +85,18 @@ void BluetoothChooser::ShowDiscoveryState(DiscoveryState state) {
}
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,
Expand Down
1 change: 0 additions & 1 deletion shell/browser/lib/bluetooth_chooser.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ class BluetoothChooser : public content::BluetoothChooser {
std::map<std::string, std::u16string> device_map_;
api::WebContents* api_web_contents_;
EventHandler event_handler_;
int num_retries_ = 0;
bool refreshing_ = false;
bool rescan_ = false;
};
Expand Down

0 comments on commit 6a6908c

Please sign in to comment.