From 03a911e520e39437bda734f677dc7705eb21afb7 Mon Sep 17 00:00:00 2001 From: John Kleinschmidt Date: Thu, 18 Aug 2022 10:43:18 -0400 Subject: [PATCH 1/7] feat: add bluetooth pairing handler --- docs/api/session.md | 62 ++++++++++++++++ docs/fiddles/features/web-bluetooth/main.js | 25 +++++++ docs/tutorial/devices.md | 4 ++ shell/browser/api/electron_api_session.cc | 14 ++++ shell/browser/api/electron_api_session.h | 2 + .../bluetooth/electron_bluetooth_delegate.cc | 71 ++++++++++++++++++- .../bluetooth/electron_bluetooth_delegate.h | 8 +++ shell/browser/electron_permission_manager.cc | 17 +++++ shell/browser/electron_permission_manager.h | 9 +++ 9 files changed, 209 insertions(+), 3 deletions(-) diff --git a/docs/api/session.md b/docs/api/session.md index c691f237e7260..a6d47b332adef 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -769,6 +769,68 @@ app.whenReady().then(() => { }) ``` +#### `ses.setBluetoothPairingHandler(handler)` _Windows_ _Linux_ + +* `handler` Function | null + * `details` Object + * `deviceId` string + * `pairingKind` string - The type of pairing prompt being requested. + One of the following values: + * `confirm` + This prompt is requesting confirmation that the bluetooth device should + be paired. + * `confirmPin` + This prompt is requesting confirmation that the provided pin matches the + pin displayed on the device. + * `providePin` + This prompt is requesting that a pin be provided for the device. + * `frame` [WebFrameMain](web-frame-main.md) + * `pin` string - If the `pairingKind` is `confirmPin`, this value will be + the pin value to verify. + * `callback` Function + * `response` Object + * `confirmed` boolean - `false` should be passed in if the dialog is canceled. + If the `pairingKind` is `confirm` or `confirmPin`, this value should indicate + if the pairing is confirmed. If the `pairingKind` is `providePin` the value + should be `true` when a value is provided. + * `pin` string | null (optional) - When the `pairingKind` is `providePin` + this value should be the required pin for the bluetooth device. + +Sets the handler which can be used to respond when a bluetooth device requires +a response to pairing. This handler allows developers to handle devices that +require additional validation for pairing. +To clear the handler, call `setBluetoothPairingHandler(null)`. + +```javascript + +const { session } = require('electron') + +session.defaultSession.setBluetoothPairingHandler((details, callback) => { + const response = {} + + switch (details.pairingKind) { + case 'confirm': { + response.confirmed = confirm(`Do you want to connect to device ${details.deviceId}`) + break + } + case 'confirmPin': { + response.confirmed = confirm(`Does the pin ${details.pin} match the pin displayed on device ${details.deviceId}?`) + break + } + case 'providePin': { + const pin = prompt(`Please provide a pin for ${details.deviceId}`) + if (pin) { + response.pin = pin + response.confirmed = true + } else { + response.confirmed = false + } + } + } + callback(response) +}) +``` + #### `ses.clearHostResolverCache()` Returns `Promise` - Resolves when the operation is complete. diff --git a/docs/fiddles/features/web-bluetooth/main.js b/docs/fiddles/features/web-bluetooth/main.js index b3cc55a438198..0ff72bb6be48f 100644 --- a/docs/fiddles/features/web-bluetooth/main.js +++ b/docs/fiddles/features/web-bluetooth/main.js @@ -14,6 +14,31 @@ function createWindow () { } }) + mainWindow.webContents.session.setBluetoothPairingHandler((details, callback) => { + const response = {} + + switch (details.pairingKind) { + case 'confirm': { + response.confirmed = confirm(`Do you want to connect to device ${details.deviceId}`) + break + } + case 'confirmPin': { + response.confirmed = confirm(`Does the pin ${details.pin} match the pin displayed on device ${details.deviceId}?`) + break + } + case 'providePin': { + const pin = prompt(`Please provide a pin for ${details.deviceId}`) + if (pin) { + response.pin = pin + response.confirmed = true + } else { + response.confirmed = false + } + } + } + callback(response) + }) + mainWindow.loadFile('index.html') } diff --git a/docs/tutorial/devices.md b/docs/tutorial/devices.md index cba807891f07b..6f11fec760208 100644 --- a/docs/tutorial/devices.md +++ b/docs/tutorial/devices.md @@ -16,6 +16,10 @@ with bluetooth devices. In order to use this API in Electron, developers will need to handle the [`select-bluetooth-device` event on the webContents](../api/web-contents.md#event-select-bluetooth-device) associated with the device request. +Additionally, [`ses.setBluetoothPairingHandler(handler)`](../api/session.md#sessetbluetoothpairinghandlerhandler-windows-linux) +can be used to handle pairing to bluetooth devices on Windows or Linux when +additional validation such as a pin is needed. + ### Example This example demonstrates an Electron application that automatically selects diff --git a/shell/browser/api/electron_api_session.cc b/shell/browser/api/electron_api_session.cc index a5d59b2d2b2d8..340adbd1a65a3 100644 --- a/shell/browser/api/electron_api_session.cc +++ b/shell/browser/api/electron_api_session.cc @@ -655,6 +655,18 @@ void Session::SetDevicePermissionHandler(v8::Local val, permission_manager->SetDevicePermissionHandler(handler); } +void Session::SetBluetoothPairingHandler(v8::Local val, + gin::Arguments* args) { + ElectronPermissionManager::BluetoothPairingHandler handler; + if (!(val->IsNull() || gin::ConvertFromV8(args->isolate(), val, &handler))) { + args->ThrowTypeError("Must pass null or function"); + return; + } + auto* permission_manager = static_cast( + browser_context()->GetPermissionControllerDelegate()); + permission_manager->SetBluetoothPairingHandler(handler); +} + v8::Local Session::ClearHostResolverCache(gin::Arguments* args) { v8::Isolate* isolate = args->isolate(); gin_helper::Promise promise(isolate); @@ -1204,6 +1216,8 @@ gin::ObjectTemplateBuilder Session::GetObjectTemplateBuilder( &Session::SetPermissionCheckHandler) .SetMethod("setDevicePermissionHandler", &Session::SetDevicePermissionHandler) + .SetMethod("setBluetoothPairingHandler", + &Session::SetBluetoothPairingHandler) .SetMethod("clearHostResolverCache", &Session::ClearHostResolverCache) .SetMethod("clearAuthCache", &Session::ClearAuthCache) .SetMethod("allowNTLMCredentialsForDomains", diff --git a/shell/browser/api/electron_api_session.h b/shell/browser/api/electron_api_session.h index c5f21920dcc6e..9507d706a4f20 100644 --- a/shell/browser/api/electron_api_session.h +++ b/shell/browser/api/electron_api_session.h @@ -105,6 +105,8 @@ class Session : public gin::Wrappable, gin::Arguments* args); void SetDevicePermissionHandler(v8::Local val, gin::Arguments* args); + void SetBluetoothPairingHandler(v8::Local val, + gin::Arguments* args); v8::Local ClearHostResolverCache(gin::Arguments* args); v8::Local ClearAuthCache(); void AllowNTLMCredentialsForDomains(const std::string& domains); diff --git a/shell/browser/bluetooth/electron_bluetooth_delegate.cc b/shell/browser/bluetooth/electron_bluetooth_delegate.cc index 340cb5a4a35f2..982f6b5cc6071 100644 --- a/shell/browser/bluetooth/electron_bluetooth_delegate.cc +++ b/shell/browser/bluetooth/electron_bluetooth_delegate.cc @@ -7,6 +7,7 @@ #include #include +#include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" #include "content/public/browser/render_frame_host.h" @@ -14,7 +15,10 @@ #include "device/bluetooth/bluetooth_device.h" #include "device/bluetooth/public/cpp/bluetooth_uuid.h" #include "shell/browser/api/electron_api_web_contents.h" +#include "shell/browser/electron_permission_manager.h" #include "shell/browser/lib/bluetooth_chooser.h" +#include "shell/common/gin_converters/frame_converter.h" +#include "shell/common/gin_helper/dictionary.h" #include "third_party/blink/public/common/bluetooth/web_bluetooth_device_id.h" #include "third_party/blink/public/mojom/bluetooth/web_bluetooth.mojom.h" @@ -23,6 +27,28 @@ using content::RenderFrameHost; using content::WebContents; using device::BluetoothUUID; +namespace gin { + +template <> +struct Converter { + static v8::Local ToV8( + v8::Isolate* isolate, + content::BluetoothDelegate::PairingKind paring_kind) { + switch (paring_kind) { + case content::BluetoothDelegate::PairingKind::kConfirmOnly: + return StringToV8(isolate, "confirm"); + case content::BluetoothDelegate::PairingKind::kConfirmPinMatch: + return StringToV8(isolate, "confirmPin"); + case content::BluetoothDelegate::PairingKind::kProvidePin: + return StringToV8(isolate, "providePin"); + default: + return StringToV8(isolate, "unknown"); + } + } +}; + +} // namespace gin + namespace electron { ElectronBluetoothDelegate::ElectronBluetoothDelegate() = default; @@ -136,9 +162,48 @@ void ElectronBluetoothDelegate::ShowDevicePairPrompt( PairPromptCallback callback, PairingKind pairing_kind, const absl::optional& pin) { - NOTIMPLEMENTED(); - std::move(callback).Run(BluetoothDelegate::PairPromptResult( - BluetoothDelegate::PairPromptStatus::kCancelled)); + pair_prompt_callback_ = std::move(callback); + + auto* web_contents = content::WebContents::FromRenderFrameHost(frame); + if (web_contents) { + auto* permission_manager = static_cast( + web_contents->GetBrowserContext()->GetPermissionControllerDelegate()); + + v8::Isolate* isolate = JavascriptEnvironment::GetIsolate(); + v8::HandleScope scope(isolate); + gin_helper::Dictionary details = + gin_helper::Dictionary::CreateEmpty(isolate); + details.Set("deviceId", device_identifier); + details.Set("pairingKind", pairing_kind); + details.SetGetter("frame", frame); + if (pin.has_value()) { + details.Set("pin", pin.value()); + } + + permission_manager->CheckBluetoothDevicePair( + details, base::AdaptCallbackForRepeating(base::BindOnce( + &ElectronBluetoothDelegate::OnDevicePairPromptResponse, + weak_factory_.GetWeakPtr()))); + } +} + +void ElectronBluetoothDelegate::OnDevicePairPromptResponse( + base::Value::Dict response) { + BluetoothDelegate::PairPromptResult result; + if (response.FindBool("confirmed").value_or(false)) { + result.result_code = BluetoothDelegate::PairPromptStatus::kSuccess; + } else { + result.result_code = BluetoothDelegate::PairPromptStatus::kCancelled; + } + + std::string* pin = response.FindString("pin"); + if (pin) { + std::u16string trimmed_input; + base::TrimWhitespace(base::UTF8ToUTF16(*pin), base::TRIM_ALL, + &trimmed_input); + result.pin = base::UTF16ToUTF8(trimmed_input); + } + std::move(pair_prompt_callback_).Run(result); } } // namespace electron diff --git a/shell/browser/bluetooth/electron_bluetooth_delegate.h b/shell/browser/bluetooth/electron_bluetooth_delegate.h index df4f07703e815..a007c414439f3 100644 --- a/shell/browser/bluetooth/electron_bluetooth_delegate.h +++ b/shell/browser/bluetooth/electron_bluetooth_delegate.h @@ -9,6 +9,7 @@ #include #include +#include "base/memory/weak_ptr.h" #include "base/observer_list.h" #include "base/scoped_observation.h" #include "content/public/browser/bluetooth_delegate.h" @@ -91,6 +92,13 @@ class ElectronBluetoothDelegate : public content::BluetoothDelegate { void AddFramePermissionObserver(FramePermissionObserver* observer) override; void RemoveFramePermissionObserver( FramePermissionObserver* observer) override; + + private: + void OnDevicePairPromptResponse(base::Value::Dict response); + + PairPromptCallback pair_prompt_callback_; + + base::WeakPtrFactory weak_factory_{this}; }; } // namespace electron diff --git a/shell/browser/electron_permission_manager.cc b/shell/browser/electron_permission_manager.cc index 8b3d4d50df02f..995a704bb6503 100644 --- a/shell/browser/electron_permission_manager.cc +++ b/shell/browser/electron_permission_manager.cc @@ -130,6 +130,11 @@ void ElectronPermissionManager::SetDevicePermissionHandler( device_permission_handler_ = handler; } +void ElectronPermissionManager::SetBluetoothPairingHandler( + const BluetoothPairingHandler& handler) { + bluetooth_pairing_handler_ = handler; +} + void ElectronPermissionManager::RequestPermission( blink::PermissionType permission, content::RenderFrameHost* render_frame_host, @@ -276,6 +281,18 @@ ElectronPermissionManager::SubscribePermissionStatusChange( void ElectronPermissionManager::UnsubscribePermissionStatusChange( SubscriptionId id) {} +void ElectronPermissionManager::CheckBluetoothDevicePair( + gin_helper::Dictionary details, + PairCallback pair_callback) const { + if (bluetooth_pairing_handler_.is_null()) { + base::Value::Dict response; + response.Set("confirmed", false); + std::move(pair_callback).Run(std::move(response)); + } else { + bluetooth_pairing_handler_.Run(details, std::move(pair_callback)); + } +} + bool ElectronPermissionManager::CheckPermissionWithDetails( blink::PermissionType permission, content::RenderFrameHost* render_frame_host, diff --git a/shell/browser/electron_permission_manager.h b/shell/browser/electron_permission_manager.h index 00cbb81e9f733..3aa980ea22d71 100644 --- a/shell/browser/electron_permission_manager.h +++ b/shell/browser/electron_permission_manager.h @@ -13,6 +13,7 @@ #include "content/public/browser/permission_controller_delegate.h" #include "gin/dictionary.h" #include "shell/browser/electron_browser_context.h" +#include "shell/common/gin_helper/dictionary.h" namespace base { class Value; @@ -38,6 +39,7 @@ class ElectronPermissionManager : public content::PermissionControllerDelegate { base::OnceCallback; using StatusesCallback = base::OnceCallback&)>; + using PairCallback = base::OnceCallback; using RequestHandler = base::RepeatingCallback&)>; + using BluetoothPairingHandler = + base::RepeatingCallback; // Handler to dispatch permission requests in JS. void SetPermissionRequestHandler(const RequestHandler& handler); void SetPermissionCheckHandler(const CheckHandler& handler); void SetDevicePermissionHandler(const DeviceCheckHandler& handler); + void SetBluetoothPairingHandler(const BluetoothPairingHandler& handler); // content::PermissionControllerDelegate: void RequestPermission(blink::PermissionType permission, @@ -81,6 +86,9 @@ class ElectronPermissionManager : public content::PermissionControllerDelegate { base::Value::Dict details, StatusesCallback callback); + void CheckBluetoothDevicePair(gin_helper::Dictionary details, + PairCallback pair_callback) const; + bool CheckPermissionWithDetails(blink::PermissionType permission, content::RenderFrameHost* render_frame_host, const GURL& requesting_origin, @@ -147,6 +155,7 @@ class ElectronPermissionManager : public content::PermissionControllerDelegate { RequestHandler request_handler_; CheckHandler check_handler_; DeviceCheckHandler device_permission_handler_; + BluetoothPairingHandler bluetooth_pairing_handler_; PendingRequestsMap pending_requests_; }; From 678b170789a164c7ca4905f2d952739066d3d71a Mon Sep 17 00:00:00 2001 From: John Kleinschmidt Date: Tue, 20 Sep 2022 15:30:55 -0400 Subject: [PATCH 2/7] Update docs/api/session.md Co-authored-by: Charles Kerr --- docs/api/session.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/api/session.md b/docs/api/session.md index a6d47b332adef..c3593ab5d911a 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -785,8 +785,7 @@ app.whenReady().then(() => { * `providePin` This prompt is requesting that a pin be provided for the device. * `frame` [WebFrameMain](web-frame-main.md) - * `pin` string - If the `pairingKind` is `confirmPin`, this value will be - the pin value to verify. + * `pin` string (optional) - The pin value to verify if `pairingKind` is `confirmPin`. * `callback` Function * `response` Object * `confirmed` boolean - `false` should be passed in if the dialog is canceled. From 3994d0928491b05c65b522bcc35ab15bc1dc4f59 Mon Sep 17 00:00:00 2001 From: John Kleinschmidt Date: Tue, 20 Sep 2022 15:31:26 -0400 Subject: [PATCH 3/7] Update docs/api/session.md Co-authored-by: Charles Kerr --- docs/api/session.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/api/session.md b/docs/api/session.md index c3593ab5d911a..b3b01bb1593c0 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -795,9 +795,9 @@ app.whenReady().then(() => { * `pin` string | null (optional) - When the `pairingKind` is `providePin` this value should be the required pin for the bluetooth device. -Sets the handler which can be used to respond when a bluetooth device requires -a response to pairing. This handler allows developers to handle devices that -require additional validation for pairing. +Sets a handler to respond to bluetooth pairing requests. This handler +allows developers to handle devices that require additional validation +before pairing. To clear the handler, call `setBluetoothPairingHandler(null)`. ```javascript From 7fd109e7fe9adb3cb2d45f308154c7ad4aaed17b Mon Sep 17 00:00:00 2001 From: John Kleinschmidt Date: Tue, 20 Sep 2022 15:34:15 -0400 Subject: [PATCH 4/7] docs: update based on review --- docs/api/session.md | 3 ++- filenames.auto.gni | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/api/session.md b/docs/api/session.md index b3b01bb1593c0..ba12b42ca39e4 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -797,7 +797,8 @@ app.whenReady().then(() => { Sets a handler to respond to bluetooth pairing requests. This handler allows developers to handle devices that require additional validation -before pairing. +before pairing. When a handler is not defined, any pairing on Linux or Windows +that requires additional validation will be automatically cancelled. To clear the handler, call `setBluetoothPairingHandler(null)`. ```javascript diff --git a/filenames.auto.gni b/filenames.auto.gni index 2b865962a3885..57d9f4d1384ee 100644 --- a/filenames.auto.gni +++ b/filenames.auto.gni @@ -142,6 +142,7 @@ auto_filenames = { "lib/common/define-properties.ts", "lib/common/ipc-messages.ts", "lib/common/web-view-methods.ts", + "lib/common/webpack-globals-provider.ts", "lib/renderer/api/context-bridge.ts", "lib/renderer/api/crash-reporter.ts", "lib/renderer/api/ipc-renderer.ts", From f47c49e64ee9d5d9334468630b01924a60f2e651 Mon Sep 17 00:00:00 2001 From: John Kleinschmidt Date: Wed, 21 Sep 2022 10:13:58 -0400 Subject: [PATCH 5/7] Apply suggestions from code review Co-authored-by: Erick Zhao Co-authored-by: Charles Kerr --- docs/api/session.md | 4 ++-- .../browser/bluetooth/electron_bluetooth_delegate.cc | 11 +++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/api/session.md b/docs/api/session.md index ba12b42ca39e4..8cd6226a8baf6 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -777,10 +777,10 @@ app.whenReady().then(() => { * `pairingKind` string - The type of pairing prompt being requested. One of the following values: * `confirm` - This prompt is requesting confirmation that the bluetooth device should + This prompt is requesting confirmation that the Bluetooth device should be paired. * `confirmPin` - This prompt is requesting confirmation that the provided pin matches the + This prompt is requesting confirmation that the provided PIN matches the pin displayed on the device. * `providePin` This prompt is requesting that a pin be provided for the device. diff --git a/shell/browser/bluetooth/electron_bluetooth_delegate.cc b/shell/browser/bluetooth/electron_bluetooth_delegate.cc index 982f6b5cc6071..241af48b8adeb 100644 --- a/shell/browser/bluetooth/electron_bluetooth_delegate.cc +++ b/shell/browser/bluetooth/electron_bluetooth_delegate.cc @@ -33,8 +33,8 @@ template <> struct Converter { static v8::Local ToV8( v8::Isolate* isolate, - content::BluetoothDelegate::PairingKind paring_kind) { - switch (paring_kind) { + content::BluetoothDelegate::PairingKind pairing_kind) { + switch (pairing_kind) { case content::BluetoothDelegate::PairingKind::kConfirmOnly: return StringToV8(isolate, "confirm"); case content::BluetoothDelegate::PairingKind::kConfirmPinMatch: @@ -196,11 +196,10 @@ void ElectronBluetoothDelegate::OnDevicePairPromptResponse( result.result_code = BluetoothDelegate::PairPromptStatus::kCancelled; } - std::string* pin = response.FindString("pin"); + const std::string* pin = response.FindString("pin"); if (pin) { - std::u16string trimmed_input; - base::TrimWhitespace(base::UTF8ToUTF16(*pin), base::TRIM_ALL, - &trimmed_input); + std::u16string trimmed_input = base::UTF8ToUTF16(*pin); + base::TrimWhitespace(trimmed_input, base::TRIM_ALL, &trimmed_input); result.pin = base::UTF16ToUTF8(trimmed_input); } std::move(pair_prompt_callback_).Run(result); From 41b6f58cafcc43b1992b5b0edc45038a843e3b0b Mon Sep 17 00:00:00 2001 From: John Kleinschmidt Date: Wed, 21 Sep 2022 12:08:52 -0400 Subject: [PATCH 6/7] chore: update docs per review --- docs/api/session.md | 53 ++++++++++--------- docs/fiddles/features/web-bluetooth/main.js | 38 +++++-------- .../fiddles/features/web-bluetooth/preload.js | 6 +++ .../features/web-bluetooth/renderer.js | 28 +++++++++- 4 files changed, 75 insertions(+), 50 deletions(-) create mode 100644 docs/fiddles/features/web-bluetooth/preload.js diff --git a/docs/api/session.md b/docs/api/session.md index 8cd6226a8baf6..ee1c3e7a8b347 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -793,41 +793,44 @@ app.whenReady().then(() => { if the pairing is confirmed. If the `pairingKind` is `providePin` the value should be `true` when a value is provided. * `pin` string | null (optional) - When the `pairingKind` is `providePin` - this value should be the required pin for the bluetooth device. + this value should be the required pin for the Bluetooth device. -Sets a handler to respond to bluetooth pairing requests. This handler +Sets a handler to respond to Bluetooth pairing requests. This handler allows developers to handle devices that require additional validation before pairing. When a handler is not defined, any pairing on Linux or Windows that requires additional validation will be automatically cancelled. -To clear the handler, call `setBluetoothPairingHandler(null)`. +macOS does not require a handler because macOS handles the pairing +automatically. To clear the handler, call `setBluetoothPairingHandler(null)`. ```javascript -const { session } = require('electron') +const { app, BrowserWindow, ipcMain, session } = require('electron') -session.defaultSession.setBluetoothPairingHandler((details, callback) => { - const response = {} +let bluetoothPinCallback = null - switch (details.pairingKind) { - case 'confirm': { - response.confirmed = confirm(`Do you want to connect to device ${details.deviceId}`) - break - } - case 'confirmPin': { - response.confirmed = confirm(`Does the pin ${details.pin} match the pin displayed on device ${details.deviceId}?`) - break - } - case 'providePin': { - const pin = prompt(`Please provide a pin for ${details.deviceId}`) - if (pin) { - response.pin = pin - response.confirmed = true - } else { - response.confirmed = false - } +function createWindow () { + const mainWindow = new BrowserWindow({ + webPreferences: { + preload: path.join(__dirname, 'preload.js') } - } - callback(response) + }) +} + +// Listen for an IPC 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 IPC message to the renderer to prompt the user to confirm the pairing. + // Note that this will require logic in the renderer to handle this message and + // display a prompt to the user. + mainWindow.webContents.send('bluetooth-pairing-request', details) +}) + +app.whenReady().then(() => { + createWindow() }) ``` diff --git a/docs/fiddles/features/web-bluetooth/main.js b/docs/fiddles/features/web-bluetooth/main.js index 0ff72bb6be48f..5d560579cc05f 100644 --- a/docs/fiddles/features/web-bluetooth/main.js +++ b/docs/fiddles/features/web-bluetooth/main.js @@ -1,10 +1,13 @@ -const {app, BrowserWindow} = require('electron') +const {app, BrowserWindow, ipcMain} = require('electron') const path = require('path') function createWindow () { const mainWindow = new BrowserWindow({ width: 800, - height: 600 + height: 600, + webPreferences: { + preload: path.join(__dirname, 'preload.js') + } }) mainWindow.webContents.on('select-bluetooth-device', (event, deviceList, callback) => { @@ -14,29 +17,16 @@ function createWindow () { } }) + // 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) => { - const response = {} - - switch (details.pairingKind) { - case 'confirm': { - response.confirmed = confirm(`Do you want to connect to device ${details.deviceId}`) - break - } - case 'confirmPin': { - response.confirmed = confirm(`Does the pin ${details.pin} match the pin displayed on device ${details.deviceId}?`) - break - } - case 'providePin': { - const pin = prompt(`Please provide a pin for ${details.deviceId}`) - if (pin) { - response.pin = pin - response.confirmed = true - } else { - response.confirmed = false - } - } - } - callback(response) + + 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') diff --git a/docs/fiddles/features/web-bluetooth/preload.js b/docs/fiddles/features/web-bluetooth/preload.js new file mode 100644 index 0000000000000..0732ea315ff7f --- /dev/null +++ b/docs/fiddles/features/web-bluetooth/preload.js @@ -0,0 +1,6 @@ +const { contextBridge, ipcRenderer } = require('electron') + +contextBridge.exposeInMainWorld('electronAPI', { + bluetoothPairingRequest: (callback) => ipcRenderer.on('bluetooth-pairing-request', callback), + bluetoothPairingResponse: (response) => ipcRenderer.send('bluetooth-pairing-respnse', response) +}) \ No newline at end of file diff --git a/docs/fiddles/features/web-bluetooth/renderer.js b/docs/fiddles/features/web-bluetooth/renderer.js index e5830955599af..080fb6105b287 100644 --- a/docs/fiddles/features/web-bluetooth/renderer.js +++ b/docs/fiddles/features/web-bluetooth/renderer.js @@ -5,4 +5,30 @@ async function testIt() { document.getElementById('device-name').innerHTML = device.name || `ID: ${device.id}` } -document.getElementById('clickme').addEventListener('click',testIt) \ No newline at end of file +document.getElementById('clickme').addEventListener('click',testIt) + +window.electronAPI.bluetoothPairingRequest((event, details) => { + const response = {} + + switch (details.pairingKind) { + case 'confirm': { + response.confirmed = confirm(`Do you want to connect to device ${details.deviceId}?`) + break + } + case 'confirmPin': { + response.confirmed = confirm(`Does the pin ${details.pin} match the pin displayed on device ${details.deviceId}?`) + break + } + case 'providePin': { + const pin = prompt(`Please provide a pin for ${details.deviceId}.`) + if (pin) { + response.pin = pin + response.confirmed = true + } else { + response.confirmed = false + } + } + } + + window.electronAPI.bluetoothPairingResponse(response) +}) \ No newline at end of file From d610b18db65a64207d088472ebdb90e0ee051690 Mon Sep 17 00:00:00 2001 From: John Kleinschmidt Date: Wed, 21 Sep 2022 12:46:28 -0400 Subject: [PATCH 7/7] chore: cleanup callback per review --- shell/browser/bluetooth/electron_bluetooth_delegate.cc | 7 +++---- shell/browser/bluetooth/electron_bluetooth_delegate.h | 5 ++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/shell/browser/bluetooth/electron_bluetooth_delegate.cc b/shell/browser/bluetooth/electron_bluetooth_delegate.cc index 241af48b8adeb..6cfc775041553 100644 --- a/shell/browser/bluetooth/electron_bluetooth_delegate.cc +++ b/shell/browser/bluetooth/electron_bluetooth_delegate.cc @@ -162,8 +162,6 @@ void ElectronBluetoothDelegate::ShowDevicePairPrompt( PairPromptCallback callback, PairingKind pairing_kind, const absl::optional& pin) { - pair_prompt_callback_ = std::move(callback); - auto* web_contents = content::WebContents::FromRenderFrameHost(frame); if (web_contents) { auto* permission_manager = static_cast( @@ -183,11 +181,12 @@ void ElectronBluetoothDelegate::ShowDevicePairPrompt( permission_manager->CheckBluetoothDevicePair( details, base::AdaptCallbackForRepeating(base::BindOnce( &ElectronBluetoothDelegate::OnDevicePairPromptResponse, - weak_factory_.GetWeakPtr()))); + weak_factory_.GetWeakPtr(), std::move(callback)))); } } void ElectronBluetoothDelegate::OnDevicePairPromptResponse( + PairPromptCallback callback, base::Value::Dict response) { BluetoothDelegate::PairPromptResult result; if (response.FindBool("confirmed").value_or(false)) { @@ -202,7 +201,7 @@ void ElectronBluetoothDelegate::OnDevicePairPromptResponse( base::TrimWhitespace(trimmed_input, base::TRIM_ALL, &trimmed_input); result.pin = base::UTF16ToUTF8(trimmed_input); } - std::move(pair_prompt_callback_).Run(result); + std::move(callback).Run(result); } } // namespace electron diff --git a/shell/browser/bluetooth/electron_bluetooth_delegate.h b/shell/browser/bluetooth/electron_bluetooth_delegate.h index a007c414439f3..8328f45636ad3 100644 --- a/shell/browser/bluetooth/electron_bluetooth_delegate.h +++ b/shell/browser/bluetooth/electron_bluetooth_delegate.h @@ -94,9 +94,8 @@ class ElectronBluetoothDelegate : public content::BluetoothDelegate { FramePermissionObserver* observer) override; private: - void OnDevicePairPromptResponse(base::Value::Dict response); - - PairPromptCallback pair_prompt_callback_; + void OnDevicePairPromptResponse(PairPromptCallback callback, + base::Value::Dict response); base::WeakPtrFactory weak_factory_{this}; };