fix(auth): only clear inFlightRefresh if it still points to the settling promise
This commit is contained in:
parent
0949f4c854
commit
5ce9135eba
|
|
@ -120,4 +120,34 @@ describe('refreshToken in-flight dedup', () => {
|
||||||
|
|
||||||
expect(localStorage.getItem('token')).toBeNull()
|
expect(localStorage.getItem('token')).toBeNull()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('an older refresh settling does not clobber a newer in-flight one', async () => {
|
||||||
|
// Refresh A starts and stays in flight.
|
||||||
|
const pA = refreshToken(true)
|
||||||
|
expect(postCallCount).toBe(1)
|
||||||
|
const resolveA = resolvePost
|
||||||
|
|
||||||
|
// User logs out, which drops the in-flight reference to A.
|
||||||
|
removeToken()
|
||||||
|
|
||||||
|
// Refresh B starts; it must claim the in-flight slot.
|
||||||
|
const pB = refreshToken(true)
|
||||||
|
expect(postCallCount).toBe(2)
|
||||||
|
const resolveB = resolvePost
|
||||||
|
|
||||||
|
// A settles after B started. Its cleanup must NOT null the in-flight
|
||||||
|
// slot, since that slot now belongs to B. Without the `=== p` guard,
|
||||||
|
// A's .finally would clobber B and let a concurrent caller fire a
|
||||||
|
// second parallel POST.
|
||||||
|
resolveA?.({data: {token: FAKE_TOKEN}})
|
||||||
|
await pA
|
||||||
|
|
||||||
|
// A concurrent caller while B is still in flight must dedup to B —
|
||||||
|
// no third POST.
|
||||||
|
const pB2 = refreshToken(true)
|
||||||
|
expect(postCallCount).toBe(2)
|
||||||
|
|
||||||
|
resolveB?.({data: {token: FAKE_TOKEN}})
|
||||||
|
await Promise.all([pB, pB2])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -63,10 +63,16 @@ export async function refreshToken(persist: boolean): Promise<void> {
|
||||||
if (inFlightRefresh) {
|
if (inFlightRefresh) {
|
||||||
return inFlightRefresh
|
return inFlightRefresh
|
||||||
}
|
}
|
||||||
inFlightRefresh = doRefresh(persist).finally(() => {
|
const p = doRefresh(persist)
|
||||||
inFlightRefresh = null
|
inFlightRefresh = p
|
||||||
|
// Only clear if it still points to this promise — a logout (or a newer
|
||||||
|
// refresh started after it) may have replaced inFlightRefresh meanwhile.
|
||||||
|
p.finally(() => {
|
||||||
|
if (inFlightRefresh === p) {
|
||||||
|
inFlightRefresh = null
|
||||||
|
}
|
||||||
})
|
})
|
||||||
return inFlightRefresh
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
async function doRefresh(persist: boolean): Promise<void> {
|
async function doRefresh(persist: boolean): Promise<void> {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue