initial commit. Cloned timetracker repository
This commit is contained in:
77
bo/src/composable/useFetchJson.ts
Normal file
77
bo/src/composable/useFetchJson.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
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/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}`
|
||||
}
|
||||
Reference in New Issue
Block a user