Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
},
"include": ["./src"],
"references": [
{
"path": "../../../packages/type-utils"
}
{ "path": "../../../packages/type-utils" }
]
}
2 changes: 1 addition & 1 deletion packages/suite/mocks/extraDependenciesDesktopMock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
createNetworkModuleRepository,
createNetworksCompositionRoot,
} from '@suite-common/networks';
import { type ExtraDependenciesStatic } from '@suite-common/redux-utils';
import { type ExtraDependenciesStatic } from '@suite-common/redux-extra-dependencies';
import { extraDependenciesCommonMock } from '@suite-common/test-utils';
import { ok } from '@trezor/type-utils';

Expand Down
1 change: 1 addition & 0 deletions packages/suite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"@suite-common/platform-encryption": "workspace:*",
"@suite-common/react-query": "workspace:*",
"@suite-common/receive": "workspace:*",
"@suite-common/redux-extra-dependencies": "workspace:*",
"@suite-common/redux-utils": "workspace:*",
"@suite-common/sentry": "workspace:*",
"@suite-common/staking": "workspace:*",
Expand Down
36 changes: 19 additions & 17 deletions packages/suite/src/actions/bluetooth/bluetoothEraseBondsThunk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
type ForgetBluetoothDeviceThunkParams,
bluetoothActions,
} from '@suite-common/bluetooth';
import { selectSelectedDevice } from '@suite-common/device';
import { type DeviceRootState, selectSelectedDevice } from '@suite-common/device';
import { createThunk } from '@suite-common/redux-utils';
import { notificationsActions } from '@suite-common/toast-notifications';
import TrezorConnect from '@trezor/connect';
Expand Down Expand Up @@ -36,29 +36,31 @@ export const forgetBluetoothDeviceThunk = createThunk<void, ForgetBluetoothDevic
);

type UnpairCurrentBondThunkParams = Record<never, never>;
type UnpairCurrentBondThunkState = DeviceRootState;

/**
* Sends bleUnpair command to the Trezor device and cleans up BT state on success.
* Does NOT trigger the global OS removal modal or forgetBluetoothDeviceThunk.
* Returns whether the unpair was successful.
*/
export const unpairCurrentBondThunk = createThunk<boolean, UnpairCurrentBondThunkParams, void>(
`${BLUETOOTH_PREFIX}/unpairCurrentBond`,
async (_, { dispatch, getState }) => {
const device = selectSelectedDevice(getState());
export const unpairCurrentBondThunk = createThunk<
boolean,
UnpairCurrentBondThunkParams,
{ state: UnpairCurrentBondThunkState }
>(`${BLUETOOTH_PREFIX}/unpairCurrentBond`, async (_, { dispatch, getState }) => {
const device = selectSelectedDevice(getState());

if (!device) return false;
if (!device) return false;

const result = await TrezorConnect.bleUnpair({ device, all: false });
if (
result.success ||
result.error.code === 'Device_Disconnected' // This is an expected success
) {
return true;
}
const result = await TrezorConnect.bleUnpair({ device, all: false });
if (
result.success ||
result.error.code === 'Device_Disconnected' // This is an expected success
) {
return true;
}

dispatch(notificationsActions.addToast({ type: 'error', error: result.error.message }));
dispatch(notificationsActions.addToast({ type: 'error', error: result.error.message }));

return false;
},
);
return false;
});
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
import { BLUETOOTH_PREFIX } from '@suite-common/bluetooth';
import { BLUETOOTH_PREFIX, type WithBluetoothState } from '@suite-common/bluetooth';
import { selectKnownDeviceByDeviceId } from '@suite-common/bluetooth/src/bluetoothSelectors';
import { createThunk } from '@suite-common/redux-utils';
import { type Device } from '@trezor/connect';

import { type DesktopBluetoothDevice } from './DesktopBluetoothDevice';
import { bluetoothDisconnectDeviceThunk } from './bluetoothDisconnectDeviceThunk';

type BluetoothOnDeviceConnectedThunkState = WithBluetoothState<DesktopBluetoothDevice>;

// called on DEVICE.CONNECT event
export const bluetoothOnDeviceConnectedThunk = createThunk<void, Device, void>(
`${BLUETOOTH_PREFIX}/bluetoothOnDeviceConnectedThunk`,
(device, { dispatch, getState }) => {
const knownDevice = selectKnownDeviceByDeviceId(getState(), device.id ?? undefined);
export const bluetoothOnDeviceConnectedThunk = createThunk<
void,
Device,
{ state: BluetoothOnDeviceConnectedThunkState }
>(`${BLUETOOTH_PREFIX}/bluetoothOnDeviceConnectedThunk`, (device, { dispatch, getState }) => {
const knownDevice = selectKnownDeviceByDeviceId(getState(), device.id ?? undefined);

// device is re-connected over USB, but the same device is already connected over BT
// -> disconnect the BT connection
if (
device.descriptor.apiType !== 'bluetooth' &&
(knownDevice?.connectionStatus.type === 'connected' ||
knownDevice?.connectionStatus.type === 'connecting')
) {
console.warn('Disconnecting BT device because the same device was connected over USB');
dispatch(
bluetoothDisconnectDeviceThunk({
id: knownDevice.id,
}),
);
}
},
);
// device is re-connected over USB, but the same device is already connected over BT
// -> disconnect the BT connection
if (
device.descriptor.apiType !== 'bluetooth' &&
(knownDevice?.connectionStatus.type === 'connected' ||
knownDevice?.connectionStatus.type === 'connecting')
) {
console.warn('Disconnecting BT device because the same device was connected over USB');
dispatch(
bluetoothDisconnectDeviceThunk({
id: knownDevice.id,
}),
);
}
});
39 changes: 23 additions & 16 deletions packages/suite/src/actions/bluetooth/bluetoothStopScanningThunk.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
import { selectDeviceDefaultConnectionMode, selectIsConnectionModalOpen } from '@suite/device';
import {
type DesktopDeviceRootState,
selectDeviceDefaultConnectionMode,
selectIsConnectionModalOpen,
} from '@suite/device';
import { BLUETOOTH_PREFIX, bluetoothActions } from '@suite-common/bluetooth';
import { createThunk } from '@suite-common/redux-utils';
import { bluetoothIpc } from '@trezor/transport-bluetooth';

export const bluetoothStopScanningThunk = createThunk<void, void, void>(
`${BLUETOOTH_PREFIX}/bluetoothStopScanningThunk`,
(_, { dispatch, getState }) => {
const defaultConnectionMode = selectDeviceDefaultConnectionMode(getState());
const isModalOpen = selectIsConnectionModalOpen(getState());
if (defaultConnectionMode === 'bluetooth' && isModalOpen) {
// Don't actually stop scanning
return;
}
type BluetoothStopScanningThunkState = DesktopDeviceRootState;

dispatch(bluetoothActions.scanStatusAction({ status: 'idle' }));
// This can fail, but there is nothing we can do about it
console.log('_____BT: scanning - STOP');
bluetoothIpc.stopScan();
},
);
export const bluetoothStopScanningThunk = createThunk<
void,
void,
{ state: BluetoothStopScanningThunkState }
>(`${BLUETOOTH_PREFIX}/bluetoothStopScanningThunk`, (_, { dispatch, getState }) => {
const defaultConnectionMode = selectDeviceDefaultConnectionMode(getState());
const isModalOpen = selectIsConnectionModalOpen(getState());
if (defaultConnectionMode === 'bluetooth' && isModalOpen) {
// Don't actually stop scanning
return;
}

dispatch(bluetoothActions.scanStatusAction({ status: 'idle' }));
// This can fail, but there is nothing we can do about it
console.log('_____BT: scanning - STOP');
bluetoothIpc.stopScan();
});
9 changes: 6 additions & 3 deletions packages/suite/src/actions/bluetooth/initBluetoothThunk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import {
selectAutoConnectPolicy,
selectKnownDevices,
} from '@suite-common/bluetooth';
import { selectDevices } from '@suite-common/device';
import { selectFirmware } from '@suite-common/firmware';
import { type DeviceRootState, selectDevices } from '@suite-common/device';
import { type FirmwareRootState, selectFirmware } from '@suite-common/firmware';
import { createThunk } from '@suite-common/redux-utils';
import { notificationsActions } from '@suite-common/toast-notifications';
import TrezorConnect from '@trezor/connect';
Expand All @@ -21,12 +21,15 @@ import {
} from './DesktopBluetoothDevice';
import { bluetoothConnectDeviceThunk } from './bluetoothConnectDeviceThunk';
import { bluetoothStartScanningThunk } from './bluetoothStartScanningThunk';
import { type WithBluetoothRootState } from './desktopBluetoothReducer';
import { selectConnectingDevices } from './desktopBluetoothSelectors';
import { fixLinuxManufacturerData } from './fixLinuxManufacturerData';
import { isBluetoothDeviceReachable } from './isBluetoothDeviceReachable';
import { remapKnownDevicesForLinuxAndWindows } from './remapKnownDevicesForLinuxAndWindows';

export const initBluetoothThunk = createThunk<void, void, void>(
type InitBluetoothThunkState = DeviceRootState & FirmwareRootState & WithBluetoothRootState;

export const initBluetoothThunk = createThunk<void, void, { state: InitBluetoothThunkState }>(
`${BLUETOOTH_PREFIX}/initBluetoothThunk`,
async (_, { getState, dispatch }) => {
const knownDevices = selectKnownDevices<DesktopBluetoothDevice>(getState());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
import { BLUETOOTH_PREFIX, bluetoothActions, selectNearbyDevices } from '@suite-common/bluetooth';
import {
BLUETOOTH_PREFIX,
type WithBluetoothState,
bluetoothActions,
selectNearbyDevices,
} from '@suite-common/bluetooth';
import { createThunk } from '@suite-common/redux-utils';

import { type DesktopBluetoothDevice } from './DesktopBluetoothDevice';
import { filterOutNonResponsiveDevices } from './filterOutNonResponsiveDevices';

export const removeNonResponsiveNearbyDevicesThunk = createThunk<void, void>(
`${BLUETOOTH_PREFIX}/removeNonResponsiveNearbyDevicesThunk`,
(_, { dispatch, getState }) => {
const nearbyDevices = selectNearbyDevices<DesktopBluetoothDevice>(getState());
type RemoveNonResponsiveNearbyDevicesThunkState = WithBluetoothState<DesktopBluetoothDevice>;

dispatch(
bluetoothActions.nearbyDevicesUpdateAction({
nearbyDevices: filterOutNonResponsiveDevices(nearbyDevices),
}),
);
},
);
export const removeNonResponsiveNearbyDevicesThunk = createThunk<
void,
void,
{ state: RemoveNonResponsiveNearbyDevicesThunkState }
>(`${BLUETOOTH_PREFIX}/removeNonResponsiveNearbyDevicesThunk`, (_, { dispatch, getState }) => {
const nearbyDevices = selectNearbyDevices<DesktopBluetoothDevice>(getState());

dispatch(
bluetoothActions.nearbyDevicesUpdateAction({
nearbyDevices: filterOutNonResponsiveDevices(nearbyDevices),
}),
);
});
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import { METADATA } from '@suite/metadata';
import { type Bip329Dep } from '@suite-common/bip329-types';
import { createThunk } from '@suite-common/redux-utils';
import { triggerWebDownloadFile } from '@suite-common/suite-utils';
import { notificationsActions } from '@suite-common/toast-notifications';
import { type Account } from '@suite-common/wallet-types';
import { sanitizeFilename } from '@trezor/utils';

type ExportMetadataToBip329FileThunkDeps = {
services: Bip329Dep;
};

export const exportMetadataToBip329File = createThunk<
void,
{
account: Account;
defaultAccountLabel: string;
},
void
{ extra: ExportMetadataToBip329FileThunkDeps }
>(
METADATA.EXPORT_METADATA_TO_BIP329_FILE,
({ account, defaultAccountLabel }, { dispatch, extra: { services } }) => {
Expand Down
77 changes: 42 additions & 35 deletions packages/suite/src/actions/settings/deviceSettingsActions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { openModal } from '@suite/modal';
import { selectIsEntropyCheckEnabled } from '@suite/settings';
import { selectSelectedDevice, selectSimulatedEntropyCheckFail } from '@suite-common/device';
import {
type DeviceRootState,
selectSelectedDevice,
selectSimulatedEntropyCheckFail,
} from '@suite-common/device';
import { FIRMWARE_MODULE_PREFIX } from '@suite-common/firmware';
import { Feature, selectIsFeatureDisabled } from '@suite-common/message-system';
import { createThunk } from '@suite-common/redux-utils';
Expand Down Expand Up @@ -175,39 +179,42 @@ export const resetDevice =
return result;
};

export const changeLanguage = createThunk(
`${FIRMWARE_MODULE_PREFIX}/update-firmware-language`,
async (params: Parameters<typeof TrezorConnect.changeLanguage>[0], { dispatch, getState }) => {
const device = selectSelectedDevice(getState());

if (!device) return;

const result = await TrezorConnect.changeLanguage({
device: {
path: device.path,
},
...params,
});

if (result.success) {
dispatch(notificationsActions.addToast({ type: 'firmware-language-changed' }));
type ChangeLanguageThunkState = DeviceRootState;

export const changeLanguage = createThunk<
Awaited<ReturnType<typeof TrezorConnect.changeLanguage>> | undefined,
Parameters<typeof TrezorConnect.changeLanguage>[0],
{ state: ChangeLanguageThunkState }
>(`${FIRMWARE_MODULE_PREFIX}/update-firmware-language`, async (params, { dispatch, getState }) => {
const device = selectSelectedDevice(getState());

if (!device) return;

const result = await TrezorConnect.changeLanguage({
device: {
path: device.path,
},
...params,
});

if (result.success) {
dispatch(notificationsActions.addToast({ type: 'firmware-language-changed' }));
} else {
// Different errors for desktop/Chrome/Firefox
const isFetchError =
result.error.code === ('ENOTFOUND' as ERRORS.ErrorCode) ||
['Failed to fetch', 'NetworkError when attempting to fetch resource.'].includes(
result.error.message,
);
if (isFetchError) {
dispatch(notificationsActions.addToast({ type: 'firmware-language-fetch-error' }));
} else {
// Different errors for desktop/Chrome/Firefox
const isFetchError =
result.error.code === ('ENOTFOUND' as ERRORS.ErrorCode) ||
['Failed to fetch', 'NetworkError when attempting to fetch resource.'].includes(
result.error.message,
);
if (isFetchError) {
dispatch(notificationsActions.addToast({ type: 'firmware-language-fetch-error' }));
} else {
dispatch(
notificationsActions.addToast({
type: 'error',
error: result.error.message,
}),
);
}
dispatch(
notificationsActions.addToast({
type: 'error',
error: result.error.message,
}),
);
}
},
);
}
});
Loading
Loading