Skip to content

Commit d909267

Browse files
authored
fix: refresh token issue (#546)
1 parent 3141a6d commit d909267

File tree

4 files changed

+34
-54
lines changed

4 files changed

+34
-54
lines changed

ui/leafwiki-ui/package-lock.json

Lines changed: 1 addition & 25 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ui/leafwiki-ui/src/components/ui/button.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ const buttonVariants = cva(
3535
)
3636

3737
export interface ButtonProps
38-
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
38+
extends
39+
React.ButtonHTMLAttributes<HTMLButtonElement>,
3940
VariantProps<typeof buttonVariants> {
4041
asChild?: boolean
4142
}

ui/leafwiki-ui/src/lib/api/auth.ts

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -71,16 +71,13 @@ export async function logout() {
7171
}).catch(() => {})
7272
}
7373

74-
let isRefreshing = false // to prevent multiple simultaneous refreshes
75-
let refreshPromise: Promise<void> | null = null
76-
7774
export async function fetchWithAuth(
7875
path: string,
7976
options: RequestInit = {},
8077
retry = true,
8178
): Promise<unknown> {
8279
const store = useSessionStore.getState()
83-
const logout = store.logout
80+
const sessionLogout = store.logout
8481
const authDisabled = useConfigStore.getState().authDisabled
8582

8683
const headers = new Headers(options.headers || {})
@@ -118,21 +115,13 @@ export async function fetchWithAuth(
118115
let res = await doFetch()
119116

120117
if (res.status === 401 && retry && !authDisabled) {
121-
if (!isRefreshing) {
122-
isRefreshing = true
123-
refreshPromise = refreshAccessToken().finally(() => {
124-
isRefreshing = false
125-
refreshPromise = null
126-
})
127-
}
128-
129118
try {
130-
await refreshPromise
119+
await ensureRefresh()
131120
res = await doFetch()
132121
} catch {
133122
// Refresh token failed, log out the user
134123
if (!authDisabled) {
135-
logout()
124+
sessionLogout()
136125
const { setUser } = useSessionStore.getState()
137126
setUser(null)
138127
}
@@ -161,6 +150,32 @@ export async function fetchWithAuth(
161150
}
162151
}
163152

153+
declare global {
154+
// Explicitly initialized below; runtime value is either a Promise or null
155+
var __leafwikiRefreshPromise: Promise<void> | null
156+
}
157+
158+
// Ensure the global is initialized to a known value at module load time.
159+
if (typeof globalThis.__leafwikiRefreshPromise === 'undefined') {
160+
globalThis.__leafwikiRefreshPromise = null
161+
}
162+
/**
163+
* Ensures there is only ONE refresh in-flight across the whole runtime (even if module is duplicated).
164+
*/
165+
export function ensureRefresh(): Promise<void> {
166+
const { authDisabled } = useConfigStore.getState()
167+
if (authDisabled) {
168+
return Promise.resolve()
169+
}
170+
171+
if (!globalThis.__leafwikiRefreshPromise) {
172+
globalThis.__leafwikiRefreshPromise = refreshAccessToken().finally(() => {
173+
globalThis.__leafwikiRefreshPromise = null
174+
})
175+
}
176+
return globalThis.__leafwikiRefreshPromise
177+
}
178+
164179
async function refreshAccessToken() {
165180
const store = useSessionStore.getState()
166181

ui/leafwiki-ui/src/lib/bootstrapAuth.ts

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useSessionStore } from '@/stores/session'
22
import { useEffect } from 'react'
3-
import { API_BASE_URL } from './config'
3+
import { ensureRefresh } from './api/auth'
44

55
export function useBootstrapAuth(enabled = true) {
66
const setUser = useSessionStore((s) => s.setUser)
@@ -16,19 +16,7 @@ export function useBootstrapAuth(enabled = true) {
1616
;(async () => {
1717
setRefreshing(true)
1818
try {
19-
const res = await fetch(`${API_BASE_URL}/api/auth/refresh-token`, {
20-
method: 'POST',
21-
credentials: 'include',
22-
})
23-
24-
if (!res.ok) {
25-
// Clear user state when refresh fails to prevent stale data
26-
if (!cancelled) setUser(null)
27-
return
28-
}
29-
30-
const data = await res.json()
31-
if (!cancelled) setUser(data.user)
19+
await ensureRefresh()
3220
} catch (err) {
3321
// Clear user state when refresh fails to prevent stale data
3422
if (!cancelled) setUser(null)

0 commit comments

Comments
 (0)