62 lines
1.8 KiB
TypeScript
62 lines
1.8 KiB
TypeScript
/**
|
|
* Server-only fetch service.
|
|
* Import as: import { serverFetch } from '@/lib/api/server'
|
|
*
|
|
* Use this in Server Components and Server Actions — never in client components.
|
|
* All errors throw ApiError; callers decide whether to notFound(), redirect(), or surface an error boundary.
|
|
*/
|
|
import { headers } from 'next/headers';
|
|
|
|
import { API_URL } from '@/config';
|
|
import { COOKIE_NAMES } from '@/lib/cookies';
|
|
import { HEADER_NAMES } from '@/constants';
|
|
import { getServerCookie } from '@/lib/cookies/server';
|
|
import { ApiError } from './errors';
|
|
|
|
async function parseBody(response: Response): Promise<{ message?: string; code?: string }> {
|
|
try {
|
|
return await response.json();
|
|
} catch {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
export async function serverFetch<T>(path: string, options?: RequestInit): Promise<T> {
|
|
const token = await getServerCookie(COOKIE_NAMES.ACCESS_TOKEN);
|
|
|
|
let locale = 'fa';
|
|
try {
|
|
const reqHeaders = await headers();
|
|
locale = reqHeaders.get(HEADER_NAMES.LOCALE) ?? 'fa';
|
|
} catch {
|
|
// Outside request context (build-time prerendering) — use default locale
|
|
}
|
|
|
|
const reqHeaders: Record<string, string> = {
|
|
'Content-Type': 'application/json',
|
|
'Accept-Language': locale,
|
|
};
|
|
if (token) {
|
|
reqHeaders['Authorization'] = `Bearer ${token}`;
|
|
}
|
|
|
|
let response: Response;
|
|
try {
|
|
response = await fetch(`${API_URL}${path}`, {
|
|
cache: 'no-store',
|
|
...options,
|
|
headers: { ...reqHeaders, ...(options?.headers as Record<string, string>) },
|
|
});
|
|
} catch {
|
|
throw new ApiError(0, 'Network error');
|
|
}
|
|
|
|
if (response.ok) {
|
|
if (response.status === 204) return undefined as T;
|
|
return response.json() as Promise<T>;
|
|
}
|
|
|
|
const body = await parseBody(response);
|
|
throw new ApiError(response.status, body.message ?? response.statusText, body.code);
|
|
}
|