Conversation
6d0b107 to
3bd32ee
Compare
| // and is best covered by integration tests. | ||
|
|
||
| describe('InvalidTokenError', () => { | ||
| it('has the correct name and message', () => { |
There was a problem hiding this comment.
I don't really see the point of this test. What about tests for the other functions in userAccountData? Maybe these got lost in the shuffle.
There was a problem hiding this comment.
Yea I'll remove this, I guess my initial iterations on the approach required tests for each module. To test this would require complicated mocks that is probably better as integration tests.
| } | ||
| } | ||
|
|
||
| function transformAttachedClientsResponse(response: any[]): AttachedClient[] { |
| const accountData: Partial<AccountState> = {}; | ||
|
|
||
| if (accountResult.status === 'fulfilled') { | ||
| Object.assign(accountData, transformAccountResponse(accountResult.value)); |
There was a problem hiding this comment.
No big reason, the object changes three times and this seemed a little cleaner?
There was a problem hiding this comment.
The issue I have with doing this is it essentially by passes type checking.
|
|
||
| try { | ||
| const [accountResult, profileResult, attachedClientsResult] = | ||
| await Promise.allSettled([ |
There was a problem hiding this comment.
Why use .allSettled instead of .all?
There was a problem hiding this comment.
Added note above, but this allows seeing all the errors vs just failing with first. Theres probably cases where we want to still get account data if the profile or attached devices fail for some reason.
There was a problem hiding this comment.
My immediate concern when I see this kind of stuff is that we are essentailly quietly supresing errors for functionality that should not be failing... As a result we might never ever get wind of the failures, and give users a false impressions about the state of their account.
I like that a failure on a single call doesn't brick the page, but I think we should capture a Sentry error, and ideally we should notify the user that some sort of problem has occurred.
| getFullAccountData, | ||
| } from './account-storage'; | ||
|
|
||
| const { __mock: store } = require('./storage'); |
There was a problem hiding this comment.
Why use require here? I feel like something is off with how this is being mocked...
There was a problem hiding this comment.
Added comment above, this was one of the tricky things I didn't know but found through iterating on the tests.
| } | ||
|
|
||
| function savedAccount() { | ||
| return store.set.mock.calls[0][1][UID]; |
There was a problem hiding this comment.
I find this function misleading... The function name implies that this will give you the account currently set / saved, but then why calls[0]? How do you know this the call you always want to reference? What if there multiple calls to store.set?
| set: jest.fn(), | ||
| remove: jest.fn(), | ||
| }; | ||
| return { __esModule: true, __mock: mock, default: { factory: () => mock } }; |
There was a problem hiding this comment.
Looks like this was just the testing convention to access mock instance.
| const currentAccountUid = useLocalStorageSync('currentAccountUid') as string | undefined; | ||
|
|
||
| const accountState = useMemo<AccountState>(() => { | ||
| void accountsData; // Reference to trigger recompute on storage change |
There was a problem hiding this comment.
Is this only needed cause we are inside useMemo? Do we realy need to useMemo here? If unifiedToAccountData is an expensive call, we should note that's why we are using useMemo here.
There was a problem hiding this comment.
I removed the useMemo, it was hack to trigger recomputing unifiedToAccountState. That function is pretty cheap so it probably does not matter.
| clearExtendedAccountState(uid); | ||
| }, []); | ||
|
|
||
| const value = useMemo<AccountStateContextValue>( |
There was a problem hiding this comment.
I think a comment about why we useMemo here would be helpful.
| return context; | ||
| } | ||
|
|
||
| export function useAccountUid(): string | null { |
There was a problem hiding this comment.
I don't see what these functions get us. Why not just have useAccountState?
There was a problem hiding this comment.
Good call, I cleaned this up and simplified the code.
| return context; | ||
| } | ||
|
|
||
| export function useIsSignedIn(): boolean { |
There was a problem hiding this comment.
Same comment as before. Why not just have useAuthState?
e908f0f to
ad0842f
Compare
| } | ||
|
|
||
| /** Transform the consolidated /account endpoint response to AccountState format. */ | ||
| function transformAccountResponse(response: any): Partial<AccountState> { |
There was a problem hiding this comment.
Can we define a type here other than any?
| }; | ||
|
|
||
| const securityEvents: SecurityEvent[] = (response.securityEvents || []).map( | ||
| (e: any) => ({ |
There was a problem hiding this comment.
More uses of any. Can we clean these up?
06b66a3 to
0eef7c8
Compare
| const seen = new Set<string>(); | ||
| bounces = [...normalizedBounces, ...wildcardBounces].filter( | ||
| (bounce: any) => { | ||
| (bounce: { email: string; createdAt: number }) => { |
There was a problem hiding this comment.
Nice, thanks for updating these too!
| verified: false, | ||
| metricsEnabled: true, | ||
| sessionVerified: false, | ||
| ...defaultExtendedState, |
There was a problem hiding this comment.
The default state should be spread before the other variables. It's unusual to do it in this order, becuase anything defined above would be overridden by the defaults, which is a bit backwards and not how we humans do it :] I know there are not overrides here, but it's just not idiomatic.
| providerUid?: string; | ||
| } | ||
|
|
||
| export type ExtendedAccountState = Pick< |
There was a problem hiding this comment.
I find this name confusing... Extending to me implies adding, but this is actually reducing the 'UnifiedAccountState'... a name that I also think is a litte confusing.
Because
This pull request
account-storage.ts, unified localStorage abstraction for all account datauseAccountData.ts, React hook that fetches account data from 3 sources in parallel (auth-server consolidated endpoint, profile server, attached clients)AccountStateContext.tsx, React context backed by localStorage with cross-tab sync vialocalStorageChangeeventsAuthStateContext.tsx, React context for authentication state (session token, signed-in status)Issue
Closes: https://mozilla-hub.atlassian.net/browse/FXA-12994
Checklist
Other Information
We won't be able to fully test the flow until #19981 lands, however this PR adds all the state management needed to support that.