Skip to content

Commit

Permalink
feat: prepare for usage of connectionStatus from ipc
Browse files Browse the repository at this point in the history
  • Loading branch information
peter-sanderson committed Feb 26, 2025
1 parent 20f538d commit c227f7d
Show file tree
Hide file tree
Showing 15 changed files with 221 additions and 231 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BLUETOOTH_PREFIX, bluetoothActions } from '@suite-common/bluetooth';
import { BLUETOOTH_PREFIX } from '@suite-common/bluetooth';
import { createThunk } from '@suite-common/redux-utils';
import { notificationsActions } from '@suite-common/toast-notifications';
import { bluetoothIpc } from '@trezor/transport-bluetooth';
Expand All @@ -17,25 +17,12 @@ export const bluetoothConnectDeviceThunk = createThunk<
const result = await bluetoothIpc.connectDevice(id);

if (!result.success) {
dispatch(
bluetoothActions.connectDeviceEventAction({
id,
connectionStatus: { type: 'error', error: result.error },
}),
);
dispatch(
notificationsActions.addToast({
type: 'error',
error: result.error,
}),
);
} else {
dispatch(
bluetoothActions.connectDeviceEventAction({
id,
connectionStatus: { type: 'connected' },
}),
);
}

return fulfillWithValue({ success: result.success });
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { BLUETOOTH_PREFIX } from '@suite-common/bluetooth';
import { createThunk } from '@suite-common/redux-utils';
import { notificationsActions } from '@suite-common/toast-notifications';
import { bluetoothIpc } from '@trezor/transport-bluetooth';

type BluetoothDisconnectDeviceThunkResult = {
success: boolean;
};

export const bluetoothDisconnectDeviceThunk = createThunk<
BluetoothDisconnectDeviceThunkResult,
{ id: string },
void
>(
`${BLUETOOTH_PREFIX}/bluetoothConnectDeviceThunk`,
async ({ id }, { fulfillWithValue, dispatch }) => {
const result = await bluetoothIpc.connectDevice(id);

if (!result.success) {
dispatch(
notificationsActions.addToast({
type: 'error',
error: result.error,
}),
);
}

return fulfillWithValue({ success: result.success });
},
);
22 changes: 4 additions & 18 deletions packages/suite/src/actions/bluetooth/initBluetoothThunk.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import { BLUETOOTH_PREFIX, bluetoothActions, 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 { BluetoothDevice, bluetoothIpc } from '@trezor/transport-bluetooth';

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

type DeviceConnectionStatusWithOptionalId = Without<DeviceConnectionStatus, 'id'> & {
id?: string;
};

export const initBluetoothThunk = createThunk<void, void, void>(
`${BLUETOOTH_PREFIX}/initBluetoothThunk`,
async (_, { dispatch, getState }) => {
Expand Down Expand Up @@ -40,19 +35,10 @@ export const initBluetoothThunk = createThunk<void, void, void>(
dispatch(bluetoothActions.nearbyDevicesUpdateAction({ nearbyDevices }));
});

bluetoothIpc.on('device-connection-status', connectionStatus => {
console.warn('device-connection-status', connectionStatus);
const copyConnectionStatus: DeviceConnectionStatusWithOptionalId = {
...connectionStatus,
};
delete copyConnectionStatus.id; // So we dont pollute redux store
bluetoothIpc.on('device-update', (device: BluetoothDevice) => {
console.warn('device-update', device);

dispatch(
bluetoothActions.connectDeviceEventAction({
id: connectionStatus.id,
connectionStatus: copyConnectionStatus,
}),
);
dispatch(bluetoothActions.connectDeviceEventAction({ device }));
});

// TODO: this should be called after trezor/connect init?
Expand Down
1 change: 1 addition & 0 deletions packages/suite/src/actions/suite/storageActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export const saveCoinjoinDebugSettings = () => async (_dispatch: Dispatch, getSt
export const saveKnownDevices = () => async (_dispatch: Dispatch, getState: GetState) => {
if (!(await db.isAccessible())) return;
const { knownDevices } = getState().bluetooth;
// Todo: consider adding serializeBluetoothDevice (do not save status, ... signal strength, ...)
db.addItem('knownDevices', { bluetooth: knownDevices }, 'devices', true);
};

Expand Down
31 changes: 10 additions & 21 deletions packages/suite/src/components/suite/bluetooth/BluetoothConnect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
selectKnownDevices,
selectScanStatus,
} from '@suite-common/bluetooth';
import { selectDevices } from '@suite-common/wallet-core';
import { Card, Column, ElevationUp } from '@trezor/components';
import { spacings } from '@trezor/theme';
import { BluetoothDevice } from '@trezor/transport-bluetooth';
Expand Down Expand Up @@ -43,8 +42,6 @@ export const BluetoothConnect = ({ onClose, uiMode }: BluetoothConnectProps) =>
const [selectedDeviceId, setSelectedDeviceId] = useState<string | null>(null);
const [scannerTimerId, setScannerTimerId] = useState<TimerId | null>(null);

const trezorDevices = useSelector(selectDevices);

const bluetoothAdapterStatus = useSelector(selectAdapterStatus);
const scanStatus = useSelector(selectScanStatus);
const allDevices = useSelector(selectAllDevices);
Expand All @@ -56,27 +53,19 @@ export const BluetoothConnect = ({ onClose, uiMode }: BluetoothConnectProps) =>
console.log('allDevices', allDevices);

const devices = allDevices.filter(it => {
const isDeviceAlreadyConnected =
trezorDevices.find(trezorDevice => trezorDevice.bluetoothProps?.id === it.device.id) !==
undefined;

if (isDeviceAlreadyConnected) {
return false;
}

const isDeviceUnresponsiveForTooLong =
it.device.lastUpdatedTimestamp < lasUpdatedBoundaryTimestamp;
it.lastUpdatedTimestamp < lasUpdatedBoundaryTimestamp;

if (isDeviceUnresponsiveForTooLong) {
return knownDevices.find(knownDevice => knownDevice.id === it.device.id) !== undefined;
return knownDevices.find(knownDevice => knownDevice.id === it.id) !== undefined;
}

return true;
});

const selectedDevice =
selectedDeviceId !== null
? devices.find(device => device.device.id === selectedDeviceId)
? devices.find(device => device.id === selectedDeviceId)
: undefined;

useEffect(() => {
Expand Down Expand Up @@ -113,7 +102,7 @@ export const BluetoothConnect = ({ onClose, uiMode }: BluetoothConnectProps) =>
setScannerTimerId(timerId);
};

const onSelect = async (id: string) => {
const onConnect = async (id: string) => {
setSelectedDeviceId(id);
const result = await dispatch(bluetoothConnectDeviceThunk({ id })).unwrap();

Expand Down Expand Up @@ -145,14 +134,14 @@ export const BluetoothConnect = ({ onClose, uiMode }: BluetoothConnectProps) =>

if (
selectedDevice !== undefined &&
selectedDevice.status !== null &&
selectedDevice.status.type === 'pairing' &&
(selectedDevice.status.pin?.length ?? 0) > 0
selectedDevice !== null &&
selectedDevice.connectionStatus.type === 'pairing' &&
(selectedDevice.connectionStatus?.pin?.length ?? 0) > 0
) {
return (
<BluetoothPairingPin
device={selectedDevice.device}
pairingPin={selectedDevice.status.pin}
device={selectedDevice}
pairingPin={selectedDevice.connectionStatus.pin}
onCancel={handlePairingCancel}
/>
);
Expand All @@ -173,7 +162,7 @@ export const BluetoothConnect = ({ onClose, uiMode }: BluetoothConnectProps) =>
) : (
<BluetoothDeviceList
isDisabled={false}
onSelect={onSelect}
onConnect={onConnect}
deviceList={devices}
isScanning={isScanning}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,61 @@
import { BluetoothDeviceState, DeviceBluetoothStatusType } from '@suite-common/bluetooth';
import { DeviceBluetoothConnectionStatusType } from '@suite-common/bluetooth';
import { Button, Row } from '@trezor/components';
import { spacings } from '@trezor/theme';
import { BluetoothDevice } from '@trezor/transport-bluetooth';

import { BluetoothDeviceComponent } from './BluetoothDeviceComponent';
import { bluetoothDisconnectDeviceThunk } from '../../../actions/bluetooth/bluetoothDisconnectDeviceThunk';
import { useDispatch } from '../../../hooks/suite';

const map: Record<DeviceBluetoothStatusType, string> = {
const labelMap: Record<DeviceBluetoothConnectionStatusType, string> = {
disconnected: 'Connect',
connecting: 'Connecting',
connected: 'Connected',
error: 'Error',
connected: 'Disconnect',
'connection-error': 'Try again', // Out-of-range, offline, in the faraday cage, ...
pairing: 'Pairing',
paired: 'Paired',
'pairing-error': '', // shall never be show to user
};

const LOADING_STATUSES: DeviceBluetoothConnectionStatusType[] = ['pairing', 'connecting'];

type BluetoothDeviceItemProps = {
device: BluetoothDeviceState<BluetoothDevice>;
onClick: () => void;
device: BluetoothDevice;
onConnect: () => void;
};

export const BluetoothDeviceItem = ({ device, onClick }: BluetoothDeviceItemProps) => {
const isDisabled = device.status !== null;
const isLoading = device.status?.type === 'pairing' || device.status?.type === 'connecting';
export const BluetoothDeviceItem = ({ device, onConnect }: BluetoothDeviceItemProps) => {
const dispatch = useDispatch();

const isDisabled = device.connectionStatus.type !== 'disconnected';
const isLoading = LOADING_STATUSES.includes(device.connectionStatus.type);

const onDisconnect = () => {
dispatch(bluetoothDisconnectDeviceThunk({ id: device.id })).unwrap();
};

const onClickMap: Record<DeviceBluetoothConnectionStatusType, (() => void) | null> = {
'connection-error': onConnect,
'pairing-error': null,
connected: onDisconnect,
connecting: null,
disconnected: onConnect,
paired: null,
pairing: null,
};

return (
<Row onClick={/*isDisabled ? undefined : */ onClick} gap={spacings.md} alignItems="stretch">
<BluetoothDeviceComponent device={device.device} flex="1" />
<Row gap={spacings.md} alignItems="stretch">
<BluetoothDeviceComponent device={device} flex="1" />
<Button
variant="primary"
size="small"
margin={{ vertical: spacings.xxs }}
isDisabled={isDisabled}
isLoading={isLoading}
onClick={onClickMap[device.connectionStatus.type]}
>
{device.status ? map[device.status?.type] : 'Connect'}
{labelMap[device.connectionStatus.type]}
</Button>
</Row>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +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';

type BluetoothDeviceListProps = {
deviceList: BluetoothDeviceState<BluetoothDevice>[];
onSelect: (id: string) => void;
deviceList: BluetoothDevice[];
onConnect: (id: string) => void;
isScanning: boolean;
isDisabled: boolean;
};
Expand All @@ -24,19 +23,19 @@ const SkeletonDevice = () => (
);

export const BluetoothDeviceList = ({
onSelect,
onConnect,
deviceList,
isScanning,
}: BluetoothDeviceListProps) => (
<Card>
<Column gap={spacings.md} alignItems="stretch">
{deviceList.map(device => (
<BluetoothDeviceItem
key={device.device.id}
device={device}
onClick={() => onSelect(device.device.id)}
/>
))}
<BluetoothDeviceItem
key={device.id}
device={device}
onClick={() => onConnect(device.id)}
/>
))}
{isScanning && <SkeletonDevice />}
</Column>
</Card>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ReactNode } from 'react';

import { BluetoothDeviceState, DeviceBluetoothStatusType } from '@suite-common/bluetooth';
import { BluetoothDeviceState, DeviceBluetoothConnectionStatusType } from '@suite-common/bluetooth';
import {
Button,
Card,
Expand Down Expand Up @@ -44,7 +44,7 @@ export type OkComponentProps = {
};

const OkComponent = ({ device, onCancel }: OkComponentProps) => {
const map: Record<DeviceBluetoothStatusType, ReactNode> = {
const map: Record<DeviceBluetoothConnectionStatusType, ReactNode> = {
connecting: <ConnectingComponent />,
connected: null,
error: null,
Expand Down
5 changes: 3 additions & 2 deletions packages/suite/src/middlewares/wallet/storageMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,9 +204,10 @@ const storageMiddleware = (api: MiddlewareAPI<Dispatch, AppState>) => {
}

if (
deviceActions.connectDevice.match(action) ||
deviceActions.connectDevice.match(action) || // Known device is stored
bluetoothActions.knownDevicesUpdateAction.match(action) ||
bluetoothActions.removeKnownDeviceAction.match(action)
bluetoothActions.removeKnownDeviceAction.match(action) ||
bluetoothActions.connectDeviceEventAction.match(action) // Known devices may be updated
) {
api.dispatch(storageActions.saveKnownDevices());
}
Expand Down
10 changes: 3 additions & 7 deletions suite-common/bluetooth/src/bluetoothActions.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { createAction } from '@reduxjs/toolkit';

import {
BluetoothDeviceCommon,
BluetoothScanStatus,
DeviceBluetoothStatus,
} from './bluetoothReducer';
import { BluetoothDeviceCommon, BluetoothScanStatus } from './bluetoothReducer';

export const BLUETOOTH_PREFIX = '@suite/bluetooth';

Expand Down Expand Up @@ -44,8 +40,8 @@ const removeKnownDeviceAction = createAction(

const connectDeviceEventAction = createAction(
`${BLUETOOTH_PREFIX}/connect-device-event`,
({ connectionStatus, id }: { id: string; connectionStatus: DeviceBluetoothStatus }) => ({
payload: { id, connectionStatus },
({ device }: { device: BluetoothDeviceCommon }) => ({
payload: { device },
}),
);

Expand Down
Loading

0 comments on commit c227f7d

Please sign in to comment.