From 70020275a7c47e1a4c5b0e739944ef34a2bfd280 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Sat, 1 Apr 2023 10:49:34 -0500 Subject: [PATCH] ble/alerts/Disconnected: better error message for disconnected WebBluetooth generally uses DomException with name NetworkError to mean that Bluetooth is disconnected, so we can use that to provide a helpful error message. --- CHANGELOG.md | 3 +++ src/ble/alerts/Disconnected.test.tsx | 19 +++++++++++++++++++ src/ble/alerts/Disconnected.tsx | 19 +++++++++++++++++++ src/ble/alerts/index.ts | 4 +++- src/ble/alerts/translations/en.json | 3 +++ src/hub/sagas.ts | 14 ++++++++++---- 6 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 src/ble/alerts/Disconnected.test.tsx create mode 100644 src/ble/alerts/Disconnected.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 33cf072e..81e9a1df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ ## [Unreleased] +### Changed +- Better error message when download and run fails due to disconnected hub. + ### Fixed - Fixed run button active while hub is disconnecting ([support#1021]). diff --git a/src/ble/alerts/Disconnected.test.tsx b/src/ble/alerts/Disconnected.test.tsx new file mode 100644 index 00000000..cc872083 --- /dev/null +++ b/src/ble/alerts/Disconnected.test.tsx @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2022-2023 The Pybricks Authors + +import { Toast } from '@blueprintjs/core'; +import { act } from '@testing-library/react'; +import React from 'react'; +import { testRender } from '../../../test'; +import { disconnected } from './Disconnected'; + +it('should dismiss when close is clicked', async () => { + const callback = jest.fn(); + const toast = disconnected(callback, undefined as never); + + const [user, message] = testRender(); + + await act(() => user.click(message.getByRole('button', { name: /close/i }))); + + expect(callback).toHaveBeenCalledWith('dismiss'); +}); diff --git a/src/ble/alerts/Disconnected.tsx b/src/ble/alerts/Disconnected.tsx new file mode 100644 index 00000000..93b43a2c --- /dev/null +++ b/src/ble/alerts/Disconnected.tsx @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2022-2023 The Pybricks Authors + +import { Intent } from '@blueprintjs/core'; +import React from 'react'; +import type { CreateToast } from '../../toasterTypes'; +import { useI18n } from './i18n'; + +const Disconnected: React.VoidFunctionComponent = () => { + const i18n = useI18n(); + return

{i18n.translate('disconnected.message')}

; +}; + +export const disconnected: CreateToast = (onAction) => ({ + message: , + icon: 'error', + intent: Intent.DANGER, + onDismiss: () => onAction('dismiss'), +}); diff --git a/src/ble/alerts/index.ts b/src/ble/alerts/index.ts index 22b7ffc0..18d7641a 100644 --- a/src/ble/alerts/index.ts +++ b/src/ble/alerts/index.ts @@ -1,7 +1,8 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2022 The Pybricks Authors +// Copyright (c) 2022-2023 The Pybricks Authors import { bluetoothNotAvailable } from './BluetoothNotAvailable'; +import { disconnected } from './Disconnected'; import { missingService } from './MissingService'; import { noGatt } from './NoGatt'; import { noHub } from './NoHub'; @@ -11,6 +12,7 @@ import { oldFirmware } from './OldFirmware'; // gathers all of the alert creation functions for passing up to the top level export default { bluetoothNotAvailable, + disconnected, missingService, noGatt, noHub, diff --git a/src/ble/alerts/translations/en.json b/src/ble/alerts/translations/en.json index 97c4da8b..3b12f341 100644 --- a/src/ble/alerts/translations/en.json +++ b/src/ble/alerts/translations/en.json @@ -29,5 +29,8 @@ "flashFirmware": { "label": "Update Pybricks firmware" } + }, + "disconnected": { + "message": "The hub is no longer connected. Please reconnect and try again." } } diff --git a/src/hub/sagas.ts b/src/hub/sagas.ts index 2bdc5d37..5c789ed5 100644 --- a/src/hub/sagas.ts +++ b/src/hub/sagas.ts @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2020-2022 The Pybricks Authors +// Copyright (c) 2020-2023 The Pybricks Authors import { SagaGenerator, @@ -326,9 +326,15 @@ function* handleDownloadAndRun(action: ReturnType): Gener console.error(err); } - yield* put( - alertsShowAlert('alerts', 'unexpectedError', { error: ensureError(err) }), - ); + if (err instanceof DOMException && err.name === 'NetworkError') { + yield* put(alertsShowAlert('ble', 'disconnected')); + } else { + yield* put( + alertsShowAlert('alerts', 'unexpectedError', { + error: ensureError(err), + }), + ); + } yield* put(didFailToFinishDownload()); }