Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨feat(llm): trustchain integration to qr code scan #7545

Merged
merged 1 commit into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/real-apricots-rush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"live-mobile": patch
---

Trustchain integration on LLM scan qr code. We can now join a trustchain scanning a qr code
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import React from "react";
import SelectAddAccountMethod from "./SelectAddAccountMethod";
import ChooseSyncMethod from "LLM/features/WalletSync/screens/Synchronize/ChooseMethod";
import QrCodeMethod from "LLM/features/WalletSync/screens/Synchronize/QrCodeMethod";
import PinCodeInput from "LLM/features/WalletSync/screens/Synchronize/PinCodeInput";
import { TrackScreen } from "~/analytics";
import { CryptoCurrency, TokenCurrency } from "@ledgerhq/types-cryptoassets";
import { AnalyticsPage } from "LLM/features/WalletSync/hooks/useLedgerSyncAnalytics";
import { Options, Steps } from "LLM/features/WalletSync/types/Activation";
import SyncError from "LLM/features/WalletSync/screens/Synchronize/SyncError";
import PinCodeDisplay from "LLM/features/WalletSync/screens/Synchronize/PinCodeDisplay";
import PinCodeInput from "LLM/features/WalletSync/screens/Synchronize/PinCodeInput";
import { useInitMemberCredentials } from "~/newArch/features/WalletSync/hooks/useInitMemberCredentials";
import { useSyncWithQrCode } from "~/newArch/features/WalletSync/hooks/useSyncWithQrCode";

type Props = {
currentStep: Steps;
Expand All @@ -19,7 +21,7 @@ type Props = {
currentOption: Options;
navigateToChooseSyncMethod: () => void;
navigateToQrCodeMethod: () => void;
onQrCodeScanned: (data: string) => void;
onQrCodeScanned: () => void;
qrProcess: {
url: string | null;
error: Error | null;
Expand All @@ -38,7 +40,21 @@ const StepFlow = ({
navigateToQrCodeMethod,
onQrCodeScanned,
qrProcess,
setCurrentStep,
}: Props) => {
const { memberCredentials } = useInitMemberCredentials();

const { handleStart, handleSendDigits, inputCallback, digits } = useSyncWithQrCode();

const handleQrCodeScanned = (data: string) => {
onQrCodeScanned();
if (memberCredentials) handleStart(data, memberCredentials, setCurrentStep);
};

const handlePinCodeSubmit = (input: string) => {
if (input && inputCallback && digits === input.length) handleSendDigits(inputCallback, input);
};

const getScene = () => {
switch (currentStep) {
case Steps.AddAccountMethod:
Expand All @@ -62,7 +78,7 @@ const StepFlow = ({
case Steps.QrCodeMethod:
return (
<QrCodeMethod
onQrCodeScanned={onQrCodeScanned}
onQrCodeScanned={handleQrCodeScanned}
currentOption={currentOption}
setSelectedOption={setCurrentOption}
/>
Expand All @@ -72,7 +88,7 @@ const StepFlow = ({
return qrProcess.pinCode ? <PinCodeDisplay pinCode={qrProcess.pinCode} /> : null;

case Steps.PinInput:
return <PinCodeInput />;
return <PinCodeInput handleSendDigits={handlePinCodeSubmit} />;

case Steps.SyncError:
return <SyncError tryAgain={navigateToQrCodeMethod} />;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,7 @@ const useAddAccountViewModel = ({ isOpened, onClose }: AddAccountDrawerProps) =>
currentOption,
});

const onQrCodeScanned = (data: string) => {
// eslint-disable-next-line no-console
console.log(data);
//setCurrentStep(Steps.PinCodeInput);
};
const onQrCodeScanned = () => setCurrentStep(Steps.PinInput);

return {
isAddAccountDrawerVisible: isOpened,
Expand All @@ -69,8 +65,8 @@ const useAddAccountViewModel = ({ isOpened, onClose }: AddAccountDrawerProps) =>
setCurrentOption,
currentOption,
setCurrentStep,
onGoBack,
onQrCodeScanned,
onGoBack,
qrProcess: {
url,
error,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { AnalyticsPage } from "../../hooks/useLedgerSyncAnalytics";
import PinCodeDisplay from "../../screens/Synchronize/PinCodeDisplay";
import PinCodeInput from "../../screens/Synchronize/PinCodeInput";
import SyncError from "../../screens/Synchronize/SyncError";
import { useInitMemberCredentials } from "../../hooks/useInitMemberCredentials";
import { useSyncWithQrCode } from "../../hooks/useSyncWithQrCode";

type Props = {
currentStep: Steps;
Expand All @@ -19,9 +21,10 @@ type Props = {
isLoading: boolean;
pinCode: string | null;
};
onQrCodeScanned: (data: string) => void;
onQrCodeScanned: () => void;
currentOption: Options;
setOption: (option: Options) => void;
setCurrentStep: (step: Steps) => void;
};

const ActivationFlow = ({
Expand All @@ -32,7 +35,21 @@ const ActivationFlow = ({
currentOption,
setOption,
onQrCodeScanned,
setCurrentStep,
}: Props) => {
const { memberCredentials } = useInitMemberCredentials();

const { handleStart, handleSendDigits, inputCallback, digits } = useSyncWithQrCode();

const handleQrCodeScanned = (data: string) => {
onQrCodeScanned();
if (memberCredentials) handleStart(data, memberCredentials, setCurrentStep);
};

const handlePinCodeSubmit = (input: string) => {
if (input && inputCallback && digits === input.length) handleSendDigits(inputCallback, input);
};

const getScene = () => {
switch (currentStep) {
case Steps.Activation:
Expand All @@ -52,7 +69,7 @@ const ActivationFlow = ({
case Steps.QrCodeMethod:
return (
<QrCodeMethod
onQrCodeScanned={onQrCodeScanned}
onQrCodeScanned={handleQrCodeScanned}
currentOption={currentOption}
setSelectedOption={setOption}
/>
Expand All @@ -62,7 +79,7 @@ const ActivationFlow = ({
return qrProcess.pinCode ? <PinCodeDisplay pinCode={qrProcess.pinCode} /> : null;

case Steps.PinInput:
return <PinCodeInput />;
return <PinCodeInput handleSendDigits={handlePinCodeSubmit} />;

case Steps.SyncError:
return <SyncError tryAgain={navigateToQrCodeMethod} />;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { getEnv } from "@ledgerhq/live-env";
import { Platform } from "react-native";

const platformMap: Record<string, string | undefined> = {
ios: "iOS",
android: "Android",
};

export function useInstanceName(): string {
const hash = getEnv("USER_ID").slice(0, 5);
return `${platformMap[Platform.OS] ?? Platform.OS} ${Platform.Version} ${hash ? " " + hash : ""}`;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { useCallback, useState } from "react";
import { MemberCredentials } from "@ledgerhq/trustchain/types";
import { createQRCodeCandidateInstance } from "@ledgerhq/trustchain/qrcode/index";
import { InvalidDigitsError } from "@ledgerhq/trustchain/errors";
import { setTrustchain } from "@ledgerhq/trustchain/store";
import { useDispatch } from "react-redux";
import { useNavigation } from "@react-navigation/native";
import { Steps } from "../types/Activation";
import { NavigatorName, ScreenName } from "~/const";
import { useInstanceName } from "./useInstanceName";

export const useSyncWithQrCode = () => {
const [digits, setDigits] = useState<number | null>(null);
const [input, setInput] = useState<string | null>(null);
const instanceName = useInstanceName();

const navigation = useNavigation();

const [inputCallback, setInputCallback] = useState<((input: string) => void) | null>(null);
const dispatch = useDispatch();

const onRequestQRCodeInput = useCallback(
(config: { digits: number }, callback: (input: string) => void) => {
setDigits(config.digits);
setInputCallback(() => callback);
},
[],
);

const onSyncFinished = useCallback(() => {
setDigits(null);
setInput(null);
setInputCallback(null);
navigation.navigate(NavigatorName.WalletSync, {
screen: ScreenName.WalletSyncSuccess,
params: {
created: false,
},
});
}, [navigation]);

const handleStart = useCallback(
async (
url: string,
memberCredentials: MemberCredentials,
setCurrentStep: (step: Steps) => void,
) => {
try {
const trustchain = await createQRCodeCandidateInstance({
memberCredentials,
scannedUrl: url,
memberName: instanceName,
onRequestQRCodeInput,
});
dispatch(setTrustchain(trustchain));
onSyncFinished();
return true;
} catch (e) {
if (e instanceof InvalidDigitsError) {
setCurrentStep(Steps.SyncError);
return;
}
throw e;
}
},
[instanceName, onRequestQRCodeInput, onSyncFinished, dispatch],
);

const handleSendDigits = useCallback(
(inputCallback: (_: string) => void, input: string) => (inputCallback(input), true),
[],
);

return { digits, input, handleStart, handleSendDigits, setInput, inputCallback };
};
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import { useMemo } from "react";
import { getEnv } from "@ledgerhq/live-env";
import { getSdk } from "@ledgerhq/trustchain/index";
import { Platform } from "react-native";
import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess";
import { TrustchainSDK } from "@ledgerhq/trustchain/types";
import { useFeature } from "@ledgerhq/live-common/featureFlags/index";
import getWalletSyncEnvironmentParams from "@ledgerhq/live-common/walletSync/getEnvironmentParams";

const platformMap: Record<string, string | undefined> = {
ios: "iOS",
android: "Android",
};
import { useInstanceName } from "./useInstanceName";

let sdkInstance: TrustchainSDK | null = null;

Expand All @@ -19,14 +14,14 @@ export function useTrustchainSdk() {
const environment = featureWalletSync?.params?.environment;
const { trustchainApiBaseUrl } = getWalletSyncEnvironmentParams(environment);
const isMockEnv = !!getEnv("MOCK");
const instanceName = useInstanceName();

const defaultContext = useMemo(() => {
const applicationId = 16;
const hash = getEnv("USER_ID").slice(0, 5);

const name = `${platformMap[Platform.OS] ?? Platform.OS} ${Platform.Version} ${hash ? " " + hash : ""}`;
const name = instanceName;
return { applicationId, name, apiBaseUrl: trustchainApiBaseUrl };
}, [trustchainApiBaseUrl]);
}, [trustchainApiBaseUrl, instanceName]);

if (sdkInstance === null) {
sdkInstance = getSdk(isMockEnv, defaultContext, withDevice);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ function View({
qrProcess,
currentOption,
setCurrentOption,
setCurrentStep,
}: ViewProps) {
const CustomDrawerHeader = () => <DrawerHeader onClose={handleClose} />;

Expand All @@ -51,6 +52,7 @@ function View({
currentOption={currentOption}
setOption={setCurrentOption}
onQrCodeScanned={onQrCodeScanned}
setCurrentStep={setCurrentStep}
/>
</Flex>
</QueuedDrawer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,7 @@ const useActivationDrawerModel = ({ isOpen, startingStep, handleClose }: Props)
setCurrentStep(Steps.QrCodeMethod);
};

// That means the url as be stored in the store
const onQrCodeScanned = (data: string) => {
// eslint-disable-next-line no-console
console.log(data);
//setCurrentStep(Steps.PinCodeInput);
};
const onQrCodeScanned = () => setCurrentStep(Steps.PinInput);

const resetStep = () => setCurrentStep(startingStep);
const resetOption = () => setCurrentOption(Options.SCAN);
Expand Down Expand Up @@ -86,6 +81,7 @@ const useActivationDrawerModel = ({ isOpen, startingStep, handleClose }: Props)
qrProcess: { url, error, isLoading, pinCode },
currentOption,
setCurrentOption,
setCurrentStep,
};
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
import React, { forwardRef, useImperativeHandle, useRef, useState } from "react";
import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { Flex, Text } from "@ledgerhq/native-ui";
import styled from "styled-components/native";
import { AnalyticsPage } from "../../hooks/useLedgerSyncAnalytics";
import TrackScreen from "~/analytics/TrackScreen";
import { NativeSyntheticEvent, TextInput, TextInputKeyPressEventData } from "react-native";

export default function PinCodeInput() {
type Props = {
handleSendDigits: (input: string) => void;
};

export default function PinCodeInput({ handleSendDigits }: Props) {
const { t } = useTranslation();
const inputRefs = [useRef<TextInput>(null), useRef<TextInput>(null), useRef<TextInput>(null)];
const [digits, setDigits] = useState<string[]>(["", "", ""]);

useEffect(() => {
if (digits.every(digit => digit)) {
handleSendDigits(digits.join(""));
}
}, [digits, handleSendDigits]);

const handleChange = (value: string, index: number) => {
const newDigits = [...digits];
newDigits[index] = value;
Expand Down
Loading