Skip to content

Commit

Permalink
✨feat(llm): trustchain integration to qr code scan
Browse files Browse the repository at this point in the history
  • Loading branch information
LucasWerey committed Aug 8, 2024
1 parent bc044e4 commit 2f26264
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 30 deletions.
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

0 comments on commit 2f26264

Please sign in to comment.