Skip to content
Clone
07f791d
86 lines 2.9 KB
export type HttpService = 'auth' | 'api' | 'git'
export interface RequestOptions extends RequestInit {
baseUrl?: string
timeoutMs?: number
responseType?: 'json' | 'text' | 'blob'
auth?: 'session' | 'bearer' | 'optional-bearer' | 'public'
handleUnauthorized?: boolean
manager?: unknown
}
export interface Response<T> {
data: T
status: number
headers: Headers
}
const resolveBase = (baseUrl?: string): string => {
if (baseUrl) return baseUrl
if (typeof document === 'undefined') return ''
const value = document.querySelector<HTMLMetaElement>('meta[name="api-base-url"]')?.content?.trim() ?? ''
return value.startsWith('%') && value.endsWith('%') ? '' : value
}
const buildUrl = (path: string, baseUrl?: string): string => {
const origin = typeof location === 'undefined' ? 'http://localhost' : location.origin
const base = new URL(resolveBase(baseUrl) || origin, origin)
const absolute = /^[a-z][a-z\d+.-]*:/i.test(path)
const prefix = base.pathname.replace(/\/+$/, '')
const url = new URL(absolute ? path : `${base.origin}${prefix}/${path.replace(/^\/+/, '')}`)
if (url.origin !== base.origin) throw new TypeError('Request URL must belong to the configured service')
return url.href
}
export const serviceFetch = (
_service: HttpService,
path: string,
options: RequestOptions = {},
): Promise<globalThis.Response> => {
const {
baseUrl,
timeoutMs,
responseType: _responseType,
auth: _auth,
handleUnauthorized: _handleUnauthorized,
manager: _manager,
...init
} = options
const signal = AbortSignal.timeout(timeoutMs ?? 20_000)
return (globalThis.fetch ?? fetch)(buildUrl(path, baseUrl), {
...init,
credentials: 'include',
signal: init.signal ?? signal,
})
}
export const request = async <T>(
service: HttpService,
path: string,
options: RequestOptions = {},
): Promise<Response<T>> => {
const response = await serviceFetch(service, path, options)
const contentType = response.headers.get('Content-Type')?.toLowerCase() ?? ''
let data: unknown = null
if (options.method?.toUpperCase() !== 'HEAD' && ![204, 205, 304].includes(response.status)) {
if (
options.responseType === 'blob' ||
/^(application\/(octet-stream|zip|x-tar|gzip)|image\/|video\/|audio\/)/.test(contentType)
) {
data = await response.blob()
} else if (contentType.includes('json')) {
data = await response.json()
} else {
data = await response.text()
}
}
return { data: data as T, status: response.status, headers: response.headers }
}
export const authClient = <T>(path: string, options: RequestOptions = {}) => request<T>('auth', path, options)
export const apiClient = <T>(path: string, options: RequestOptions = {}) => request<T>('api', path, options)
export const gitClient = <T>(path: string, options: RequestOptions = {}) => request<T>('git', path, options)
export const configureRequests = (_options: unknown): void => {}