Skip to content

Commit

Permalink
Merge pull request #346 from LedgerHQ/feat/FAT-13-sync-onboarding-logic
Browse files Browse the repository at this point in the history
feat/FAT-13 Sync Onboarding logic
  • Loading branch information
alexandremgo authored Jun 30, 2022
2 parents cb1a6fb + 60fb9ef commit f26fa16
Show file tree
Hide file tree
Showing 11 changed files with 1,190 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .changeset/brown-mugs-beam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@ledgerhq/live-common": minor
---

Synchronized onboarding logic with:

- Function to extract the device onboarding state from byte flags
- Polling mechanism to retrieve the device onboarding state
- Polling mechanism available as a react hook for LLM and LLD
2 changes: 2 additions & 0 deletions apps/cli/src/commands-index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import signMessage from "./commands/signMessage";
import speculosList from "./commands/speculosList";
import swap from "./commands/swap";
import sync from "./commands/sync";
import synchronousOnboarding from "./commands/synchronousOnboarding";
import testDetectOpCollision from "./commands/testDetectOpCollision";
import testGetTrustedInputFromTxHash from "./commands/testGetTrustedInputFromTxHash";
import user from "./commands/user";
Expand Down Expand Up @@ -91,6 +92,7 @@ export default {
speculosList,
swap,
sync,
synchronousOnboarding,
testDetectOpCollision,
testGetTrustedInputFromTxHash,
user,
Expand Down
30 changes: 30 additions & 0 deletions apps/cli/src/commands/synchronousOnboarding.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {
getOnboardingStatePolling,
OnboardingStatePollingResult,
} from "@ledgerhq/live-common/lib/hw/getOnboardingStatePolling";
import { Observable } from "rxjs";
import { deviceOpt } from "../scan";

export default {
description: "track the onboarding status of your device",
args: [
{
name: "pollingPeriodMs",
alias: "p",
desc: "polling period in milliseconds",
type: Number,
},
deviceOpt,
],
job: ({
device,
pollingPeriodMs,
}: Partial<{
device: string;
pollingPeriodMs: number;
}>): Observable<OnboardingStatePollingResult | null> =>
getOnboardingStatePolling({
deviceId: device ?? "",
pollingPeriodMs: pollingPeriodMs ?? 1000,
}),
};
251 changes: 251 additions & 0 deletions libs/ledger-live-common/src/hw/extractOnboardingState.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
import {
extractOnboardingState,
OnboardingStep,
} from "./extractOnboardingState";

describe("@hw/extractOnboardingState", () => {
describe("extractOnboardingState", () => {
describe("When the flag bytes are incorrect", () => {
it("should throw an error", () => {
const incompleteFlagsBytes = Buffer.from([0, 0]);
// DeviceExtractOnboardingStateError is not of type Error,
// so cannot check in toThrow(DeviceExtractOnboardingStateError)
expect(() => extractOnboardingState(incompleteFlagsBytes)).toThrow();
});
});

describe("When the device is onboarded", () => {
it("should return a device state that is onboarded", () => {
const flagsBytes = Buffer.from([1 << 2, 0, 0, 0]);

const onboardingState = extractOnboardingState(flagsBytes);

expect(onboardingState).not.toBeNull();
expect(onboardingState?.isOnboarded).toBe(true);
});
});

describe("When the device is in recovery mode", () => {
it("should return a device state that is in recovery mode", () => {
const flagsBytes = Buffer.from([1, 0, 0, 0]);

const onboardingState = extractOnboardingState(flagsBytes);

expect(onboardingState).not.toBeNull();
expect(onboardingState?.isInRecoveryMode).toBe(true);
});
});

describe("When the device is not onboarded and in normal mode", () => {
let flagsBytes: Buffer;

beforeEach(() => {
flagsBytes = Buffer.from([0, 0, 0, 0]);
});

describe("and the user is on the welcome screen", () => {
beforeEach(() => {
flagsBytes[3] = 0;
});

it("should return an onboarding step that is set at the welcome screen", () => {
const onboardingState = extractOnboardingState(flagsBytes);

expect(onboardingState).not.toBeNull();
expect(onboardingState?.currentOnboardingStep).toBe(
OnboardingStep.WelcomeScreen
);
});
});

describe("and the user is choosing what kind of setup they want", () => {
beforeEach(() => {
flagsBytes[3] = 1;
});

it("should return an onboarding step that is set at the setup choice", () => {
const onboardingState = extractOnboardingState(flagsBytes);

expect(onboardingState).not.toBeNull();
expect(onboardingState?.currentOnboardingStep).toBe(
OnboardingStep.SetupChoice
);
});
});

describe("and the user is setting their pin", () => {
beforeEach(() => {
flagsBytes[3] = 2;
});

it("should return an onboarding step that is set at setting the pin", () => {
const onboardingState = extractOnboardingState(flagsBytes);

expect(onboardingState).not.toBeNull();
expect(onboardingState?.currentOnboardingStep).toBe(
OnboardingStep.Pin
);
});
});

describe("and the user is generating a new seed", () => {
describe("and the seed phrase type is set to 24 words", () => {
beforeEach(() => {
// 24-words seed
flagsBytes[2] |= 0 << 5;
});

it("should return a device state with the correct seed phrase type", () => {
const onboardingState = extractOnboardingState(flagsBytes);

expect(onboardingState).not.toBeNull();
expect(onboardingState?.seedPhraseType).toBe("24-words");
});

describe("and the user is writing the seed word i", () => {
beforeEach(() => {
flagsBytes[3] = 3;
});

it("should return an onboarding step that is set at writting the seed phrase", () => {
const onboardingState = extractOnboardingState(flagsBytes);

expect(onboardingState).not.toBeNull();
expect(onboardingState?.currentOnboardingStep).toBe(
OnboardingStep.NewDevice
);
});

it("should return a device state with the index of the current seed word being written", () => {
const byte3 = flagsBytes[2];
for (let wordIndex = 0; wordIndex < 24; wordIndex++) {
flagsBytes[2] = byte3 | wordIndex;

const onboardingState = extractOnboardingState(flagsBytes);

expect(onboardingState).not.toBeNull();
expect(onboardingState?.currentSeedWordIndex).toBe(wordIndex);
}
});
});

describe("and the user is confirming the seed word i", () => {
beforeEach(() => {
flagsBytes[3] = 4;
});

it("should return an onboarding step that is set at confirming the seed phrase", () => {
const onboardingState = extractOnboardingState(flagsBytes);

expect(onboardingState).not.toBeNull();
expect(onboardingState?.currentOnboardingStep).toBe(
OnboardingStep.NewDeviceConfirming
);
});

it("should return a device state with the index of the current seed word being confirmed", () => {
const byte3 = flagsBytes[2];
for (let wordIndex = 0; wordIndex < 24; wordIndex++) {
flagsBytes[2] = byte3 | wordIndex;

const onboardingState = extractOnboardingState(flagsBytes);

expect(onboardingState).not.toBeNull();
expect(onboardingState?.currentSeedWordIndex).toBe(wordIndex);
}
});
});
});
});

describe("and the user is recovering a seed", () => {
describe("and the seed phrase type is set to X words", () => {
it("should return a device state with the correct seed phrase type", () => {
const byte3 = flagsBytes[2];

// 24-words
flagsBytes[2] = byte3 | (0 << 5);
let onboardingState = extractOnboardingState(flagsBytes);

expect(onboardingState).not.toBeNull();
expect(onboardingState?.seedPhraseType).toBe("24-words");

// 18-words
flagsBytes[2] = byte3 | (1 << 5);
onboardingState = extractOnboardingState(flagsBytes);

expect(onboardingState).not.toBeNull();
expect(onboardingState?.seedPhraseType).toBe("18-words");

// 12-words
flagsBytes[2] = byte3 | (2 << 5);
onboardingState = extractOnboardingState(flagsBytes);

expect(onboardingState).not.toBeNull();
expect(onboardingState?.seedPhraseType).toBe("12-words");
});

describe("and the user is confirming (seed recovery) the seed word i", () => {
beforeEach(() => {
// 24-words seed
flagsBytes[2] |= 0 << 5;

flagsBytes[3] = 5;
});

it("should return an onboarding step that is set at confirming the restored seed phrase", () => {
const onboardingState = extractOnboardingState(flagsBytes);

expect(onboardingState).not.toBeNull();
expect(onboardingState?.currentOnboardingStep).toBe(
OnboardingStep.RestoreSeed
);
});

it("should return a device state with the index of the current seed word being confirmed", () => {
const byte3 = flagsBytes[2];
for (let wordIndex = 0; wordIndex < 24; wordIndex++) {
flagsBytes[2] = byte3 | wordIndex;

const onboardingState = extractOnboardingState(flagsBytes);

expect(onboardingState).not.toBeNull();
expect(onboardingState?.currentSeedWordIndex).toBe(wordIndex);
}
});
});
});
});

describe("and the user is on the safety warning screen", () => {
beforeEach(() => {
flagsBytes[3] = 6;
});

it("should return an onboarding step that is set at the safety warning screen", () => {
const onboardingState = extractOnboardingState(flagsBytes);

expect(onboardingState).not.toBeNull();
expect(onboardingState?.currentOnboardingStep).toBe(
OnboardingStep.SafetyWarning
);
});
});

describe("and the user finished the onboarding process", () => {
beforeEach(() => {
flagsBytes[3] = 7;
});

it("should return an onboarding step that is set at ready", () => {
const onboardingState = extractOnboardingState(flagsBytes);

expect(onboardingState).not.toBeNull();
expect(onboardingState?.currentOnboardingStep).toBe(
OnboardingStep.Ready
);
});
});
});
});
});
Loading

0 comments on commit f26fa16

Please sign in to comment.