Skip to content

Commit b5a624c

Browse files
committed
frontend/bb02-settings: add item to enable/disable Bluetooth
1 parent b7505b4 commit b5a624c

File tree

5 files changed

+137
-8
lines changed

5 files changed

+137
-8
lines changed

backend/devices/bitbox02/handlers/handlers.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ type BitBox02 interface {
5555
GotoStartupSettings() error
5656
RootFingerprint() ([]byte, error)
5757
BIP85AppBip39() error
58+
BluetoothToggleEnabled() error
5859
}
5960

6061
// Handlers provides a web API to the Bitbox.
@@ -92,6 +93,7 @@ func NewHandlers(
9293
handleFunc("/goto-startup-settings", handlers.postGotoStartupSettings).Methods("POST")
9394
handleFunc("/root-fingerprint", handlers.getRootFingerprint).Methods("GET")
9495
handleFunc("/invoke-bip85", handlers.postInvokeBIP85Handler).Methods("POST")
96+
handleFunc("/bluetooth/toggle-enabled", handlers.postBluetoothToggleEnabled).Methods("POST")
9597
return handlers
9698
}
9799

@@ -381,3 +383,11 @@ func (handlers *Handlers) postInvokeBIP85Handler(_ *http.Request) interface{} {
381383
}
382384
return map[string]interface{}{"success": true}
383385
}
386+
387+
func (handlers *Handlers) postBluetoothToggleEnabled(_ *http.Request) interface{} {
388+
err := handlers.device.BluetoothToggleEnabled()
389+
if err != nil {
390+
return maybeBB02Err(err, handlers.log)
391+
}
392+
return map[string]interface{}{"success": true}
393+
}

frontends/web/src/api/bitbox02.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,3 +226,7 @@ export const attestationCheckDone = (
226226
): TUnsubscribe => {
227227
return subscribeEndpoint(`devices/bitbox02/${deviceID}/attestationCheckDone`, cb);
228228
};
229+
230+
export const bluetoothToggleEnabled = (deviceID: string): Promise<SuccessResponse | FailResponse> => {
231+
return apiPost(`devices/bitbox02/${deviceID}/bluetooth/toggle-enabled`);
232+
};

frontends/web/src/locales/en/app.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,13 @@
191191
"followInstructionsMnemonicTitle": "Restore from recovery words"
192192
},
193193
"bitbox02Settings": {
194+
"bluetoothToggleEnabled": {
195+
"alertDisabled": "Bluetooth is now disabled on your BitBox. If you want to connect with iPhones on the future, you will need to re-enable it.",
196+
"alertEnabled": "Bluetooth is now enabled.",
197+
"description": "Bluetooth is used for connecting with iPhone.",
198+
"titleDisabled": "Enable Bluetooth",
199+
"titleEnabled": "Disable Bluetooth"
200+
},
194201
"deviceName": {
195202
"current": "Current device name",
196203
"error": "Device name could not be set",

frontends/web/src/routes/settings/bb02-settings.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { Skeleton } from '@/components/skeleton/skeleton';
3131
import { AttestationCheckSetting } from './components/device-settings/attestation-check-setting';
3232
import { FirmwareSetting } from './components/device-settings/firmware-setting';
3333
import { BluetoothFirmwareSetting } from './components/device-settings/bluetooth-firmware-setting';
34+
import { BluetoothToggleEnabledSetting } from './components/device-settings/bluetooth-toggle-enabled-setting';
3435
import { SecureChipSetting } from './components/device-settings/secure-chip-setting';
3536
import { DeviceNameSetting } from './components/device-settings/device-name-setting';
3637
import { FactoryResetSetting } from './components/device-settings/factory-reset-setting';
@@ -158,14 +159,14 @@ const Content = ({ deviceID }: TProps) => {
158159

159160
{/*"Bluetooth" section*/}
160161
{ deviceInfo && deviceInfo.bluetooth ? (
161-
<>
162-
<div className={styles.section}>
163-
<SubTitle className={styles.withMobilePadding}>Bluetooth</SubTitle>
164-
<BluetoothFirmwareSetting
165-
firmwareVersion={deviceInfo.bluetooth.firmwareVersion}
166-
/>
167-
</div>
168-
</>
162+
<div className={styles.section}>
163+
<SubTitle className={styles.withMobilePadding}>Bluetooth</SubTitle>
164+
<BluetoothToggleEnabledSetting
165+
deviceID={deviceID} />
166+
<BluetoothFirmwareSetting
167+
firmwareVersion={deviceInfo.bluetooth.firmwareVersion}
168+
/>
169+
</div>
169170
) : null }
170171

171172
{/*"Expert settings" section*/}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/**
2+
* Copyright 2025 Shift Crypto AG
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { useCallback, useEffect, useState } from 'react';
18+
import { useTranslation } from 'react-i18next';
19+
import { PointToBitBox02 } from '@/components/icon';
20+
import { bluetoothToggleEnabled, getDeviceInfo } from '@/api/bitbox02';
21+
import { alertUser } from '@/components/alert/Alert';
22+
import { SettingsItem } from '@/routes/settings/components/settingsItem/settingsItem';
23+
import { WaitDialog } from '@/components/wait-dialog/wait-dialog';
24+
import { StyledSkeleton } from '@/routes/settings/bb02-settings';
25+
26+
type TBluetoothToggleEnabledSettingProps = {
27+
deviceID: string;
28+
}
29+
30+
type TToggleEnabledWaitDialogProps = {
31+
show: boolean;
32+
enabled: boolean;
33+
}
34+
35+
const ToggleEnabledWaitDialog = ({ show, enabled }: TToggleEnabledWaitDialogProps) => {
36+
const { t } = useTranslation();
37+
38+
if (!show) {
39+
return null;
40+
}
41+
42+
return (
43+
<WaitDialog
44+
title={enabled ? t('bitbox02Settings.bluetoothToggleEnabled.titleEnabled') : t('bitbox02Settings.bluetoothToggleEnabled.titleDisabled')} >
45+
<p>{t('bitbox02Interact.followInstructions')}</p>
46+
<PointToBitBox02 />
47+
</WaitDialog>
48+
);
49+
};
50+
51+
const BluetoothToggleEnabledSetting = ({ deviceID }: TBluetoothToggleEnabledSettingProps) => {
52+
const { t } = useTranslation();
53+
const [show, setShow] = useState(false);
54+
const [enabled, setEnabled] = useState<undefined | boolean>(undefined);
55+
56+
const updateEnabled = useCallback(async () => {
57+
const deviceInfoResult = await getDeviceInfo(deviceID);
58+
if (!deviceInfoResult.success) {
59+
console.error(deviceInfoResult.message);
60+
alertUser(deviceInfoResult.message || t('genericError'));
61+
return;
62+
}
63+
const bluetooth = deviceInfoResult.deviceInfo.bluetooth;
64+
if (!bluetooth) {
65+
return;
66+
}
67+
setEnabled(bluetooth.enabled);
68+
return bluetooth.enabled;
69+
}, [deviceID, t]);
70+
71+
useEffect(() => {
72+
updateEnabled();
73+
}, [updateEnabled]);
74+
75+
const handleBluetoothToggleEnabled = async () => {
76+
setShow(true);
77+
const result = await bluetoothToggleEnabled(deviceID);
78+
if (!result.success) {
79+
setShow(false);
80+
console.error(result.message);
81+
alertUser(result.message || t('genericError'));
82+
return;
83+
}
84+
setShow(false);
85+
const enabled = await updateEnabled();
86+
if (enabled === true) {
87+
alertUser(t('bitbox02Settings.bluetoothToggleEnabled.alertEnabled'));
88+
} else if (enabled === false) {
89+
alertUser(t('bitbox02Settings.bluetoothToggleEnabled.alertDisabled'));
90+
}
91+
};
92+
if (enabled === undefined) {
93+
return <StyledSkeleton />;
94+
}
95+
return (
96+
<>
97+
<SettingsItem
98+
settingName={enabled ? t('bitbox02Settings.bluetoothToggleEnabled.titleEnabled') : t('bitbox02Settings.bluetoothToggleEnabled.titleDisabled')}
99+
secondaryText={t('bitbox02Settings.bluetoothToggleEnabled.description')}
100+
onClick={handleBluetoothToggleEnabled}
101+
/>
102+
<ToggleEnabledWaitDialog show={show} enabled={enabled} />
103+
</>
104+
);
105+
};
106+
107+
export { BluetoothToggleEnabledSetting };

0 commit comments

Comments
 (0)