Skip to content

Commit

Permalink
feat: implement suite-common bluetooth code into Suite
Browse files Browse the repository at this point in the history
  • Loading branch information
peter-sanderson committed Feb 19, 2025
1 parent f9e9852 commit 06d724b
Show file tree
Hide file tree
Showing 18 changed files with 97 additions and 259 deletions.
39 changes: 0 additions & 39 deletions packages/suite/src/actions/bluetooth/bluetoothActions.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { BLUETOOTH_PREFIX } from '@suite-common/bluetooth';
import { createThunk } from '@suite-common/redux-utils';
import { bluetoothIpc } from '@trezor/transport-bluetooth';

import { BLUETOOTH_PREFIX } from './bluetoothActions';

type ThunkResponse = ReturnType<typeof bluetoothIpc.connectDevice>;

export const bluetoothConnectDeviceThunk = createThunk<ThunkResponse, { id: string }, void>(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { BLUETOOTH_PREFIX, bluetoothScanStatusAction } from '@suite-common/bluetooth';
import { createThunk } from '@suite-common/redux-utils';
import { bluetoothIpc } from '@trezor/transport-bluetooth';

import { BLUETOOTH_PREFIX } from './bluetoothActions';

export const bluetoothStartScanningThunk = createThunk<void, void, void>(
`${BLUETOOTH_PREFIX}/bluetoothStartScanningThunk`,
_ => {
(_, { dispatch }) => {
dispatch(bluetoothScanStatusAction({ status: 'running' }));
// This can fail, but if there is an error we already got it from `adapter-event`
// and user is informed about it (bluetooth turned-off, ...)
bluetoothIpc.startScan();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { BLUETOOTH_PREFIX, bluetoothScanStatusAction } from '@suite-common/bluetooth';
import { createThunk } from '@suite-common/redux-utils';
import { bluetoothIpc } from '@trezor/transport-bluetooth';

import { BLUETOOTH_PREFIX } from './bluetoothActions';

export const bluetoothStopScanningThunk = createThunk<void, void, void>(
`${BLUETOOTH_PREFIX}/bluetoothStopScanningThunk`,
_ => {
(_, { dispatch }) => {
dispatch(bluetoothScanStatusAction({ status: 'idle' }));
// This can fail, but there is nothing we can do about it
bluetoothIpc.stopScan();
},
Expand Down
31 changes: 23 additions & 8 deletions packages/suite/src/actions/bluetooth/initBluetoothThunk.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { createThunk } from '@suite-common/redux-utils/';
import { DeviceConnectionStatus, bluetoothIpc } from '@trezor/transport-bluetooth';
import { Without } from '@trezor/type-utils';

import {
BLUETOOTH_PREFIX,
bluetoothAdapterEventAction,
bluetoothConnectDeviceEventAction,
bluetoothDeviceListUpdate,
} from './bluetoothActions';
bluetoothNearbyDevicesUpdateAction,
selectKnownDevices,
} from '@suite-common/bluetooth';
import { createThunk } from '@suite-common/redux-utils/';
import { BluetoothDevice, DeviceConnectionStatus, bluetoothIpc } from '@trezor/transport-bluetooth';
import { Without } from '@trezor/type-utils';

import { selectSuiteFlags } from '../../reducers/suite/suiteReducer';

type DeviceConnectionStatusWithOptionalId = Without<DeviceConnectionStatus, 'id'> & {
Expand All @@ -19,18 +20,32 @@ export const initBluetoothThunk = createThunk<void, void, void>(
async (_, { dispatch, getState }) => {
const { isBluetoothEnabled } = selectSuiteFlags(getState());

console.log('______initBluetoothThunk :: 1', isBluetoothEnabled);

if (!isBluetoothEnabled) {
return;
}

console.log('______initBluetoothThunk :: 2');

bluetoothIpc.on('adapter-event', isPowered => {
console.warn('adapter-event', isPowered);
dispatch(bluetoothAdapterEventAction({ isPowered }));
});

bluetoothIpc.on('device-list-update', devices => {
console.warn('device-list-update', devices);
dispatch(bluetoothDeviceListUpdate({ devices }));

// update pairedDevices, id is changed after pairing (linux)
const linuxFixedDevices = devices.reduce((prev, curr) => {
// find devices with the same address but different id
const changed = devices.find(d => d.address === curr.address && d.id !== curr.id);
prev.push(changed ? { ...curr, id: changed.id } : curr);

return prev;
}, [] as BluetoothDevice[]);

dispatch(bluetoothNearbyDevicesUpdateAction({ nearbyDevices: linuxFixedDevices }));
});

bluetoothIpc.on('device-connection-status', connectionStatus => {
Expand All @@ -49,7 +64,7 @@ export const initBluetoothThunk = createThunk<void, void, void>(
});

// TODO: this should be called after trezor/connect init?
const knownDevices = getState().bluetooth.pairedDevices;
const knownDevices = selectKnownDevices<BluetoothDevice>(getState());
await bluetoothIpc.init({ knownDevices });
},
);
4 changes: 2 additions & 2 deletions packages/suite/src/actions/suite/storageActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ export const saveCoinjoinDebugSettings = () => async (_dispatch: Dispatch, getSt

export const saveKnownDevices = () => async (_dispatch: Dispatch, getState: GetState) => {
if (!(await db.isAccessible())) return;
const { pairedDevices } = getState().bluetooth;
db.addItem('knownDevices', { bluetooth: pairedDevices }, 'devices', true);
const { knownDevices } = getState().bluetooth;
db.addItem('knownDevices', { bluetooth: knownDevices }, 'devices', true);
};

export const saveFormDraft = async (key: string, draft: FieldValues) => {
Expand Down
52 changes: 27 additions & 25 deletions packages/suite/src/components/suite/bluetooth/BluetoothConnect.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { useCallback, useEffect, useState } from 'react';

import {
bluetoothConnectDeviceEventAction,
bluetoothScanStatusAction,
selectAdapterStatus,
selectNearbyDevices,
selectScanStatus,
} from '@suite-common/bluetooth';
import { notificationsActions } from '@suite-common/toast-notifications';
import { Card, Column, ElevationUp } from '@trezor/components';
import TrezorConnect from '@trezor/connect';
import { spacings } from '@trezor/theme';
import { BluetoothDevice } from '@trezor/transport-bluetooth';
import { TimerId } from '@trezor/type-utils';

import { BluetoothDeviceList } from './BluetoothDeviceList';
Expand All @@ -14,19 +22,10 @@ import { BluetoothSelectedDevice } from './BluetoothSelectedDevice';
import { BluetoothTips } from './BluetoothTips';
import { BluetoothNotEnabled } from './errors/BluetoothNotEnabled';
import { BluetoothVersionNotCompatible } from './errors/BluetoothVersionNotCompatible';
import {
bluetoothConnectDeviceEventAction,
bluetoothScanStatusAction,
} from '../../../actions/bluetooth/bluetoothActions';
import { bluetoothConnectDeviceThunk } from '../../../actions/bluetooth/bluetoothConnectDeviceThunk';
import { bluetoothStartScanningThunk } from '../../../actions/bluetooth/bluetoothStartScanningThunk';
import { bluetoothStopScanningThunk } from '../../../actions/bluetooth/bluetoothStopScanningThunk';
import { useDispatch, useSelector } from '../../../hooks/suite';
import {
selectBluetoothDeviceList,
selectBluetoothEnabled,
selectBluetoothScanStatus,
} from '../../../reducers/bluetooth/bluetoothSelectors';

const SCAN_TIMEOUT = 30_000;

Expand All @@ -40,12 +39,14 @@ export const BluetoothConnect = ({ onClose, uiMode }: BluetoothConnectProps) =>
const [selectedDeviceId, setSelectedDeviceId] = useState<string | null>(null);
const [scannerTimerId, setScannerTimerId] = useState<TimerId | null>(null);

const isBluetoothEnabled = useSelector(selectBluetoothEnabled);
const scanStatus = useSelector(selectBluetoothScanStatus);
const deviceList = useSelector(selectBluetoothDeviceList);
const devices = Object.values(deviceList);
const bluetoothAdapterStatus = useSelector(selectAdapterStatus);
const scanStatus = useSelector(selectScanStatus);
const nearbyDevices = useSelector(selectNearbyDevices<BluetoothDevice>);

const selectedDevice = selectedDeviceId !== null ? deviceList[selectedDeviceId] ?? null : null;
const selectedDevice =
selectedDeviceId !== null
? nearbyDevices.find(device => device.device.id === selectedDeviceId)
: undefined;

useEffect(() => {
dispatch(bluetoothStartScanningThunk());
Expand All @@ -64,7 +65,7 @@ export const BluetoothConnect = ({ onClose, uiMode }: BluetoothConnectProps) =>
useEffect(() => {
// Intentionally no `clearScamTimer`, this is first run and if we use this we would create infinite re-render
const timerId = setTimeout(() => {
dispatch(bluetoothScanStatusAction({ status: 'done' }));
dispatch(bluetoothScanStatusAction({ status: 'idle' }));
}, SCAN_TIMEOUT);

setScannerTimerId(timerId);
Expand All @@ -76,7 +77,7 @@ export const BluetoothConnect = ({ onClose, uiMode }: BluetoothConnectProps) =>

clearScamTimer();
const timerId = setTimeout(() => {
dispatch(bluetoothScanStatusAction({ status: 'done' }));
dispatch(bluetoothScanStatusAction({ status: 'idle' }));
}, SCAN_TIMEOUT);
setScannerTimerId(timerId);
};
Expand Down Expand Up @@ -120,7 +121,7 @@ export const BluetoothConnect = ({ onClose, uiMode }: BluetoothConnectProps) =>
}
};

if (!isBluetoothEnabled) {
if (bluetoothAdapterStatus === 'disabled') {
return <BluetoothNotEnabled onCancel={onClose} />;
}

Expand All @@ -133,16 +134,17 @@ export const BluetoothConnect = ({ onClose, uiMode }: BluetoothConnectProps) =>
console.log('selectedDevice', selectedDevice);

// This is fake, we scan for devices all the time
const isScanning = scanStatus !== 'done';
const scanFailed = devices.length === 0 && scanStatus === 'done';
const isScanning = scanStatus === 'running';
const scanFailed = nearbyDevices.length === 0 && scanStatus === 'idle';

const handlePairingCancel = () => {
setSelectedDeviceId(null);
onReScanClick();
};

if (
selectedDevice !== null &&
selectedDevice !== undefined &&
selectedDevice.status !== null &&
selectedDevice.status.type === 'pairing' &&
(selectedDevice.status.pin?.length ?? 0) > 0
) {
Expand All @@ -155,7 +157,7 @@ export const BluetoothConnect = ({ onClose, uiMode }: BluetoothConnectProps) =>
);
}

if (selectedDevice !== null) {
if (selectedDevice !== undefined) {
return <BluetoothSelectedDevice device={selectedDevice} onReScanClick={onReScanClick} />;
}

Expand All @@ -165,7 +167,7 @@ export const BluetoothConnect = ({ onClose, uiMode }: BluetoothConnectProps) =>
<BluetoothDeviceList
isDisabled={false}
onSelect={onSelect}
deviceList={devices}
deviceList={nearbyDevices}
isScanning={isScanning}
/>
);
Expand All @@ -181,7 +183,7 @@ export const BluetoothConnect = ({ onClose, uiMode }: BluetoothConnectProps) =>
<BluetoothScanHeader
isScanning={isScanning}
onClose={onClose}
numberOfDevices={devices.length}
numberOfDevices={nearbyDevices.length}
/>

{/* Here we need to do +1 in elevation because of custom design on the welcome screen */}
Expand All @@ -190,7 +192,7 @@ export const BluetoothConnect = ({ onClose, uiMode }: BluetoothConnectProps) =>
{uiMode === 'card' && (
<BluetoothScanFooter
onReScanClick={onReScanClick}
numberOfDevices={devices.length}
numberOfDevices={nearbyDevices.length}
scanStatus={scanStatus}
/>
)}
Expand All @@ -203,7 +205,7 @@ export const BluetoothConnect = ({ onClose, uiMode }: BluetoothConnectProps) =>
<ElevationUp>
<BluetoothScanFooter
onReScanClick={onReScanClick}
numberOfDevices={devices.length}
numberOfDevices={nearbyDevices.length}
scanStatus={scanStatus}
/>
</ElevationUp>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { DeviceModelInternal } from '@trezor/connect';
import { models } from '@trezor/connect/src/data/models'; // Todo: solve this import issue
import { RotateDeviceImage } from '@trezor/product-components';
import { spacings } from '@trezor/theme';
import { BluetoothDevice as BluetoothDeviceType } from '@trezor/transport-bluetooth';
import { BluetoothDevice } from '@trezor/transport-bluetooth';

type BluetoothDeviceProps = {
device: BluetoothDeviceType;
device: BluetoothDevice;
flex?: FlexProps['flex'];
margin?: FlexProps['margin'];
};
Expand All @@ -18,7 +18,7 @@ const getModelEnumFromBytesUtil = (_id: number) => DeviceModelInternal.T3W1;
// discuss final format of it
const getColorEnumFromVariantBytesUtil = (variant: number) => variant;

export const BluetoothDevice = ({ device, flex, margin }: BluetoothDeviceProps) => {
export const BluetoothDeviceComponent = ({ device, flex, margin }: BluetoothDeviceProps) => {
const model = getModelEnumFromBytesUtil(device.data[2]);
const color = getColorEnumFromVariantBytesUtil(device.data[1]);
const colorName = models[model].colors[color.toString()];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Button, Row } from '@trezor/components';
import { spacings } from '@trezor/theme';
import { BluetoothDevice as BluetoothDeviceType } from '@trezor/transport-bluetooth';

import { BluetoothDevice } from './BluetoothDevice';
import { BluetoothDeviceComponent } from './BluetoothDeviceComponent';

type BluetoothDeviceItemProps = {
device: BluetoothDeviceType;
Expand All @@ -12,7 +12,7 @@ type BluetoothDeviceItemProps = {

export const BluetoothDeviceItem = ({ device, onClick, isDisabled }: BluetoothDeviceItemProps) => (
<Row onClick={onClick} gap={spacings.md} alignItems="stretch">
<BluetoothDevice device={device} flex="1" />
<BluetoothDeviceComponent device={device} flex="1" />
<Button
variant="primary"
size="small"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { BluetoothDeviceState } from '@suite-common/bluetooth';
import { Card, Column, Row, SkeletonRectangle } from '@trezor/components';
import { spacings } from '@trezor/theme';
import { BluetoothDevice } from '@trezor/transport-bluetooth';

import { BluetoothDeviceItem } from './BluetoothDeviceItem';
import { BluetoothDeviceState } from '../../../reducers/bluetooth/bluetoothReducer';

type BluetoothDeviceListProps = {
deviceList: BluetoothDeviceState[];
deviceList: BluetoothDeviceState<BluetoothDevice>[];
onSelect: (id: string) => void;
isScanning: boolean;
isDisabled: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import styled from 'styled-components';

import { Card, Link, NewModal, Row, Text } from '@trezor/components';
import { spacings, spacingsPx, typography } from '@trezor/theme';
import { BluetoothDevice as BluetoothDeviceType } from '@trezor/transport-bluetooth';
import { BluetoothDevice } from '@trezor/transport-bluetooth';

import { BluetoothDevice } from './BluetoothDevice';
import { BluetoothDeviceComponent } from './BluetoothDeviceComponent';

const Pin = styled.div`
display: flex;
Expand All @@ -18,7 +18,7 @@ const Pin = styled.div`
type BluetoothPairingPinProps = {
onCancel: () => void;
pairingPin?: string;
device: BluetoothDeviceType;
device: BluetoothDevice;
};

export const BluetoothPairingPin = ({ onCancel, pairingPin, device }: BluetoothPairingPinProps) => (
Expand All @@ -40,7 +40,7 @@ export const BluetoothPairingPin = ({ onCancel, pairingPin, device }: BluetoothP
margin={{ vertical: spacings.xxl, horizontal: spacings.xxl }}
>
<Pin>{pairingPin}</Pin>
<BluetoothDevice
<BluetoothDeviceComponent
device={device}
margin={{ vertical: spacings.xxs, horizontal: spacings.xxs }}
/>
Expand Down
Loading

0 comments on commit 06d724b

Please sign in to comment.