-
Notifications
You must be signed in to change notification settings - Fork 355
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #346 from LedgerHQ/feat/FAT-13-sync-onboarding-logic
feat/FAT-13 Sync Onboarding logic
- Loading branch information
Showing
11 changed files
with
1,190 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
251
libs/ledger-live-common/src/hw/extractOnboardingState.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.