73 lines
2.0 KiB
TypeScript
73 lines
2.0 KiB
TypeScript
function isLoopbackHostname(hostname: string) {
|
|
return hostname === 'localhost' || hostname === '127.0.0.1';
|
|
}
|
|
|
|
function normalizeBaseUrl(baseUrl: string) {
|
|
return baseUrl.replace(/\/+$/, '');
|
|
}
|
|
|
|
function resolveApiBaseUrl() {
|
|
const configuredBaseUrl = (import.meta.env.VITE_API_BASE_URL ?? '').trim();
|
|
|
|
if (typeof window === 'undefined') {
|
|
return normalizeBaseUrl(configuredBaseUrl);
|
|
}
|
|
|
|
const fallbackBaseUrl = `${window.location.protocol}//${window.location.hostname}:4000/api`;
|
|
|
|
if (!configuredBaseUrl) {
|
|
return fallbackBaseUrl;
|
|
}
|
|
|
|
try {
|
|
const configuredUrl = new URL(configuredBaseUrl);
|
|
|
|
if (isLoopbackHostname(configuredUrl.hostname) && !isLoopbackHostname(window.location.hostname)) {
|
|
configuredUrl.hostname = window.location.hostname;
|
|
return normalizeBaseUrl(configuredUrl.toString());
|
|
}
|
|
|
|
return normalizeBaseUrl(configuredUrl.toString());
|
|
} catch {
|
|
return normalizeBaseUrl(configuredBaseUrl);
|
|
}
|
|
}
|
|
|
|
const apiBaseUrl = resolveApiBaseUrl();
|
|
|
|
export const hasApiConfig = Boolean(apiBaseUrl);
|
|
|
|
export async function apiRequest<T>(path: string, init?: RequestInit): Promise<T> {
|
|
if (!apiBaseUrl) {
|
|
throw new Error('VITE_API_BASE_URL is not configured.');
|
|
}
|
|
|
|
let response: Response;
|
|
|
|
try {
|
|
response = await fetch(`${apiBaseUrl}${path}`, {
|
|
credentials: 'include',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
...(init?.headers ?? {}),
|
|
},
|
|
...init,
|
|
});
|
|
} catch (error) {
|
|
throw new Error(
|
|
`Failed to reach the local API at ${apiBaseUrl}. Check that the API server is running and that VITE_API_BASE_URL matches the host you opened in the browser.`,
|
|
{ cause: error },
|
|
);
|
|
}
|
|
|
|
const contentType = response.headers.get('content-type') || '';
|
|
const payload = contentType.includes('application/json') ? ((await response.json()) as unknown) : null;
|
|
|
|
if (!response.ok) {
|
|
const message = payload && typeof payload === 'object' && 'error' in payload ? String(payload.error) : 'Request failed.';
|
|
throw new Error(message);
|
|
}
|
|
|
|
return payload as T;
|
|
}
|