Skip to content

Commit

Permalink
[FEAT]: Add trustchain & createQRCodeHostInstance flow
Browse files Browse the repository at this point in the history
  • Loading branch information
mcayuelas-ledger committed Aug 6, 2024
1 parent 233c9e2 commit b08f703
Show file tree
Hide file tree
Showing 14 changed files with 290 additions and 55 deletions.
5 changes: 5 additions & 0 deletions .changeset/brave-lobsters-smoke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"live-mobile": patch
---

Add trustchain & createQRCodeHostInstance flow
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { TrackScreen } from "~/analytics";
import { CryptoCurrency, TokenCurrency } from "@ledgerhq/types-cryptoassets";
import { Steps } from "../../../types/enum/addAccount";
import { AnalyticsPage } from "LLM/features/WalletSync/hooks/useLedgerSyncAnalytics";
import { Options } from "~/newArch/features/WalletSync/types/Activation";

type Props = {
startingStep: Steps;
Expand All @@ -23,6 +24,7 @@ const StepFlow = ({
onStepChange,
}: Props) => {
const [currentStep, setCurrentStep] = useState<Steps>(startingStep);
const [currentOption, setCurrentOption] = useState<Options>(Options.SCAN);

useEffect(() => {
if (onStepChange) onStepChange(currentStep);
Expand Down Expand Up @@ -70,7 +72,7 @@ const StepFlow = ({
</>
);
case Steps.QrCodeMethod:
return <QrCodeMethod />;
return <QrCodeMethod setSelectedOption={setCurrentOption} currentOption={currentOption} />;
default:
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from "react";
import { render, screen } from "@tests/test-renderer";

import { INITIAL_TEST, WalletSyncSettingsNavigator } from "./shared";

describe("scanQRCode", () => {
it("Should open the QR code scene when 'scan a qr code' toggle is pressed", async () => {
const { user } = render(<WalletSyncSettingsNavigator />, {
overrideInitialState: INITIAL_TEST,
});
await user.press(await screen.findByText(/ledger sync/i));
await user.press(await screen.findByText(/already created a key?/i));
await user.press(await screen.findByText(/scan a qr code/i));
await expect(await screen.findByText(/show qr/i)).toBeVisible();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { WalletSyncNavigatorStackParamList } from "~/components/RootNavigator/ty
import WalletSyncNavigator from "../WalletSyncNavigator";
import { BaseNavigatorStackParamList } from "~/components/RootNavigator/types/BaseNavigator";
import { QueryClientProvider, QueryClient } from "@tanstack/react-query";
import { State } from "~/reducers/types";

const Stack = createStackNavigator<
BaseNavigatorStackParamList & SettingsNavigatorStackParamList & WalletSyncNavigatorStackParamList
Expand Down Expand Up @@ -41,3 +42,24 @@ export function WalletSyncSharedNavigator() {
</QueryClientProvider>
);
}

export const INITIAL_TEST = (state: State) => ({
...state,
settings: {
...state.settings,
readOnlyModeEnabled: false,
overriddenFeatureFlags: {
llmWalletSync: {
enabled: true,
params: {
environment: "STAGING",
watchConfig: {
pollingInterval: 10000,
initialTimeout: 5000,
userIntentDebounce: 1000,
},
},
},
},
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from "react";
import { render, screen, waitFor } from "@tests/test-renderer";

import { INITIAL_TEST, WalletSyncSettingsNavigator } from "./shared";
import getWalletSyncEnvironmentParams from "@ledgerhq/live-common/walletSync/getEnvironmentParams";

jest.mock("@ledgerhq/trustchain/qrcode/index", () => ({
createQRCodeHostInstance: () => ({
trustchainApiBaseUrl: getWalletSyncEnvironmentParams("STAGING").trustchainApiBaseUrl,
onDisplayQRCode: jest.fn().mockImplementation(url => url),
onDisplayDigits: jest.fn().mockImplementation(digits => digits),
addMember: jest.fn(),
}),
}));

describe("SynchronizeWithQrCode", () => {
it("Should display the QR code when 'show qr' toggle is pressed and add a new member through the flow", async () => {
const { user } = render(<WalletSyncSettingsNavigator />, {
overrideInitialState: INITIAL_TEST,
});
await user.press(await screen.findByText(/ledger sync/i));
await user.press(await screen.findByText(/already created a key?/i));
await user.press(await screen.findByText(/scan a qr code/i));
await user.press(await screen.findByText(/show qr/i));
expect(await screen.getByTestId("ws-qr-code-displayed")).toBeVisible();

//PinCode Page after scanning QRCode
// Need to wait 3 seconds to simulate the time taken to scan the QR code
setTimeout(async () => {
await waitFor(() => {
expect(screen.getByText("Enter the code")).toBeDefined();
});
}, 3000);

//Succes Page after PinCode
setTimeout(async () => {
await waitFor(() => {
expect(
screen.getByText(
"Changes in your accounts will now automatically appear across all apps and platforms.",
),
).toBeDefined();
});
}, 3000);
});
});
Original file line number Diff line number Diff line change
@@ -1,35 +1,17 @@
import React from "react";
import { screen } from "@testing-library/react-native";
import { render } from "@tests/test-renderer";
import { WalletSyncSettingsNavigator } from "./shared";
import { State } from "~/reducers/types";
import { INITIAL_TEST, WalletSyncSettingsNavigator } from "./shared";

describe("WalletSyncSettings", () => {
const initialState = (state: State) => ({
...state,
settings: {
...state.settings,
readOnlyModeEnabled: false,
overriddenFeatureFlags: {
llmWalletSync: {
enabled: true,
params: {
environment: "STAGING",
watchConfig: {},
},
},
},
},
});

it("Should display the ledger sync row", async () => {
render(<WalletSyncSettingsNavigator />, { overrideInitialState: initialState });
render(<WalletSyncSettingsNavigator />, { overrideInitialState: INITIAL_TEST });
await expect(await screen.findByText(/ledger sync/i)).toBeVisible();
});

it("Should open the activation drawer when ledger sync row is pressed", async () => {
const { user } = render(<WalletSyncSettingsNavigator />, {
overrideInitialState: initialState,
overrideInitialState: INITIAL_TEST,
});
await user.press(await screen.findByText(/ledger sync/i));
await expect(await screen.findByText(/sync your accounts across all platforms/i)).toBeVisible();
Expand All @@ -38,31 +20,10 @@ describe("WalletSyncSettings", () => {

it("Should open the drawer when 'already created a key' button is pressed", async () => {
const { user } = render(<WalletSyncSettingsNavigator />, {
overrideInitialState: initialState,
overrideInitialState: INITIAL_TEST,
});
await user.press(await screen.findByText(/ledger sync/i));
await user.press(await screen.findByText(/already created a key?/i));
await expect(await screen.findByText(/choose your sync method/i)).toBeVisible();
});

it("Should open the QR code scene when 'scan a qr code' toggle is pressed", async () => {
const { user } = render(<WalletSyncSettingsNavigator />, {
overrideInitialState: initialState,
});
await user.press(await screen.findByText(/ledger sync/i));
await user.press(await screen.findByText(/already created a key?/i));
await user.press(await screen.findByText(/scan a qr code/i));
await expect(await screen.findByText(/show qr/i)).toBeVisible();
});

it("Should display the QR code when 'show qr' toggle is pressed", async () => {
const { user } = render(<WalletSyncSettingsNavigator />, {
overrideInitialState: initialState,
});
await user.press(await screen.findByText(/ledger sync/i));
await user.press(await screen.findByText(/already created a key?/i));
await user.press(await screen.findByText(/scan a qr code/i));
await user.press(await screen.findByText(/show qr/i));
await expect(await screen.getByTestId("ws-show-qr-code")).toBeVisible();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,34 @@ import Activation from ".";
import { TrackScreen } from "~/analytics";
import ChooseSyncMethod from "../../screens/Synchronize/ChooseMethod";
import QrCodeMethod from "../../screens/Synchronize/QrCodeMethod";
import { Steps } from "../../types/Activation";
import { Options, Steps } from "../../types/Activation";
import { AnalyticsPage } from "../../hooks/useLedgerSyncAnalytics";
import PinCodeDisplay from "../../screens/Synchronize/PinCodeDisplay";
import PinCodeInput from "../../screens/Synchronize/PinCodeInput";
import SyncError from "../../screens/Synchronize/SyncError";

type Props = {
currentStep: Steps;
navigateToChooseSyncMethod: () => void;
navigateToQrCodeMethod: () => void;
qrProcess: {
url: string | null;
error: Error | null;
isLoading: boolean;
pinCode: string | null;
};

currentOption: Options;
setOption: (option: Options) => void;
};

const ActivationFlow = ({
currentStep,
navigateToChooseSyncMethod,
navigateToQrCodeMethod,
qrProcess,
currentOption,
setOption,
}: Props) => {
const getScene = () => {
switch (currentStep) {
Expand All @@ -34,7 +49,17 @@ const ActivationFlow = ({
</>
);
case Steps.QrCodeMethod:
return <QrCodeMethod />;
return <QrCodeMethod currentOption={currentOption} setSelectedOption={setOption} />;

case Steps.PinDisplay:
return qrProcess.pinCode ? <PinCodeDisplay pinCode={qrProcess.pinCode} /> : null;

case Steps.PinInput:
return <PinCodeInput />;

case Steps.SyncError:
return <SyncError tryAgain={navigateToQrCodeMethod} />;

default:
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ const QrCode = ({ qrCodeValue }: Props) => {
borderRadius={11.52}
background={"#fff"}
justifyContent={"center"}
testID="ws-show-qr-code"
testID="ws-qr-code-displayed"
>
<QRCode
value={qrCodeValue}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { useCallback, useEffect, useState } from "react";
import { createQRCodeHostInstance } from "@ledgerhq/trustchain/qrcode/index";
import { InvalidDigitsError } from "@ledgerhq/trustchain/errors";
import { useSelector } from "react-redux";
import { trustchainSelector, memberCredentialsSelector } from "@ledgerhq/trustchain/store";
import { useTrustchainSdk } from "./useTrustchainSdk";
import { Options, Steps } from "../types/Activation";
import { useNavigation } from "@react-navigation/native";
import { WalletSyncNavigatorStackParamList } from "~/components/RootNavigator/types/WalletSyncNavigator";
import { StackNavigatorNavigation } from "~/components/RootNavigator/types/helpers";
import { ScreenName } from "~/const";
import { useFeature } from "@ledgerhq/live-common/featureFlags/index";
import getWalletSyncEnvironmentParams from "@ledgerhq/live-common/walletSync/getEnvironmentParams";

interface Props {
setCurrentStep: (step: Steps) => void;
currentStep: Steps;
currentOption: Options;
}

export function useQRCodeHost({ setCurrentStep, currentStep, currentOption }: Props) {
const trustchain = useSelector(trustchainSelector);
const memberCredentials = useSelector(memberCredentialsSelector);
const sdk = useTrustchainSdk();

const featureWalletSync = useFeature("llmWalletSync");
const { trustchainApiBaseUrl } = getWalletSyncEnvironmentParams(
featureWalletSync?.params?.environment,
);

const [isLoading, setIsLoading] = useState(false);
const [url, setUrl] = useState<string | null>(null);
const [error, setError] = useState<Error | null>(null);
const [pinCode, setPinCode] = useState<string | null>(null);

const navigation = useNavigation<StackNavigatorNavigation<WalletSyncNavigatorStackParamList>>();

const startQRCodeProcessing = useCallback(() => {
if (!trustchain || !memberCredentials || isLoading) return;

setError(null);
setIsLoading(true);
createQRCodeHostInstance({
trustchainApiBaseUrl,
onDisplayQRCode: url => {
setUrl(url);

//TODO-remove when clearing code, used to test behavior with webTool
// eslint-disable-next-line no-console
console.log("onDisplayQRCode", url);
},
onDisplayDigits: digits => {
setPinCode(digits);
setCurrentStep(Steps.PinDisplay);
},
addMember: async member => {
await sdk.addMember(trustchain, memberCredentials, member);

return trustchain;
},
})
.catch(e => {
if (e instanceof InvalidDigitsError) {
setCurrentStep(Steps.SyncError);
return;
}
setError(e);
})
.then(() => {
if (!error)
navigation.navigate(ScreenName.WalletSyncSuccess, {
created: false,
});
})
.finally(() => {
setUrl(null);
setPinCode(null);
setIsLoading(false);
});
}, [
trustchain,
memberCredentials,
isLoading,
trustchainApiBaseUrl,
setCurrentStep,
sdk,
error,
navigation,
]);

useEffect(() => {
if (currentStep === Steps.QrCodeMethod && currentOption === Options.SHOW_QR) {
startQRCodeProcessing();
}
}, [currentOption, currentStep, startQRCodeProcessing]);

return {
url,
error,
isLoading,
startQRCodeProcessing,
pinCode,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ function View({
goBackToPreviousStep,
handleClose,
onCloseDrawer,
qrProcess,
currentOption,
setCurrentOption,
}: ViewProps) {
const { height } = useWindowDimensions();
const maxDrawerHeight = height - 180;
Expand All @@ -46,6 +49,9 @@ function View({
currentStep={currentStep}
navigateToChooseSyncMethod={navigateToChooseSyncMethod}
navigateToQrCodeMethod={navigateToQrCodeMethod}
qrProcess={qrProcess}
currentOption={currentOption}
setOption={setCurrentOption}
/>
</Flex>
</QueuedDrawer>
Expand Down
Loading

0 comments on commit b08f703

Please sign in to comment.