mirror of
https://github.com/trezor/trezor-suite.git
synced 2026-02-20 00:33:07 +01:00
feat: handle incomaptible FW/Device Models with proper modals for SuiteSync
chore: reactoring to simgle interaction key add test for fw upgrade needed chore: add selector test fix: simplify selector usage
This commit is contained in:
committed by
Peter Sanderson
parent
86be8ca6bf
commit
2be4887662
@@ -1,4 +1,7 @@
|
||||
import { StaticSessionId } from '@trezor/connect';
|
||||
|
||||
import { DesktopSuiteSyncRootState } from './suiteSyncSlice';
|
||||
|
||||
export const selectShowEnableSuiteSyncModal = (state: DesktopSuiteSyncRootState): boolean =>
|
||||
state.suiteSync.showEnableSuiteSyncModal;
|
||||
export const selectShowEnableSuiteSyncModal = (
|
||||
state: DesktopSuiteSyncRootState,
|
||||
): StaticSessionId | null => state.suiteSync.showEnableSuiteSyncModal;
|
||||
|
||||
@@ -4,18 +4,19 @@ import {
|
||||
initialSuiteSyncState as commonInitialState,
|
||||
suiteSyncReducer,
|
||||
} from '@suite-common/suite-sync';
|
||||
import { StaticSessionId } from '@trezor/connect';
|
||||
|
||||
import { Action } from 'src/types/suite';
|
||||
|
||||
import { STORAGE } from '../suite/constants';
|
||||
|
||||
export type DesktopSuiteSyncState = SuiteSyncState & {
|
||||
showEnableSuiteSyncModal: boolean;
|
||||
showEnableSuiteSyncModal: StaticSessionId | null;
|
||||
};
|
||||
|
||||
export const initialSuiteSyncState: DesktopSuiteSyncState = {
|
||||
...commonInitialState,
|
||||
showEnableSuiteSyncModal: false,
|
||||
showEnableSuiteSyncModal: null,
|
||||
};
|
||||
|
||||
export type DesktopSuiteSyncRootState = {
|
||||
@@ -27,7 +28,7 @@ export const suiteSyncSlice = createSliceWithExtraDeps({
|
||||
initialState: initialSuiteSyncState,
|
||||
reducers: {
|
||||
updateShowEnableSuiteSyncModal: (state, action) => {
|
||||
state.showEnableSuiteSyncModal = action.payload.show;
|
||||
state.showEnableSuiteSyncModal = action.payload.deviceStaticSessionId;
|
||||
},
|
||||
},
|
||||
extraReducers: builder => {
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import { selectShowEnableSuiteSyncModal } from 'src/actions/suiteSync/suiteSyncSelectors';
|
||||
import { updateShowEnableSuiteSyncModal } from 'src/actions/suiteSync/suiteSyncSlice';
|
||||
import { useDispatch, useSelector } from 'src/hooks/suite';
|
||||
|
||||
import { TurnOnSecureSyncModal } from './TurnOnSecureSyncModal';
|
||||
|
||||
export const LabelingModalManager = () => {
|
||||
const dispatch = useDispatch();
|
||||
const showEnableSuiteSyncModal = useSelector(selectShowEnableSuiteSyncModal);
|
||||
|
||||
const onClose = () => {
|
||||
dispatch(updateShowEnableSuiteSyncModal({ show: false }));
|
||||
};
|
||||
|
||||
if (!showEnableSuiteSyncModal) return null;
|
||||
|
||||
return <TurnOnSecureSyncModal onClose={onClose} />;
|
||||
};
|
||||
@@ -5,19 +5,16 @@ import styled from 'styled-components';
|
||||
|
||||
import { Translation } from '@suite/intl';
|
||||
import {
|
||||
isSuiteSyncSupportedByDevice,
|
||||
selectIsSuiteSyncEnabled,
|
||||
selectShouldOfferSecureSync,
|
||||
selectIsTurnOnSuiteSyncInteractionNeeded,
|
||||
} from '@suite-common/suite-sync';
|
||||
import { selectDeviceByStaticSessionId } from '@suite-common/wallet-core';
|
||||
import { Button, DropdownMenuItemProps, Row, Text, Tooltip } from '@trezor/components';
|
||||
import { Button, DropdownMenuItemProps, Row } from '@trezor/components';
|
||||
import { StaticSessionId } from '@trezor/connect';
|
||||
import { EditableText, EditableTextProps } from '@trezor/product-components';
|
||||
import { spacingsPx } from '@trezor/theme';
|
||||
import { TimerId, exhaustive } from '@trezor/type-utils';
|
||||
|
||||
import { addMetadata, init, setEditing } from 'src/actions/suite/metadata/metadataLabelingActions';
|
||||
import { updateShowEnableSuiteSyncModal } from 'src/actions/suiteSync/suiteSyncSlice';
|
||||
import { useDiscovery, useDispatch, useSelector } from 'src/hooks/suite';
|
||||
import {
|
||||
selectIsLabelingAvailableForEntity,
|
||||
@@ -25,9 +22,11 @@ import {
|
||||
} from 'src/reducers/suite/metadataReducer';
|
||||
import { MetadataAddPayload } from 'src/types/suite/metadata';
|
||||
|
||||
import { SuiteSyncInteractionsTooltip } from './SuiteSyncInteractionsTooltip';
|
||||
import { LabelContentProps, LabelingVariant, MetadataProps, PrimitiveProps } from './definitions';
|
||||
import { withDropdown } from './withDropdown';
|
||||
import { withEditable } from './withEditable';
|
||||
import { updateShowEnableSuiteSyncModal } from '../../../../actions/suiteSync/suiteSyncSlice';
|
||||
import { processLegacyMetadataIntoSuiteSyncThunk } from '../../../../actions/wallet/processLegacyMetadataIntoSuiteSyncThunk';
|
||||
import { AccountTypeBadge } from '../../AccountTypeBadge';
|
||||
import { NO_HIGHLIGHT_ATTRIBUTE } from '../../FindBar/consts';
|
||||
@@ -360,14 +359,6 @@ export const Labeling = ({
|
||||
const isSuiteSyncEnabled = useSelector(selectIsSuiteSyncEnabled);
|
||||
const legacyMetadataState = useSelector(state => state.metadata);
|
||||
const isLegacyLabelingInitPossible = useSelector(selectIsLabelingInitPossible);
|
||||
const shouldOfferSecureSync = useSelector(selectShouldOfferSecureSync);
|
||||
const device = useSelector(state =>
|
||||
selectDeviceByStaticSessionId(state, deviceStaticSessionId),
|
||||
);
|
||||
|
||||
const hasDeviceSuiteSyncOwner = device?.suiteSyncOwner !== undefined;
|
||||
const isEvoluLabeling =
|
||||
isSuiteSyncEnabled && isSuiteSyncSupportedByDevice(device) && hasDeviceSuiteSyncOwner;
|
||||
|
||||
const deviceState =
|
||||
payload.type === 'walletLabel' ? (payload.entityKey as StaticSessionId) : undefined;
|
||||
@@ -375,6 +366,10 @@ export const Labeling = ({
|
||||
selectIsLabelingAvailableForEntity(state, payload.entityKey, deviceState),
|
||||
);
|
||||
|
||||
const suiteSyncInteraction = useSelector(state =>
|
||||
selectIsTurnOnSuiteSyncInteractionNeeded(state, deviceStaticSessionId),
|
||||
);
|
||||
|
||||
const handleEdit = useCallback(async () => {
|
||||
// When clicking on inline input edit, ensure that everything needed is already ready.
|
||||
if (
|
||||
@@ -384,13 +379,13 @@ export const Labeling = ({
|
||||
// Is there something that needs to be initiated?
|
||||
!isLegacyLabelingEnabled
|
||||
) {
|
||||
if (shouldOfferSecureSync) {
|
||||
dispatch(updateShowEnableSuiteSyncModal({ show: true }));
|
||||
if (suiteSyncInteraction !== null) {
|
||||
dispatch(updateShowEnableSuiteSyncModal({ deviceStaticSessionId }));
|
||||
|
||||
// user can decide if they want to enable metadata or not, so we do not set editing state yet
|
||||
return;
|
||||
} else {
|
||||
const result = await dispatch(
|
||||
return await dispatch(
|
||||
init(
|
||||
// Provide force=true argument (user wants to enable metadata).
|
||||
true,
|
||||
@@ -398,16 +393,15 @@ export const Labeling = ({
|
||||
deviceState,
|
||||
),
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}, [
|
||||
isSuiteSyncEnabled,
|
||||
legacyMetadataState.initiating,
|
||||
isLegacyLabelingEnabled,
|
||||
shouldOfferSecureSync,
|
||||
suiteSyncInteraction,
|
||||
dispatch,
|
||||
deviceStaticSessionId,
|
||||
deviceState,
|
||||
]);
|
||||
|
||||
@@ -440,20 +434,28 @@ export const Labeling = ({
|
||||
}
|
||||
};
|
||||
|
||||
const isSuiteSyncPossible =
|
||||
suiteSyncInteraction !== 'unsupported' &&
|
||||
suiteSyncInteraction !== 'firmware-upgrade-needed';
|
||||
|
||||
return (
|
||||
<EditableText
|
||||
onSubmit={onSubmit ?? handleSubmit}
|
||||
onEdit={handleEdit}
|
||||
isDisabled={
|
||||
isDisabled ||
|
||||
(!isLegacyLabelingEnabled && !isLegacyLabelingInitPossible && !isEvoluLabeling)
|
||||
}
|
||||
isLoading={legacyMetadataState.initiating || isDiscoveryRunning}
|
||||
data-testid={`@metadata/${payload.type}/${payload.defaultValue}/hover-container`}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</EditableText>
|
||||
<SuiteSyncInteractionsTooltip suiteSyncInteraction={suiteSyncInteraction}>
|
||||
<EditableText
|
||||
onSubmit={onSubmit ?? handleSubmit}
|
||||
onEdit={handleEdit}
|
||||
isDisabled={
|
||||
isDisabled ||
|
||||
(!isLegacyLabelingEnabled &&
|
||||
!isLegacyLabelingInitPossible &&
|
||||
!isSuiteSyncPossible)
|
||||
}
|
||||
isLoading={legacyMetadataState.initiating || isDiscoveryRunning}
|
||||
data-testid={`@metadata/${payload.type}/${payload.defaultValue}/hover-container`}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</EditableText>
|
||||
</SuiteSyncInteractionsTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -482,17 +484,12 @@ export const MetadataLabeling = ({
|
||||
|
||||
const isSuiteSyncEnabled = useSelector(selectIsSuiteSyncEnabled);
|
||||
const legacyMetadataState = useSelector(state => state.metadata);
|
||||
const device = useSelector(state =>
|
||||
selectDeviceByStaticSessionId(state, deviceStaticSessionId),
|
||||
);
|
||||
|
||||
const l10nLabelling = getLocalizedActions(payload.type);
|
||||
const dataTestBase = `@metadata/${payload.type}/${payload.defaultValue}`;
|
||||
const actionButtonsDisabled = isDiscoveryRunning || pending;
|
||||
const isSubscribedToSubmitResult = useRef(payload.defaultValue);
|
||||
|
||||
const hasDeviceSuiteSyncOwner = device?.suiteSyncOwner !== undefined;
|
||||
|
||||
let timeout: TimerId | undefined;
|
||||
useEffect(() => {
|
||||
setPending(false);
|
||||
@@ -511,7 +508,9 @@ export const MetadataLabeling = ({
|
||||
selectIsLabelingAvailableForEntity(state, payload.entityKey, deviceState),
|
||||
);
|
||||
|
||||
const shouldOfferSecureSync = useSelector(selectShouldOfferSecureSync);
|
||||
const suiteSyncInteraction = useSelector(state =>
|
||||
selectIsTurnOnSuiteSyncInteractionNeeded(state, deviceStaticSessionId),
|
||||
);
|
||||
|
||||
// is this concrete instance being edited?
|
||||
const editActive = legacyMetadataState.editing === payload.defaultValue;
|
||||
@@ -525,8 +524,8 @@ export const MetadataLabeling = ({
|
||||
// Is there something that needs to be initiated?
|
||||
!isLegacyLabelingEnabled
|
||||
) {
|
||||
if (shouldOfferSecureSync) {
|
||||
dispatch(updateShowEnableSuiteSyncModal({ show: true }));
|
||||
if (suiteSyncInteraction !== null) {
|
||||
dispatch(updateShowEnableSuiteSyncModal({ deviceStaticSessionId }));
|
||||
|
||||
// user can decide if they want to enable metadata or not, so we do not set editing state yet
|
||||
return;
|
||||
@@ -614,13 +613,14 @@ export const MetadataLabeling = ({
|
||||
|
||||
const labelContainerDataTest = `${dataTestBase}/hover-container`;
|
||||
|
||||
const isEvoluLabeling =
|
||||
isSuiteSyncEnabled && isSuiteSyncSupportedByDevice(device) && hasDeviceSuiteSyncOwner;
|
||||
const isSuiteSyncPossible =
|
||||
suiteSyncInteraction !== 'unsupported' &&
|
||||
suiteSyncInteraction !== 'firmware-upgrade-needed';
|
||||
|
||||
// Should "add label"/"edit label" button be visible?
|
||||
const showActionButton =
|
||||
!isDisabled &&
|
||||
(isLegacyLabelingEnabled || isLegacyLabelingInitPossible || isEvoluLabeling) &&
|
||||
(isLegacyLabelingEnabled || isLegacyLabelingInitPossible || isSuiteSyncPossible) &&
|
||||
!showSuccess &&
|
||||
!editActive;
|
||||
|
||||
@@ -640,16 +640,7 @@ export const MetadataLabeling = ({
|
||||
const showEdit = showEditOption(variant);
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
content={
|
||||
isSuiteSyncEnabled &&
|
||||
!isSuiteSyncSupportedByDevice(device) && (
|
||||
<Text variant="warning">
|
||||
<Translation id="FIRMWARE_NEEDS_UPGRADE_FOR_SUITE_SYNC" />
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
>
|
||||
<SuiteSyncInteractionsTooltip suiteSyncInteraction={suiteSyncInteraction}>
|
||||
<LabelContainer
|
||||
data-testid={labelContainerDataTest}
|
||||
onClick={e => payload.value && !editActive && e.stopPropagation()}
|
||||
@@ -707,6 +698,6 @@ export const MetadataLabeling = ({
|
||||
</Button>
|
||||
)}
|
||||
</LabelContainer>
|
||||
</Tooltip>
|
||||
</SuiteSyncInteractionsTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { Translation, TranslationKey } from '@suite/intl';
|
||||
import { SuiteSyncInteraction } from '@suite-common/suite-sync';
|
||||
import { Text, Tooltip } from '@trezor/components';
|
||||
import { exhaustive } from '@trezor/type-utils';
|
||||
|
||||
type LabelingDisabledTooltipProps = {
|
||||
suiteSyncInteraction: SuiteSyncInteraction | null;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const SuiteSyncInteractionsTooltip = ({
|
||||
suiteSyncInteraction,
|
||||
children,
|
||||
}: LabelingDisabledTooltipProps) => {
|
||||
if (suiteSyncInteraction === null) {
|
||||
return children;
|
||||
}
|
||||
|
||||
const translationMap: Record<'firmware-upgrade-needed' | 'unsupported', TranslationKey> = {
|
||||
'firmware-upgrade-needed': 'FIRMWARE_NEEDS_UPGRADE_FOR_SUITE_SYNC',
|
||||
unsupported: 'FIRMWARE_UNSUPPORTED_DEVICE_SUITE_SYNC',
|
||||
};
|
||||
|
||||
switch (suiteSyncInteraction) {
|
||||
case 'keys-needed':
|
||||
case 'suite-sync-off':
|
||||
// For disabled SuiteSync we allow editing as for Legacy devices user can
|
||||
// turn on the legacy labeling.
|
||||
|
||||
return children;
|
||||
|
||||
case 'firmware-upgrade-needed':
|
||||
case 'unsupported':
|
||||
return (
|
||||
<Tooltip
|
||||
content={
|
||||
<Text variant="warning">
|
||||
<Translation id={translationMap[suiteSyncInteraction]} />
|
||||
</Text>
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
default:
|
||||
return exhaustive(suiteSyncInteraction);
|
||||
}
|
||||
};
|
||||
@@ -1,93 +0,0 @@
|
||||
import { Translation } from '@suite/intl';
|
||||
import { notificationsActions } from '@suite-common/toast-notifications';
|
||||
import { Card, IconCircle, List, Modal, Paragraph } from '@trezor/components';
|
||||
import { exhaustive } from '@trezor/type-utils';
|
||||
|
||||
import { useDevice, useDispatch } from '../../../hooks/suite';
|
||||
import { useSuiteServices } from '../../../support/SuiteServicesProvider';
|
||||
|
||||
type TurnOnSecureSyncModalProps = {
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export const TurnOnSecureSyncModal = ({ onClose }: TurnOnSecureSyncModalProps) => {
|
||||
const { suiteSync } = useSuiteServices();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { device } = useDevice();
|
||||
|
||||
const onSwitch = async () => {
|
||||
const result = await suiteSync.turnOnSuiteSync({
|
||||
deviceStaticSessionId: device?.state?.staticSessionId,
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
const { type } = result.error;
|
||||
switch (type) {
|
||||
case 'SuiteSyncUnavailableOnDeviceError':
|
||||
case 'SuiteSyncFirmwareUpgradeNeededDeviceErrorType':
|
||||
case 'DeviceCancelled':
|
||||
case 'DeviceError':
|
||||
dispatch(notificationsActions.addToast({ type: 'error', error: type }));
|
||||
|
||||
return;
|
||||
default:
|
||||
return exhaustive(type);
|
||||
}
|
||||
}
|
||||
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
heading={<Translation id="TR_TURN_ON_SECURE_SYNC_LABELS_MODAL_HEADING" />}
|
||||
description={<Translation id="TR_TURN_ON_SECURE_SYNC_LABELS_MODAL_DESCRIPTION" />}
|
||||
onCancel={onClose}
|
||||
width={600}
|
||||
bottomContent={
|
||||
<>
|
||||
<Modal.Button onClick={onSwitch}>
|
||||
<Translation id="TR_TURN_ON_SECURE_SYNC" />
|
||||
</Modal.Button>
|
||||
<Modal.Button onClick={onClose} intent="neutral" priority="secondary">
|
||||
<Translation id="TR_CANCEL" />
|
||||
</Modal.Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Card paddingType="large">
|
||||
<List gap={16} variant="tertiary">
|
||||
<List.Item
|
||||
bulletComponent={
|
||||
<IconCircle
|
||||
name="cloudX"
|
||||
hasBorder={false}
|
||||
paddingType="medium"
|
||||
size={40}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Paragraph>
|
||||
<Translation id="TR_TURN_ON_SECURE_SYNC_DATA_STORED_LOCALLY" />
|
||||
</Paragraph>
|
||||
</List.Item>
|
||||
<List.Item
|
||||
bulletComponent={
|
||||
<IconCircle
|
||||
name="desktopTower"
|
||||
hasBorder={false}
|
||||
paddingType="medium"
|
||||
size={40}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Paragraph>
|
||||
<Translation id="TR_TURN_ON_SECURE_SYNC_ONLY_AUTHORIZED_DEVICES" />
|
||||
</Paragraph>
|
||||
</List.Item>
|
||||
</List>
|
||||
</Card>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
import { Translation } from '@suite/intl';
|
||||
import { selectDeviceByStaticSessionId } from '@suite-common/wallet-core';
|
||||
import { Card, Modal, Paragraph } from '@trezor/components';
|
||||
import { StaticSessionId } from '@trezor/connect';
|
||||
import { getFirmwareVersion } from '@trezor/device-utils';
|
||||
|
||||
import { goto } from '../../../../actions/suite/routerActions';
|
||||
import { useDispatch, useSelector } from '../../../../hooks/suite';
|
||||
|
||||
type SuiteSyncFirmwareUpgradeNeededModalProps = {
|
||||
onClose: () => void;
|
||||
deviceStaticSessionId: StaticSessionId | null;
|
||||
};
|
||||
|
||||
export const SuiteSyncFirmwareUpgradeNeededModal = ({
|
||||
onClose,
|
||||
deviceStaticSessionId,
|
||||
}: SuiteSyncFirmwareUpgradeNeededModalProps) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const device = useSelector(state =>
|
||||
deviceStaticSessionId !== null
|
||||
? selectDeviceByStaticSessionId(state, deviceStaticSessionId)
|
||||
: undefined,
|
||||
);
|
||||
|
||||
if (device === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const currentFwVersion = getFirmwareVersion(device);
|
||||
|
||||
const onClick = () => {
|
||||
// Update will disconnect device in the process and our Firmware Update
|
||||
// flow won't allow us to navigate back. So we just redirect the user
|
||||
// and close the modal.
|
||||
dispatch(goto('firmware-index', { params: { cancelable: true } }));
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
heading={<Translation id="TR_TURN_ON_SECURE_SYNC_FW_UPDATE_MODAL_HEADING" />}
|
||||
onCancel={onClose}
|
||||
width={600}
|
||||
bottomContent={
|
||||
<>
|
||||
<Modal.Button onClick={onClick}>
|
||||
<Translation id="TR_TURN_ON_SECURE_SYNC_FW_UPDATE_MODAL_UPGRADE" />
|
||||
</Modal.Button>
|
||||
<Modal.Button onClick={onClose} intent="neutral" priority="secondary">
|
||||
<Translation id="TR_TURN_ON_SECURE_SYNC_FW_UPDATE_MODAL_NOT_NOW" />
|
||||
</Modal.Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Card paddingType="large">
|
||||
<Paragraph variant="tertiary">
|
||||
<Translation
|
||||
id="TR_TURN_ON_SECURE_SYNC_FW_UPDATE_MODAL_DESCRIPTION"
|
||||
values={{ version: currentFwVersion }}
|
||||
/>
|
||||
</Paragraph>
|
||||
</Card>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,150 @@
|
||||
import { Translation } from '@suite/intl';
|
||||
import { isFwUpgradeNeededForSuiteSync } from '@suite-common/suite-sync';
|
||||
import { notificationsActions } from '@suite-common/toast-notifications';
|
||||
import { selectDeviceByStaticSessionId } from '@suite-common/wallet-core';
|
||||
import { Banner, Card, Column, IconCircle, List, Modal, Paragraph } from '@trezor/components';
|
||||
import { StaticSessionId } from '@trezor/connect';
|
||||
import { exhaustive } from '@trezor/type-utils';
|
||||
|
||||
import { goto } from '../../../../actions/suite/routerActions';
|
||||
import { useDispatch, useSelector } from '../../../../hooks/suite';
|
||||
import { useSuiteServices } from '../../../../support/SuiteServicesProvider';
|
||||
|
||||
type SuiteSyncTurnOnAndFwUpgradeModalProps = {
|
||||
deviceStaticSessionId: StaticSessionId;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export const SuiteSyncTurnOnAndFwUpgradeModal = ({
|
||||
deviceStaticSessionId,
|
||||
onClose,
|
||||
}: SuiteSyncTurnOnAndFwUpgradeModalProps) => {
|
||||
const dispatch = useDispatch();
|
||||
const { suiteSync } = useSuiteServices();
|
||||
|
||||
const device = useSelector(state =>
|
||||
deviceStaticSessionId !== null
|
||||
? selectDeviceByStaticSessionId(state, deviceStaticSessionId)
|
||||
: undefined,
|
||||
);
|
||||
|
||||
const isFwUpgradeNeeded = isFwUpgradeNeededForSuiteSync(device);
|
||||
|
||||
if (device === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const onSwitch = async () => {
|
||||
const result = await suiteSync.turnOnSuiteSync({
|
||||
deviceStaticSessionId: deviceStaticSessionId ?? undefined,
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
const { type } = result.error;
|
||||
switch (type) {
|
||||
case 'SuiteSyncFirmwareUpgradeNeededDeviceErrorType':
|
||||
// Update will disconnect device in the process and our Firmware Update
|
||||
// flow won't allow us to navigate back. So we just redirect the user
|
||||
// and close the modal.
|
||||
dispatch(goto('firmware-index', { params: { cancelable: true } }));
|
||||
onClose();
|
||||
|
||||
return;
|
||||
|
||||
case 'SuiteSyncUnavailableOnDeviceError':
|
||||
case 'DeviceCancelled':
|
||||
case 'DeviceError':
|
||||
dispatch(notificationsActions.addToast({ type: 'error', error: type }));
|
||||
|
||||
return;
|
||||
default:
|
||||
return exhaustive(type);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
heading={<Translation id="TR_TURN_ON_SECURE_SYNC_LABELS_MODAL_HEADING" />}
|
||||
description={<Translation id="TR_TURN_ON_SECURE_SYNC_LABELS_MODAL_DESCRIPTION" />}
|
||||
onCancel={onClose}
|
||||
width={600}
|
||||
bottomContent={
|
||||
<>
|
||||
<Modal.Button onClick={onSwitch}>
|
||||
<Translation
|
||||
id={
|
||||
isFwUpgradeNeeded
|
||||
? 'TR_TURN_ON_SECURE_SYNC_MODAL_TURN_ON_AND_UPGRADE'
|
||||
: 'TR_TURN_ON_SECURE_SYNC'
|
||||
}
|
||||
/>
|
||||
</Modal.Button>
|
||||
<Modal.Button onClick={onClose} intent="neutral" priority="secondary">
|
||||
<Translation id="TR_CANCEL" />
|
||||
</Modal.Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Column gap={16}>
|
||||
<Card paddingType="large">
|
||||
<List gap={16} variant="tertiary">
|
||||
<List.Item
|
||||
bulletComponent={
|
||||
<IconCircle
|
||||
name="tag"
|
||||
hasBorder={false}
|
||||
paddingType="medium"
|
||||
size={40}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Paragraph>
|
||||
<Translation id="TR_TURN_ON_SECURE_SYNC_DATA_LABELS" />
|
||||
</Paragraph>
|
||||
</List.Item>
|
||||
<List.Item
|
||||
bulletComponent={
|
||||
<IconCircle
|
||||
name="cloudX"
|
||||
hasBorder={false}
|
||||
paddingType="medium"
|
||||
size={40}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Paragraph>
|
||||
<Translation id="TR_TURN_ON_SECURE_SYNC_DATA_STORED_LOCALLY" />
|
||||
</Paragraph>
|
||||
</List.Item>
|
||||
<List.Item
|
||||
bulletComponent={
|
||||
<IconCircle
|
||||
name="desktopTower"
|
||||
hasBorder={false}
|
||||
paddingType="medium"
|
||||
size={40}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Paragraph>
|
||||
<Translation id="TR_TURN_ON_SECURE_SYNC_ONLY_AUTHORIZED_DEVICES" />
|
||||
</Paragraph>
|
||||
</List.Item>
|
||||
</List>
|
||||
</Card>
|
||||
{isFwUpgradeNeeded && (
|
||||
<Banner
|
||||
intent="info"
|
||||
icon="password"
|
||||
description={
|
||||
<Paragraph typographyStyle="hint">
|
||||
<Translation id="TR_NEEDS_ATTENTION_FIRMWARE_REQUIRED" />
|
||||
</Paragraph>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Column>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
import { selectIsTurnOnSuiteSyncInteractionNeeded } from '@suite-common/suite-sync';
|
||||
import { exhaustive } from '@trezor/type-utils';
|
||||
|
||||
import { useDispatch, useSelector } from 'src/hooks/suite';
|
||||
|
||||
import { SuiteSyncFirmwareUpgradeNeededModal } from './SuiteSyncFirmwareUpgradeNeededModal';
|
||||
import { SuiteSyncTurnOnAndFwUpgradeModal } from './SuiteSyncTurnOnAndFwUpgradeModal';
|
||||
import { selectShowEnableSuiteSyncModal } from '../../../../actions/suiteSync/suiteSyncSelectors';
|
||||
import { updateShowEnableSuiteSyncModal } from '../../../../actions/suiteSync/suiteSyncSlice';
|
||||
|
||||
export const TurnOnSuiteSyncModalManager = () => {
|
||||
const dispatch = useDispatch();
|
||||
const deviceStaticSessionId = useSelector(selectShowEnableSuiteSyncModal);
|
||||
|
||||
const suiteSyncInteraction = useSelector(state =>
|
||||
selectIsTurnOnSuiteSyncInteractionNeeded(state, deviceStaticSessionId),
|
||||
);
|
||||
|
||||
if (deviceStaticSessionId === null || suiteSyncInteraction === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const onClose = () => {
|
||||
dispatch(updateShowEnableSuiteSyncModal({ deviceStaticSessionId: null }));
|
||||
};
|
||||
|
||||
switch (suiteSyncInteraction) {
|
||||
case 'unsupported': // This modal is not relevant to unsupported devices.
|
||||
case 'keys-needed':
|
||||
return null;
|
||||
|
||||
case 'suite-sync-off':
|
||||
return (
|
||||
<SuiteSyncTurnOnAndFwUpgradeModal
|
||||
onClose={onClose}
|
||||
deviceStaticSessionId={deviceStaticSessionId}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'firmware-upgrade-needed':
|
||||
return (
|
||||
<SuiteSyncFirmwareUpgradeNeededModal
|
||||
onClose={onClose}
|
||||
deviceStaticSessionId={deviceStaticSessionId}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return exhaustive(suiteSyncInteraction);
|
||||
}
|
||||
};
|
||||
@@ -7,7 +7,7 @@ import { useDispatch, usePreferredModal, useSelector } from '../../../../hooks/s
|
||||
import type { AppState, ForegroundAppRoute } from '../../../../types/suite';
|
||||
import { SwitchDevice } from '../../../../views/suite/SwitchDevice/SwitchDevice';
|
||||
import { ThpGlobalModalManager } from '../../../connection/thp/ThpGlobalModalManager';
|
||||
import { LabelingModalManager } from '../../labeling/LabelingModalManager';
|
||||
import { TurnOnSuiteSyncModalManager } from '../../labeling/TurnOnSuiteSync/TurnOnSuiteSyncModalManager';
|
||||
import { ConfirmPassphraseBeforeAction } from '../../modals/ReduxModal/DeviceContextModal/ConfirmPassphraseBeforeAction';
|
||||
import { PassphraseModal } from '../../modals/ReduxModal/DeviceContextModal/PassphraseModal';
|
||||
import { PassphraseOnDeviceModal } from '../../modals/ReduxModal/DeviceContextModal/PassphraseOnDeviceModal';
|
||||
@@ -57,11 +57,13 @@ const ForegroundAppModal = ({ app, cancelable }: ForegroundAppModalProps) => {
|
||||
|
||||
const onCancel = () => dispatch(closeModalApp());
|
||||
|
||||
// IMPORTANT: This is the place where all the modals that need to rendered OVER
|
||||
// Wallet-Switch needs to be.
|
||||
if (app === 'switch-device') {
|
||||
return (
|
||||
<>
|
||||
<SwitchDevice cancelable={cancelable} onCancel={onCancel} />
|
||||
<LabelingModalManager />
|
||||
<TurnOnSuiteSyncModalManager />
|
||||
{/* THP flow can be triggered by auto-connect and that will open THP modals.
|
||||
* However, this ForegroundApp takes precedes and prevents ALL other modals
|
||||
* to render. So we have to render it here as well.*/}
|
||||
|
||||
@@ -118,8 +118,8 @@ export const SuiteLayout = ({ children, 'data-testid': dataTest }: SuiteLayoutPr
|
||||
|
||||
<ModalSwitcher />
|
||||
<PassphraseFlow />
|
||||
<AppShortcuts />
|
||||
|
||||
<AppShortcuts />
|
||||
<PowerMonitorManager />
|
||||
|
||||
{isBelowTablet && <CoinjoinBars />}
|
||||
@@ -154,7 +154,6 @@ export const SuiteLayout = ({ children, 'data-testid': dataTest }: SuiteLayoutPr
|
||||
</Columns>
|
||||
</Body>
|
||||
</LayoutContext.Provider>
|
||||
|
||||
{!isBelowTablet && <GuideButton />}
|
||||
</Modal.Provider>
|
||||
</PageWrapper>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { usePreferredModal } from 'src/hooks/suite/usePreferredModal';
|
||||
|
||||
import { ForegroundAppModal } from './ForegroundAppModal';
|
||||
import { WipedBleDeviceNeedsManualOsRemovalModalManager } from '../../bluetooth/WipedBleDeviceNeedsManualOsRemovalModalManager';
|
||||
import { LabelingModalManager } from '../../labeling/LabelingModalManager';
|
||||
import { TurnOnSuiteSyncModalManager } from '../../labeling/TurnOnSuiteSync/TurnOnSuiteSyncModalManager';
|
||||
import { ReduxModal } from '../ReduxModal/ReduxModal';
|
||||
|
||||
type ModalParams = ReturnType<typeof usePreferredModal>;
|
||||
@@ -33,8 +33,8 @@ export const ModalSwitcher = () => {
|
||||
<>
|
||||
<WipedBleDeviceNeedsManualOsRemovalModalManager />
|
||||
<ThpGlobalModalManager />
|
||||
<TurnOnSuiteSyncModalManager />
|
||||
<ConnectionGlobalModalManager />
|
||||
<LabelingModalManager />
|
||||
<Inner modal={modal} />
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -27,6 +27,7 @@ import { useLabelingDeviceState } from 'src/hooks/suite/useLabelingDeviceState';
|
||||
import { useSuiteServices } from 'src/support/SuiteServicesProvider';
|
||||
import { useLegacyAnalytics } from 'src/support/useAnalytics';
|
||||
|
||||
import { updateShowEnableSuiteSyncModal } from '../../../actions/suiteSync/suiteSyncSlice';
|
||||
import { LabelingSwitchToLegacyModal } from '../../../components/suite/labeling/LabelingSwitchToLegacyModal';
|
||||
|
||||
export const Labeling = () => {
|
||||
@@ -44,6 +45,9 @@ export const Labeling = () => {
|
||||
|
||||
const legacyMetadataState = useSelector(state => state.metadata);
|
||||
|
||||
if (deviceStaticSessionId === undefined) {
|
||||
return null;
|
||||
}
|
||||
const legacyEnableIfNeeded = () => {
|
||||
if (!legacyMetadataState.enabled) {
|
||||
suiteSync.turnOffSuiteSync(); // Enabling Legacy Labeling implicitly disables Evolu
|
||||
@@ -80,8 +84,11 @@ export const Labeling = () => {
|
||||
if (!result.success) {
|
||||
const { type } = result.error;
|
||||
switch (type) {
|
||||
case 'SuiteSyncUnavailableOnDeviceError':
|
||||
case 'SuiteSyncFirmwareUpgradeNeededDeviceErrorType':
|
||||
dispatch(updateShowEnableSuiteSyncModal({ deviceStaticSessionId }));
|
||||
|
||||
return;
|
||||
case 'SuiteSyncUnavailableOnDeviceError':
|
||||
case 'DeviceCancelled':
|
||||
case 'DeviceError':
|
||||
dispatch(notificationsActions.addToast({ type: 'error', error: type }));
|
||||
|
||||
@@ -20,15 +20,18 @@ export const SuiteSyncWalletDebug = ({ device }: { device: AcquiredDevice }) =>
|
||||
|
||||
const legacyMetadataState = useSelector(state => state.metadata);
|
||||
|
||||
if (
|
||||
!isSuiteSyncDebugEnabled ||
|
||||
!isSuiteSyncSupportedByDevice(device) ||
|
||||
!device.state?.staticSessionId
|
||||
) {
|
||||
const deviceStaticSessionId = device.state?.staticSessionId;
|
||||
|
||||
const isSuiteSyncDebug =
|
||||
isSuiteSyncDebugEnabled &&
|
||||
isSuiteSyncSupportedByDevice(device) &&
|
||||
deviceStaticSessionId !== undefined;
|
||||
|
||||
if (!isSuiteSyncDebug) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { walletDescriptor, deviceId } = parseDeviceStaticSessionId(device.state.staticSessionId);
|
||||
const { walletDescriptor, deviceId } = parseDeviceStaticSessionId(deviceStaticSessionId);
|
||||
|
||||
const handleResetKeysRequest = () => {
|
||||
if (!device?.id || device.state?.staticSessionId === undefined) {
|
||||
|
||||
@@ -2,9 +2,10 @@ export {
|
||||
selectIsSuiteSyncEnabled,
|
||||
selectIsFeatureSuiteSyncAvailable,
|
||||
selectSuiteSyncRelayUrl,
|
||||
selectShouldOfferSecureSync,
|
||||
selectIsTurnOnSuiteSyncInteractionNeeded,
|
||||
selectIsSuiteSyncDebugEnabled,
|
||||
} from './suiteSyncSelectors';
|
||||
export type { SuiteSyncInteraction } from './suiteSyncSelectors';
|
||||
export type { WithSuiteSyncAndDeviceState } from './suiteSyncSelectors';
|
||||
export { createSuiteSyncCompositionRoot } from './createSuiteSyncCompositionRoot';
|
||||
export { suiteSyncReducer, initialSuiteSyncState } from './suiteSyncReducer';
|
||||
@@ -34,4 +35,4 @@ export {
|
||||
findSuiteSyncAccountLabel,
|
||||
} from './data/suiteSyncDataSelectors';
|
||||
export { suiteSyncToBip329 } from './data/labeling/suiteSyncToBip329';
|
||||
export { isSuiteSyncSupportedByDevice } from './suiteSyncUtils';
|
||||
export { isSuiteSyncSupportedByDevice, isFwUpgradeNeededForSuiteSync } from './suiteSyncUtils';
|
||||
|
||||
@@ -24,6 +24,10 @@ export const createEnsureWalletSuiteSyncOn =
|
||||
async ({ deviceStaticSessionId }) => {
|
||||
const device = selectDeviceByStaticSessionId(deps.getState(), deviceStaticSessionId);
|
||||
|
||||
if (isFwUpgradeNeededForSuiteSync(device)) {
|
||||
return err({ type: 'SuiteSyncFirmwareUpgradeNeededDeviceErrorType' });
|
||||
}
|
||||
|
||||
const canTurnOnSuiteSync =
|
||||
device && isTrezorDeviceWithState(device) && isSuiteSyncSupportedByDevice(device);
|
||||
|
||||
@@ -31,9 +35,5 @@ export const createEnsureWalletSuiteSyncOn =
|
||||
return err({ type: 'SuiteSyncUnavailableOnDeviceError' });
|
||||
}
|
||||
|
||||
if (isFwUpgradeNeededForSuiteSync(device)) {
|
||||
return err({ type: 'SuiteSyncFirmwareUpgradeNeededDeviceErrorType' });
|
||||
}
|
||||
|
||||
return await deps.ensureSuiteSyncData({ deviceStaticSessionId });
|
||||
};
|
||||
|
||||
@@ -71,6 +71,26 @@ describe(createEnsureWalletSuiteSyncOn.name, () => {
|
||||
expect(deps.ensureSuiteSyncData).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('returns error when device needs firmware upgrade', async () => {
|
||||
const unavailableCapabilities: UnavailableCapabilities = { evolu: 'update-required' };
|
||||
|
||||
const deps = createMockDeps<EnsureWalletSuiteSyncOnDeps>({
|
||||
dispatch: null,
|
||||
getState: () => createMockState([createDevice({ unavailableCapabilities })]),
|
||||
refreshSuiteSyncKeys: null,
|
||||
ensureSuiteSyncData: null,
|
||||
subscriptionStorage: createSubscriptionStorageMock(),
|
||||
});
|
||||
|
||||
const ensureWalletSuiteSyncOn = createEnsureWalletSuiteSyncOn(deps);
|
||||
const result = await ensureWalletSuiteSyncOn({ deviceStaticSessionId });
|
||||
|
||||
expect(!result.success && result.error.type).toBe(
|
||||
'SuiteSyncFirmwareUpgradeNeededDeviceErrorType',
|
||||
);
|
||||
expect(deps.ensureSuiteSyncData).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls ensureSuiteSyncData when wallet is eligible', async () => {
|
||||
const ensureResult = ok({ data: {} } as any);
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { DeviceRootState } from '@suite-common/wallet-core';
|
||||
import { DeviceRootState, selectDeviceByStaticSessionId } from '@suite-common/wallet-core';
|
||||
import { StaticSessionId } from '@trezor/connect';
|
||||
|
||||
import { SuiteSyncState } from './suiteSyncReducer';
|
||||
import { isFwUpgradeNeededForSuiteSync, isSuiteSyncSupportedByDevice } from './suiteSyncUtils';
|
||||
|
||||
export type WithSuiteSyncState = {
|
||||
suiteSync: SuiteSyncState;
|
||||
@@ -20,7 +22,45 @@ export const selectIsFeatureSuiteSyncAvailable = (state: WithSuiteSyncAndDeviceS
|
||||
export const selectSuiteSyncRelayUrl = (state: WithSuiteSyncAndDeviceState) =>
|
||||
state.suiteSync.settings.suiteSyncRelayUrl;
|
||||
|
||||
export const selectShouldOfferSecureSync = (state: WithSuiteSyncAndDeviceState): boolean =>
|
||||
state.device.selectedDevice?.unavailableCapabilities?.evolu === undefined &&
|
||||
state.suiteSync.settings.isFeatureSuiteSyncAvailable &&
|
||||
!state.suiteSync.settings.isSuiteSyncEnabled;
|
||||
export type SuiteSyncInteraction =
|
||||
| 'suite-sync-off'
|
||||
| 'firmware-upgrade-needed'
|
||||
| 'unsupported'
|
||||
| 'keys-needed';
|
||||
|
||||
export const selectIsTurnOnSuiteSyncInteractionNeeded = (
|
||||
state: WithSuiteSyncAndDeviceState,
|
||||
deviceStaticSessionId: StaticSessionId | null,
|
||||
): SuiteSyncInteraction | null => {
|
||||
if (deviceStaticSessionId === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const device = selectDeviceByStaticSessionId(state, deviceStaticSessionId);
|
||||
|
||||
if (device === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!selectIsFeatureSuiteSyncAvailable(state)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!selectIsSuiteSyncEnabled(state)) {
|
||||
return 'suite-sync-off';
|
||||
}
|
||||
|
||||
if (!isSuiteSyncSupportedByDevice(device)) {
|
||||
return 'unsupported';
|
||||
}
|
||||
|
||||
if (isFwUpgradeNeededForSuiteSync(device)) {
|
||||
return 'firmware-upgrade-needed';
|
||||
}
|
||||
|
||||
if (device.suiteSyncOwner === null) {
|
||||
return 'keys-needed';
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
151
suite-common/suite-sync/src/tests/suiteSyncSelectors.test.ts
Normal file
151
suite-common/suite-sync/src/tests/suiteSyncSelectors.test.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import { asEncryptedHex } from '@suite-common/platform-encryption';
|
||||
import type { SuiteSyncOwnerSerialized, TrezorDevice } from '@suite-common/suite-types';
|
||||
import { getSuiteDevice } from '@suite-common/test-utils';
|
||||
import { deviceReducerInitialState } from '@suite-common/wallet-core';
|
||||
import type { UnavailableCapabilities } from '@trezor/connect';
|
||||
import { StaticSessionId } from '@trezor/connect';
|
||||
|
||||
import { initialSuiteSyncState } from '../suiteSyncReducer';
|
||||
import { selectIsTurnOnSuiteSyncInteractionNeeded } from '../suiteSyncSelectors';
|
||||
import type { WithSuiteSyncAndDeviceState } from '../suiteSyncSelectors';
|
||||
|
||||
const deviceStaticSessionId: StaticSessionId = '1@2:3';
|
||||
|
||||
const createDevice = (overrides: Partial<TrezorDevice> = {}): TrezorDevice =>
|
||||
({
|
||||
...getSuiteDevice(),
|
||||
id: 'device-id',
|
||||
state: {
|
||||
staticSessionId: deviceStaticSessionId,
|
||||
},
|
||||
unavailableCapabilities: {},
|
||||
...overrides,
|
||||
}) as unknown as TrezorDevice;
|
||||
|
||||
const createMockState = (
|
||||
deviceOverrides: Partial<TrezorDevice> = {},
|
||||
suiteSyncOverrides: Partial<WithSuiteSyncAndDeviceState['suiteSync']> = {},
|
||||
): WithSuiteSyncAndDeviceState => ({
|
||||
device: {
|
||||
...deviceReducerInitialState,
|
||||
devices: [createDevice(deviceOverrides)],
|
||||
},
|
||||
suiteSync: {
|
||||
...initialSuiteSyncState,
|
||||
...suiteSyncOverrides,
|
||||
},
|
||||
});
|
||||
|
||||
describe(selectIsTurnOnSuiteSyncInteractionNeeded.name, () => {
|
||||
it('no interaction needed if device is not found', () => {
|
||||
const state = createMockState();
|
||||
state.device.devices = [];
|
||||
|
||||
const result = selectIsTurnOnSuiteSyncInteractionNeeded(state, deviceStaticSessionId);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('no interaction, if suite-sync it not enabled in debug/experimental features', () => {
|
||||
const state = createMockState(
|
||||
{},
|
||||
{
|
||||
settings: {
|
||||
...initialSuiteSyncState.settings,
|
||||
isFeatureSuiteSyncAvailable: false,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const result = selectIsTurnOnSuiteSyncInteractionNeeded(state, deviceStaticSessionId);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('interaction is "suite-sync-off" when Suite Sync is disabled', () => {
|
||||
const state = createMockState(
|
||||
{},
|
||||
{
|
||||
settings: {
|
||||
...initialSuiteSyncState.settings,
|
||||
isFeatureSuiteSyncAvailable: true,
|
||||
isSuiteSyncEnabled: false,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const result = selectIsTurnOnSuiteSyncInteractionNeeded(state, deviceStaticSessionId);
|
||||
|
||||
expect(result).toBe('suite-sync-off');
|
||||
});
|
||||
|
||||
it('interaction is "unsupported" when device does not support Suite Sync (T1, TT)', () => {
|
||||
const unavailableCapabilities: UnavailableCapabilities = { evolu: 'no-support' };
|
||||
const state = createMockState(
|
||||
{ unavailableCapabilities },
|
||||
{
|
||||
settings: {
|
||||
...initialSuiteSyncState.settings,
|
||||
isFeatureSuiteSyncAvailable: true,
|
||||
isSuiteSyncEnabled: true,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const result = selectIsTurnOnSuiteSyncInteractionNeeded(state, deviceStaticSessionId);
|
||||
|
||||
expect(result).toBe('unsupported');
|
||||
});
|
||||
|
||||
it('interaction is "firmware-upgrade-needed" when device needs firmware upgrade', () => {
|
||||
const unavailableCapabilities: UnavailableCapabilities = { evolu: 'update-required' };
|
||||
const state = createMockState(
|
||||
{ unavailableCapabilities },
|
||||
{
|
||||
settings: {
|
||||
...initialSuiteSyncState.settings,
|
||||
isFeatureSuiteSyncAvailable: true,
|
||||
isSuiteSyncEnabled: true,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const result = selectIsTurnOnSuiteSyncInteractionNeeded(state, deviceStaticSessionId);
|
||||
|
||||
expect(result).toBe('firmware-upgrade-needed');
|
||||
});
|
||||
|
||||
it('interaction is "keys-needed" when device has suiteSyncOwner set', () => {
|
||||
const state = createMockState(
|
||||
{ suiteSyncOwner: null },
|
||||
{
|
||||
settings: {
|
||||
...initialSuiteSyncState.settings,
|
||||
isFeatureSuiteSyncAvailable: true,
|
||||
isSuiteSyncEnabled: true,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const result = selectIsTurnOnSuiteSyncInteractionNeeded(state, deviceStaticSessionId);
|
||||
|
||||
expect(result).toBe('keys-needed');
|
||||
});
|
||||
|
||||
it('no interaction needed', () => {
|
||||
const state = createMockState(
|
||||
{ suiteSyncOwner: asEncryptedHex<SuiteSyncOwnerSerialized>('owner-key') },
|
||||
{
|
||||
settings: {
|
||||
...initialSuiteSyncState.settings,
|
||||
isFeatureSuiteSyncAvailable: true,
|
||||
isSuiteSyncEnabled: true,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const result = selectIsTurnOnSuiteSyncInteractionNeeded(state, deviceStaticSessionId);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -30,8 +30,6 @@ export const createSuiteSyncNativeCompositionRoot = (
|
||||
...deps,
|
||||
createSuiteStorage: createEvoluStorage,
|
||||
createSuiteSyncOwner: evoluCreateSuiteSyncOwner,
|
||||
flushSuiteSyncStorage: () => {
|
||||
reloadAppAsync();
|
||||
},
|
||||
flushSuiteSyncStorage: reloadAppAsync,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -6163,6 +6163,11 @@ export const messages = defineMessages({
|
||||
id: 'FIRMWARE_NEEDS_UPGRADE_FOR_SUITE_SYNC',
|
||||
defaultMessage: "Upgrade your Trezor's firmware to use Suite Sync.",
|
||||
},
|
||||
FIRMWARE_UNSUPPORTED_DEVICE_SUITE_SYNC: {
|
||||
id: 'FIRMWARE_UNSUPPORTED_DEVICE_SUITE_SYNC',
|
||||
defaultMessage:
|
||||
'Suite Sync works with Trezor Safe 3, Trezor Safe 5, and Trezor Safe 7 devices.',
|
||||
},
|
||||
TR_DISABLED_SWITCH_TOOLTIP: {
|
||||
id: 'TR_DISABLED_SWITCH_TOOLTIP',
|
||||
defaultMessage: 'Connect & unlock device to change',
|
||||
@@ -10484,10 +10489,35 @@ export const messages = defineMessages({
|
||||
id: 'TR_TURN_ON_SECURE_SYNC_DATA_STORED_LOCALLY',
|
||||
defaultMessage: 'Your data is stored locally and only synced with devices you’ve approved.',
|
||||
},
|
||||
TR_TURN_ON_SECURE_SYNC_DATA_LABELS: {
|
||||
id: 'TR_TURN_ON_SECURE_SYNC_DATA_LABELS',
|
||||
defaultMessage: 'Name your wallets, personalize accounts, and label transactions. ',
|
||||
},
|
||||
TR_TURN_ON_SECURE_SYNC_ONLY_AUTHORIZED_DEVICES: {
|
||||
id: 'TR_TURN_ON_SECURE_SYNC_ONLY_AUTHORIZED_DEVICES',
|
||||
defaultMessage: 'Only devices you’ve authorized through your Trezor can decrypt your data.',
|
||||
},
|
||||
TR_TURN_ON_SECURE_SYNC_FW_UPDATE_MODAL_HEADING: {
|
||||
id: 'TR_TURN_ON_SECURE_SYNC_FW_UPDATE_MODAL_HEADING',
|
||||
defaultMessage: 'Firmware update needed for Suite Sync',
|
||||
},
|
||||
TR_TURN_ON_SECURE_SYNC_FW_UPDATE_MODAL_DESCRIPTION: {
|
||||
id: 'TR_TURN_ON_SECURE_SYNC_FW_UPDATE_MODAL_DESCRIPTION',
|
||||
defaultMessage:
|
||||
'The current firmware version on your Trezor is {version}. Update the firmware to use Suite Sync.',
|
||||
},
|
||||
TR_TURN_ON_SECURE_SYNC_MODAL_TURN_ON_AND_UPGRADE: {
|
||||
id: 'TR_TURN_ON_SECURE_SYNC_MODAL_TURN_ON_AND_UPGRADE',
|
||||
defaultMessage: 'Turn on & update firmware',
|
||||
},
|
||||
TR_TURN_ON_SECURE_SYNC_FW_UPDATE_MODAL_UPGRADE: {
|
||||
id: 'TR_TURN_ON_SECURE_SYNC_FW_UPDATE_MODAL_UPGRADE',
|
||||
defaultMessage: 'Upgrade',
|
||||
},
|
||||
TR_TURN_ON_SECURE_SYNC_FW_UPDATE_MODAL_NOT_NOW: {
|
||||
id: 'TR_TURN_ON_SECURE_SYNC_FW_UPDATE_MODAL_NOT_NOW',
|
||||
defaultMessage: 'Not now',
|
||||
},
|
||||
TR_SWITCH_TO_LEGACY_LABELING_MODAL_HEADING: {
|
||||
id: 'TR_SWITCH_TO_LEGACY_LABELING_MODAL_HEADING',
|
||||
defaultMessage: 'Switch to legacy labeling?',
|
||||
|
||||
Reference in New Issue
Block a user