diff --git a/.changeset/shiny-pants-refuse.md b/.changeset/shiny-pants-refuse.md new file mode 100644 index 000000000000..39cc71c9e820 --- /dev/null +++ b/.changeset/shiny-pants-refuse.md @@ -0,0 +1,5 @@ +--- +"live-mobile": patch +--- + +Avoid blink when LLM is unsync diff --git a/apps/ledger-live-mobile/__mocks__/api/LedgerSync/authenticate.json b/apps/ledger-live-mobile/__mocks__/api/LedgerSync/authenticate.json new file mode 100644 index 000000000000..4d83737de88e --- /dev/null +++ b/apps/ledger-live-mobile/__mocks__/api/LedgerSync/authenticate.json @@ -0,0 +1,8 @@ +{ + "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJ0cnVzdGNoYWluLWJhY2tlbmQuYXBpLmF3cy5zdGcubGRnLXRlY2guY29tIiwic3ViIjoiMDMyNjk2ZWJkN2MyNzZjYjIwODM4MmNjYTk2Mzg4YWJjNWYxNTBkNDE1YjU5OWY3OTgzZmYzNTc1YmNlNDQxNDAxIiwiYXVkIjpbIkNsb3VkU3luYyIsInRydXN0Y2hhaW4iXSwiZXhwIjoxNzI2NjY1MjUwLCJpYXQiOjE3MjY2NjQ5NDksImp0aSI6IjhkM2JiYTVmLTg1NGYtNDkyNy1iZDFjLWUwMzE1NjE4Y2VmMCIsInBlcm1pc3Npb25zIjp7fSwiZGV2aWNlIjp0cnVlLCJyZWZyZXNoRXhwaXJhdGlvbiI6IjIwMjQtMDktMThUMTQ6MDk6MTBaIn0.wUyEXD488WSbKbdOM7Wk33SjKMzm7VGP3nCGRfTYhgNkI6vNdPrEQnrwKo6uloWsVvL5BrNpBCkd_8GPf2eyZw", + "permissions": { + "000c9ec1a1ab774f7eaeff2b0d4ad695f1fa07ea28d33f5d34126cb1152d6d83f6": { + "m/0'/16'/0'": ["owner"] + } + } +} diff --git a/apps/ledger-live-mobile/__mocks__/api/LedgerSync/challenge.json b/apps/ledger-live-mobile/__mocks__/api/LedgerSync/challenge.json new file mode 100644 index 000000000000..f94954db0ba9 --- /dev/null +++ b/apps/ledger-live-mobile/__mocks__/api/LedgerSync/challenge.json @@ -0,0 +1,27 @@ +{ + "json": { + "version": 0, + "challenge": { + "data": "f0c8ba737d490e16fb10dec07d2ae872", + "expiry": "2024-09-18T13:41:45Z" + }, + "host": "trustchain-backend.api.aws.stg.ldg-tech.com", + "rp": [ + { + "credential": { + "version": 0, + "curveId": 33, + "signAlgorithm": 1, + "publicKey": "03cb7628e7248ddf9c07da54b979f16bf081fb3d173aac0992ad2a44ef6a388ae2" + }, + "signature": "3045022100fc6b314d0cfc74ccb3b3aa2ff0d5648c9b4e8ef997eae2370593f78223be0b1b02201e56255b3b3caf29a349ca8d69c0955b32f0eed54d70077d2b46f23b4fb19052" + } + ], + "protocolVersion": { + "major": 1, + "minor": 0, + "patch": 0 + } + }, + "tlv": "0101070201001210f0c8ba737d490e16fb10dec07d2ae87214010115473045022100fc6b314d0cfc74ccb3b3aa2ff0d5648c9b4e8ef997eae2370593f78223be0b1b02201e56255b3b3caf29a349ca8d69c0955b32f0eed54d70077d2b46f23b4fb19052160466ead899202b7472757374636861696e2d6261636b656e642e6170692e6177732e7374672e6c64672d746563682e636f6d320121332103cb7628e7248ddf9c07da54b979f16bf081fb3d173aac0992ad2a44ef6a388ae2600401000000" +} diff --git a/apps/ledger-live-mobile/__mocks__/api/LedgerSync/info.json b/apps/ledger-live-mobile/__mocks__/api/LedgerSync/info.json new file mode 100644 index 000000000000..5d74c7a5f5cd --- /dev/null +++ b/apps/ledger-live-mobile/__mocks__/api/LedgerSync/info.json @@ -0,0 +1 @@ +{ "name": "cloud-sync-backend-service", "version": "0.5.1-RC2" } diff --git a/apps/ledger-live-mobile/__mocks__/api/LedgerSync/v1.json b/apps/ledger-live-mobile/__mocks__/api/LedgerSync/v1.json new file mode 100644 index 000000000000..c2facb5138c8 --- /dev/null +++ b/apps/ledger-live-mobile/__mocks__/api/LedgerSync/v1.json @@ -0,0 +1,4 @@ +{ + "m/": "01010102201415fb105d3e3cf464bcb7cce33380c480941659f7898948d35a5baf6c29b2930621032696ebd7c276cb208382cca96388abc5f150d415b599f7983ff3575bce44140101010110b005000102000006210306e3967662753cee32f227206f75327f0cc8fe0ca946ffd3f506fc4177b67fbe05100991dfda0c5bccdef61c983b284d4d0105508e4a84ab9ddedca1e31f3a7ee15f01666dd6e25f029527bc7901956aea2938a187cdfcf105932be5f8839d4722fd7e5bcde14afdd48622c9ce1d7049d48f8d6e23e352d05ace6494ec5e4090b0680807062102a9ae8ecdae5e00ff5a5f825c69d2994ce6f8833137f5df30eb8022db4f0ac2170346304402206c05e3366d190556062cc64a5cdcd294aa79e81259a1d82676d94dfe402243b4022043c6fede859beb41e0847905869b9ac4bc4050f25f507cf6324da360f24eb682", + "m/16'": "01010102200c9ec1a1ab774f7eaeff2b0d4ad695f1fa07ea28d33f5d34126cb1152d6d83f60621032696ebd7c276cb208382cca96388abc5f150d415b599f7983ff3575bce44140101010315b8050c800000008000001080000000062103183c0c8122c89dd59204919d434f08abf831e743fd826e4ce9bbdb9cad49d6710510b27a7045d2537beb7093c0f96459d9150550b356f67f576caaf6c57bec42cfff397a3ab970844fa380f0b186129155dd3973fa3ab445201dc7e0a5537e7ae3b175594dc6db58b34ef9b5162cfbdd78b4d72ea46189532bc4889ef46a35131467c5b8062103f1041567f9b8b0f658388e2d7f033239e2884c109fa4c1a751e7ae2e3d640e181137040c64656275672d643638326230062103d682b0be923a68e2aa077c3b49c79be57d447d8dca615628f5adceb2ccd175be0104ffffffff12aa0510b5fb0ca72e5c79d5b2229eba894ee85d05505807e4e403453725031e892885aba11acec0ea1f814b013a0e4841e24ab07cd4c1c9dc9c2829c992d3a0ae416a7db24e9b96cc73c2454c8b5849aaa3db2c80afa395bab59957e6b17d7fdbd5ec124476062103d682b0be923a68e2aa077c3b49c79be57d447d8dca615628f5adceb2ccd175be0621020918680272a96341dadeb69e43800618c739f150c066162065ff351d4298de3a0346304402202ffa1cb831595fe2594fd45c094987b32b49934bef9f285ff6086997a3ab46b3022043ba33239b19a0323788d29c2c638d4408097f144194219cc813ae4e31a476d6" +} diff --git a/apps/ledger-live-mobile/__tests__/handlers/index.ts b/apps/ledger-live-mobile/__tests__/handlers/index.ts index cc6f31f32412..768cf0edb742 100644 --- a/apps/ledger-live-mobile/__tests__/handlers/index.ts +++ b/apps/ledger-live-mobile/__tests__/handlers/index.ts @@ -1,9 +1,12 @@ import marketHandlers from "./market"; +import ledgerSyncHandlers from "./ledgerSync"; export const ALLOWED_UNHANDLED_REQUESTS = [ "ledger.statuspage.io", "cdn.live.ledger.com/announcements", "swap.ledger.com/v5/currencies/all", + "https://cdn.live.ledger.com/swap-providers/data.json", + "https://crypto-assets-service.api.ledger.com/v1/partners?output=name,signature,public_key,public_key_curve&service_name=swap", ]; -export default [...marketHandlers]; +export default [...marketHandlers, ...ledgerSyncHandlers]; diff --git a/apps/ledger-live-mobile/__tests__/handlers/ledgerSync.ts b/apps/ledger-live-mobile/__tests__/handlers/ledgerSync.ts new file mode 100644 index 000000000000..8b9b0f4ae864 --- /dev/null +++ b/apps/ledger-live-mobile/__tests__/handlers/ledgerSync.ts @@ -0,0 +1,27 @@ +import { http, HttpResponse } from "msw"; +import AuthenticateJson from "../../__mocks__/api/LedgerSync/authenticate.json"; +import ChallengeJson from "../../__mocks__/api/LedgerSync/challenge.json"; +import InfoJson from "../../__mocks__/api/LedgerSync/info.json"; +import v1Json from "../../__mocks__/api/LedgerSync/v1.json"; +const handlers = [ + http.post("https://trustchain-backend.api.aws.stg.ldg-tech.com/v1/authenticate", () => { + return HttpResponse.json(AuthenticateJson); + }), + http.get("https://trustchain-backend.api.aws.stg.ldg-tech.com/v1/challenge", () => { + return HttpResponse.json(ChallengeJson); + }), + http.get("https://cloud-sync-backend.api.aws.stg.ldg-tech.com/_info", () => { + return HttpResponse.json(InfoJson); + }), + http.get("https://trustchain-backend.api.aws.stg.ldg-tech.com/_info", () => { + return HttpResponse.json(InfoJson); + }), + http.get( + "https://trustchain-backend.api.aws.stg.ldg-tech.com/v1/trustchain/000c9ec1a1ab774f7eaeff2b0d4ad695f1fa07ea28d33f5d34126cb1152d6d83f6", + () => { + return HttpResponse.json(v1Json); + }, + ), +]; + +export default handlers; diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/__integrations__/manageKey.integration.test.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/__integrations__/manageKey.integration.test.tsx index 1928e0a8f62a..a229c96cd4ea 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/__integrations__/manageKey.integration.test.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/__integrations__/manageKey.integration.test.tsx @@ -3,6 +3,7 @@ import { screen } from "@testing-library/react-native"; import { render } from "@tests/test-renderer"; import { WalletSyncSettingsNavigator } from "./shared"; import { State } from "~/reducers/types"; +import { crypto } from "@ledgerhq/hw-trustchain"; jest.mock("../hooks/useDestroyTrustchain", () => ({ useDestroyTrustchain: () => ({ @@ -16,6 +17,7 @@ jest.mock("../hooks/useDestroyTrustchain", () => ({ describe("ManageKey", () => { it("Should open ManageKey flow and delete trustchain", async () => { + const keypair = await crypto.randomKeypair(); const { user } = render(, { overrideInitialState: (state: State) => ({ ...state, @@ -39,13 +41,13 @@ describe("ManageKey", () => { trustchain: { ...state.trustchain, trustchain: { - rootId: "rootId", - applicationPath: "applicationPath", - walletSyncEncryptionKey: "walletSyncEncryptionKey", + rootId: "000c9ec1a1ab774f7eaeff2b0d4ad695f1fa07ea28d33f5d34126cb1152d6d83f6", + applicationPath: "m/0'/16'/0'", + walletSyncEncryptionKey: crypto.to_hex(keypair.privateKey), }, memberCredentials: { - privatekey: "privatekey", - pubkey: "pubkey", + privatekey: crypto.to_hex(keypair.privateKey), + pubkey: "03d682b0be923a68e2aa077c3b49c79be57d447d8dca615628f5adceb2ccd175be", }, }, }), diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/__integrations__/walletSyncActivated.integration.test.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/__integrations__/walletSyncActivated.integration.test.tsx index dec5667286bf..a64a4185928c 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/__integrations__/walletSyncActivated.integration.test.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/__integrations__/walletSyncActivated.integration.test.tsx @@ -3,8 +3,12 @@ import { screen } from "@testing-library/react-native"; import { render } from "@tests/test-renderer"; import { WalletSyncSettingsNavigator } from "./shared"; import { State } from "~/reducers/types"; +import { crypto } from "@ledgerhq/hw-trustchain"; + describe("WalletSyncActivated", () => { it("Should open WalletSyncActivated screen", async () => { + const keypair = await crypto.randomKeypair(); + const { user } = render(, { overrideInitialState: (state: State) => ({ ...state, @@ -28,9 +32,13 @@ describe("WalletSyncActivated", () => { trustchain: { ...state.trustchain, trustchain: { - rootId: "rootId", - applicationPath: "applicationPath", - walletSyncEncryptionKey: "walletSyncEncryptionKey", + rootId: "000c9ec1a1ab774f7eaeff2b0d4ad695f1fa07ea28d33f5d34126cb1152d6d83f6", + applicationPath: "m/0'/16'/0'", + walletSyncEncryptionKey: crypto.to_hex(keypair.privateKey), + }, + memberCredentials: { + privatekey: crypto.to_hex(keypair.privateKey), + pubkey: "03d682b0be923a68e2aa077c3b49c79be57d447d8dca615628f5adceb2ccd175be", }, }, }), diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/__integrations__/walletSyncStatus.integration.test.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/__integrations__/walletSyncStatus.integration.test.tsx index d6f803417a64..a3563676c8f3 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/__integrations__/walletSyncStatus.integration.test.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/__integrations__/walletSyncStatus.integration.test.tsx @@ -3,6 +3,8 @@ import { screen } from "@testing-library/react-native"; import { render } from "@tests/test-renderer"; import { WalletSyncSettingsNavigator } from "./shared"; import { State } from "~/reducers/types"; +import { http, HttpResponse } from "msw"; +import { server } from "@tests/server"; jest.mock("../hooks/useLedgerSyncStatus", () => ({ useLedgerSyncStatus: () => ({ @@ -44,6 +46,12 @@ describe("WalletSyncStatus", () => { }), }); + server.use( + http.get("https://trustchain-backend.api.aws.stg.ldg-tech.com/v1/challenge", () => { + return HttpResponse.error(); + }), + ); + // Check if the ledger sync row is visible await expect(await screen.findByText(/ledger sync/i)).toBeVisible(); diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useCustomTimeOut.ts b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useCustomTimeOut.ts new file mode 100644 index 000000000000..420ad433aa57 --- /dev/null +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/useCustomTimeOut.ts @@ -0,0 +1,17 @@ +import { useEffect, useState } from "react"; + +export function useCustomTimeOut(timeout: number) { + const [isTimeout, setIsTimeout] = useState(false); + + useEffect(() => { + const timer = setTimeout(() => { + setIsTimeout(true); + }, timeout); + + return () => { + clearTimeout(timer); + }; + }, [timeout]); + + return isTimeout; +} diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/walletSync.hooks.ts b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/walletSync.hooks.ts index 080c3b56f145..56c0f07b8c56 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/walletSync.hooks.ts +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/hooks/walletSync.hooks.ts @@ -30,8 +30,6 @@ export const useLifeCycle = () => { }; function handleError(error: Error) { - console.error("GetMember :" + error); - if (error instanceof TrustchainEjected) reset(); if (error instanceof TrustchainNotAllowed) reset(); diff --git a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Manage/index.tsx b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Manage/index.tsx index 461467e05f0e..7e31acc7a853 100644 --- a/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Manage/index.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/WalletSync/screens/Manage/index.tsx @@ -20,6 +20,7 @@ import { TrackScreen } from "~/analytics"; import { AlertLedgerSyncDown } from "../../components/AlertLedgerSyncDown"; import { useLedgerSyncStatus } from "../../hooks/useLedgerSyncStatus"; import { TrustchainNotFound } from "@ledgerhq/trustchain/errors"; +import { useCustomTimeOut } from "../../hooks/useCustomTimeOut"; const WalletSyncManage = () => { const { t } = useTranslation(); @@ -29,7 +30,15 @@ const WalletSyncManage = () => { const { error: ledgerSyncError, isError: isLedgerSyncError } = useLedgerSyncStatus(); - const { data, isLoading, isError, error: manageInstancesError } = manageInstancesHook.memberHook; + const { + data, + isLoading, + isError, + error: manageInstancesError, + isFetching, + isFetchedAfterMount, + isPending, + } = manageInstancesHook.memberHook; const { onClickTrack } = useLedgerSyncAnalytics(); @@ -97,62 +106,76 @@ const WalletSyncManage = () => { const hasError = isLedgerSyncError || isError; + const queryFetching = isPending || isFetching; + // the following checks if the instance has been deleted from another device to avoid a blink + // in dev on a real device it will blink since it's laggy + const unsynchronizedInstance = !data && !isFetchedAfterMount; + + const forcedTimeLoaderOver = useCustomTimeOut(500); + const shouldDisplayLoader = !forcedTimeLoaderOver || unsynchronizedInstance || queryFetching; + return ( - - {getTopContent()} - - {Options.map((props, index) => ( - ); };