Files
b2b/bo/src/composable/useFetchJson.ts
2026-04-03 15:53:31 +02:00

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/customer/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}`
}