-
Notifications
You must be signed in to change notification settings - Fork 115
WIP: LIVE-1792 - Onboarding #2510
base: develop
Are you sure you want to change the base?
Conversation
- polling mechanism to fetch the device state - bluetooth pairing during onboarding - navigator to go to the onboarding screen
And implement strategy to only pass serializable values in params
939e7c2
to
cddfb33
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤓 a few remarks
@@ -884,6 +884,7 @@ | |||
"nanoS": "Nano S", | |||
"nanoSP": "Nano S Plus", | |||
"nanoX": "Nano X", | |||
"nanoDev": "Nano Dev", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we call this Nano FTS for consistency, even if it means nothing?
from(getVersion(t)), | ||
).toPromise(); | ||
} catch (_) { | ||
// Silently swwallowing error, we only want to trigger the native ble pairing step |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we extract this into a hook that does the callback for us instead of having it inline? I don't like the whole try/catch there in the middle of the pairing screen if that makes sense.
const exitEarly = useHook(mySuperAwesomeHook(onDone, device, route.params))
if (route.params?.onlySelectDeviceWithoutFullAppPairing && exitEarly) return
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, what if the user rejects though? Wouldn't this also be considered a successful pairing by this logic?
We need to make sure that the thrown error below is not of the user refused or pairing unsuccessful types
deviceModelId, | ||
}); | ||
if (deviceModelId === "nanoDev") { | ||
// TODO: fix navigation typescript by adding a type def RootStackParamList to the createStackNavigator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No idea what you mean here, but seems like you do, so why not fix it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixing it means to type i would say at least BaseOnboardingNavigator (and probably BaseNavigator, but not sure of the current type of navigation because we use useNavigation()
just above) like i did with SyncOnboardingNavigator
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So it would probably be better to do it in another PR that add stuff to this one IMO
route.params?.onDone?.(device); | ||
// To avoid passing a onDone function param that is not serializable | ||
if (route.params?.onDoneNavigateTo === ScreenName.SyncOnboardingWelcome) { | ||
console.log("PairDevices: 🦮 navigate directly to SyncOnboarding"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like dogs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I follow what's happening though
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's to avoid passing a onDone
function as a route argument. It should be avoided according to the doc of react-navigation. To be sure that the navigation flow works correctly, we should only pass serialisable params
import { ScreenName } from "../../const"; | ||
import { SyncOnboardingStackParamList } from "../../components/RootNavigator/SyncOnboardingNavigator"; | ||
|
||
// FIXME: Define an initial onboarding state - or cannot have an initial state in our use case ? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It depends on whether you are showing anything before the first successful polling? Perhaps not having a state until we build it from the flags is also not a bad idea 🦐
isInRecoveryMode: false, | ||
isRecoveringSeed: false, | ||
isConfirmingSeedWords: false, | ||
seedPhraseType: "24-words", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes me think, have you tested it with 12 words too? If it's a type, can't we make it an enum so that we are not playing with strings here? SeedType.TwentyFour
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In typescript i would stick with a union type of string literals (like i did for SeedPhraseType):
- a string enum (ex
enum SeedType { TwentyFour: "24-words"}
) is transpiled in JS to a weird checking function - a numeric enum (ex
enum SeedType { TwentyFour }
) when debugging will only be numbers - you can't extend an enum (even if in our case of SeedType we don't need it) without modifying its values
- I feel the advantages of union types of string literals over enums are bigger than the drawbacks
But it's true that this is really a TypeScript thing. I would say we should all use the same (either enums, either union of string literals)
WDYT ?
And yeah, i tested with 12 and 18 words ;) i put the value "24-words" as the initial state because i'm still not sure what to do with the initial state ahah (maybe i should allow for the OnboardingState
a seedPhraseType
of type SeedPhraseType | null
? (here)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But IMO it's better that we all follow the same convention. As there is enum for DeviceModelId it's maybe better to go with enum (or enum for stuff we know won't change / won't need to be extended ?) ?
(but still think that string literals are better in TS 🙈
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the end i've used enum
because the SeedPhraseType
is fixed and will not be extended by another type + union of string literals in TS (at least in the current version of TS we have) needs to add a as SeedPhraseType
(for ex "24-words as SeedPhraseType
when using them in another context (in LLM for ex), so i needed to import SeedPhraseType
and it ended up being annoying
console.log(`SyncOnboarding: ▶️ Polling ${i}`); | ||
}), | ||
concatMap(() => | ||
withDevice(device.deviceId)(t => from(getVersion(t))), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we simplify this greatly with
withDevice(deviceId)((t => from(getVersion(t))).pipe(retryWhen(retryWhileErrors(acceptError)))
Where acceptError
does something like this.
onboardingStatePollingSubscription = timer(0, pollingFrequencyMs) | ||
.pipe( | ||
tap(i => { | ||
console.log(`SyncOnboarding: ▶️ Polling ${i}`); | ||
}), | ||
concatMap(() => | ||
withDevice(device.deviceId)(t => from(getVersion(t))), | ||
), | ||
retryWhen(errors => | ||
errors.pipe( | ||
mergeMap(error => { | ||
// Transport error: retry polling | ||
if ( | ||
error && | ||
error instanceof TransportStatusError && | ||
// @ts-expect-error TransportStatusError is not a class | ||
error.statusCode === 0x6d06 | ||
) { | ||
console.log( | ||
`SyncOnboarding: 0x6d06 error 🔨 ${JSON.stringify(error)}`, | ||
); | ||
return of(error); | ||
} | ||
// Disconnection error: retry polling | ||
if ( | ||
error && | ||
error instanceof Error && | ||
error.name === "DisconnectedDevice" | ||
) { | ||
console.log( | ||
`SyncOnboarding: disconnection error 🔌 ${JSON.stringify( | ||
error, | ||
)}`, | ||
); | ||
return of(error); | ||
} | ||
|
||
console.log( | ||
`SyncOnboarding: 💥 Error ${error} -> ${JSON.stringify( | ||
error, | ||
)}`, | ||
); | ||
return throwError(error); | ||
}), | ||
tap(() => console.log("Going to retry in 🕐️ ...")), | ||
delayWhen(() => timer(pollingFrequencyMs)), | ||
tap(() => console.log("Retrying 🏃️ !")), | ||
), | ||
), | ||
map((deviceVersion: FirmwareInfo) => | ||
extractOnboardingState(deviceVersion.flags), | ||
), | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely move this to live-common
// When reaching the synchronized onboarding, the device could already | ||
// be BLE paired to the phone and at the same time not known by LLM, | ||
// it does not matter. | ||
// So as long as the device is not onboarded, the LLM will ask for a pairing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will technically not ask for a pairing but rather send the getVersion from https://github.com/LedgerHQ/ledger-live-mobile/pull/2510/files#diff-e4fd4347723086bdb5b2f409bfa15efd6ae4eb4ba7f7fc051c7edb8fe4383ceeR129 and actually work.
New onboarding flow. A video 📹 is available on channel
live-803
Type
Feature. Only the logic, should not be merged.
Context
PR open for comments, for ticket LIVE-1792.
Dependencies
This branch depends on this branch of live-common