78 lines
2.1 KiB
TypeScript
78 lines
2.1 KiB
TypeScript
import type { Resp } from '@/types'
|
|
|
|
export async function useFetchJson<T = unknown>(url: string, opt?: RequestInit): Promise<Resp<T>> {
|
|
const prefix = import.meta.env.VITE_API_URL ?? ''
|
|
const urlFull = join(prefix, url)
|
|
|
|
const headers = new Headers(opt?.headers)
|
|
if (!headers.has('Content-Type')) {
|
|
headers.set('Content-Type', 'application/json')
|
|
}
|
|
|
|
const fetchOptions: RequestInit = {
|
|
...opt,
|
|
headers,
|
|
// Always include cookies so the backend can read the HTTPOnly access_token
|
|
credentials: 'same-origin',
|
|
}
|
|
|
|
try {
|
|
const res = await fetch(urlFull, fetchOptions)
|
|
|
|
const contentType = res.headers.get('content-type') ?? ''
|
|
|
|
if (!contentType.includes('application/json')) {
|
|
throw { message: 'this is not proper json format' } as Resp<any>
|
|
}
|
|
|
|
const data = await res.json()
|
|
|
|
// Handle 401 — access token expired; try to refresh via the HTTPOnly refresh_token cookie
|
|
if (res.status === 401) {
|
|
const { useAuthStore } = await import('@/stores/user/auth')
|
|
const authStore = useAuthStore()
|
|
|
|
const refreshed = await authStore.refreshAccessToken()
|
|
|
|
if (refreshed) {
|
|
// Retry the original request — cookies are updated by the refresh endpoint
|
|
const retryRes = await fetch(urlFull, fetchOptions)
|
|
|
|
const retryContentType = retryRes.headers.get('content-type') ?? ''
|
|
if (!retryContentType.includes('application/json')) {
|
|
throw { message: 'this is not proper json format' } as Resp<any>
|
|
}
|
|
|
|
const retryData = await retryRes.json()
|
|
|
|
if (!retryRes.ok) {
|
|
throw retryData as Resp<any>
|
|
}
|
|
|
|
return retryData as Resp<T>
|
|
}
|
|
|
|
// Refresh failed — logout and propagate the error
|
|
authStore.logout()
|
|
throw data as Resp<any>
|
|
}
|
|
|
|
if (!res.ok) {
|
|
throw data as Resp<any>
|
|
}
|
|
|
|
return data as Resp<T>
|
|
} catch (error) {
|
|
throw error as Resp<any>
|
|
}
|
|
}
|
|
|
|
export function join(...parts: string[]): string {
|
|
const path = parts
|
|
.filter(Boolean)
|
|
.join('/')
|
|
.replace(/\/{2,}/g, '/')
|
|
|
|
return path.startsWith('/') ? path : `/${path}`
|
|
}
|