Updates
This commit is contained in:
+52
-28
@@ -37,7 +37,6 @@ import type {
|
||||
} from '$lib/types';
|
||||
import { getStoredAdminSession, getStoredClientSession } from '$lib/session';
|
||||
|
||||
const DEFAULT_API_PORT = env.PUBLIC_API_PORT || '8000';
|
||||
const BACKEND_UNAVAILABLE_MESSAGE = 'Unable to reach the server. Check that the backend is running and try again.';
|
||||
|
||||
type AuthMode = 'none' | 'client' | 'admin' | 'manager';
|
||||
@@ -51,40 +50,62 @@ function getApiBaseUrl() {
|
||||
}
|
||||
}
|
||||
|
||||
const configuredBaseUrl = env.PUBLIC_API_BASE_URL?.trim();
|
||||
if (configuredBaseUrl) {
|
||||
return configuredBaseUrl.replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
if (browser) {
|
||||
return `${window.location.protocol}//${window.location.hostname}:${DEFAULT_API_PORT}`;
|
||||
const configuredBaseUrl = env.PUBLIC_API_BASE_URL?.trim();
|
||||
if (configuredBaseUrl) {
|
||||
try {
|
||||
const configuredUrl = new URL(configuredBaseUrl, window.location.origin);
|
||||
// Keep browser API traffic same-origin by default. This avoids CORS,
|
||||
// CSP `connect-src`, and cookie policy failures when the backend is
|
||||
// reverse-proxied under `/api` on the same host.
|
||||
if (configuredUrl.origin === window.location.origin || configuredUrl.hostname === window.location.hostname) {
|
||||
return '';
|
||||
}
|
||||
return configuredUrl.toString().replace(/\/+$/, '');
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
return `http://127.0.0.1:${DEFAULT_API_PORT}`;
|
||||
const defaultApiPort = env.PUBLIC_API_PORT || '8000';
|
||||
return `http://127.0.0.1:${defaultApiPort}`;
|
||||
}
|
||||
|
||||
function buildApiUrl(path: string) {
|
||||
return `${getApiBaseUrl()}${path}`;
|
||||
}
|
||||
|
||||
function getToken(auth: AuthMode) {
|
||||
if (!browser) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function getSessionFingerprint(auth: AuthMode) {
|
||||
if (auth === 'client') {
|
||||
return getStoredClientSession()?.token ?? null;
|
||||
const session = getStoredClientSession();
|
||||
return session ? `${session.role}:${session.email}:${session.user_id ?? ''}` : '';
|
||||
}
|
||||
|
||||
if (auth === 'admin') {
|
||||
return getStoredAdminSession()?.token ?? null;
|
||||
const session = getStoredAdminSession();
|
||||
return session ? `${session.role}:${session.email}` : '';
|
||||
}
|
||||
|
||||
if (auth === 'manager') {
|
||||
return getStoredAdminSession()?.token ?? getStoredClientSession()?.token ?? null;
|
||||
const admin = getStoredAdminSession();
|
||||
if (admin) {
|
||||
return `${admin.role}:${admin.email}`;
|
||||
}
|
||||
const client = getStoredClientSession();
|
||||
return client ? `${client.role}:${client.email}:${client.user_id ?? ''}` : '';
|
||||
}
|
||||
|
||||
return null;
|
||||
return '';
|
||||
}
|
||||
|
||||
function resolveRequestUrl(path: string, fetcher: ApiFetch) {
|
||||
if (fetcher !== fetch) {
|
||||
return path;
|
||||
}
|
||||
return buildApiUrl(path);
|
||||
}
|
||||
|
||||
function normalizeRequestError(error: unknown) {
|
||||
@@ -107,9 +128,8 @@ function normalizeRequestError(error: unknown) {
|
||||
|
||||
async function fetchJson<T>(path: string, fallback: T, auth: AuthMode = 'none', fetcher: ApiFetch = fetch): Promise<T> {
|
||||
try {
|
||||
const token = getToken(auth);
|
||||
const response = await fetcher(buildApiUrl(path), {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : undefined
|
||||
const response = await fetcher(resolveRequestUrl(path, fetcher), {
|
||||
credentials: 'include'
|
||||
});
|
||||
if (!response.ok) {
|
||||
if (auth !== 'none') {
|
||||
@@ -136,8 +156,8 @@ const inflightRequests = new Map<string, Promise<unknown>>();
|
||||
const READ_CACHE_TTL_MS = 30_000;
|
||||
|
||||
function makeCacheKey(path: string, auth: AuthMode) {
|
||||
const token = browser ? getToken(auth) ?? '' : '';
|
||||
return `${auth}:${token.slice(-8)}:${path}`;
|
||||
const sessionFingerprint = browser ? getSessionFingerprint(auth) : '';
|
||||
return `${auth}:${sessionFingerprint}:${path}`;
|
||||
}
|
||||
|
||||
async function cachedFetchJson<T>(
|
||||
@@ -189,13 +209,12 @@ async function request<T>(
|
||||
fetcher: ApiFetch = fetch
|
||||
): Promise<T> {
|
||||
try {
|
||||
const token = getToken(auth);
|
||||
const response = await fetcher(buildApiUrl(path), {
|
||||
const response = await fetcher(resolveRequestUrl(path, fetcher), {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
...(options.headers ?? {})
|
||||
},
|
||||
credentials: 'include',
|
||||
...options
|
||||
});
|
||||
|
||||
@@ -218,6 +237,9 @@ async function request<T>(
|
||||
// after the user creates or updates anything.
|
||||
clearApiCache();
|
||||
}
|
||||
if (response.status === 204) {
|
||||
return undefined as T;
|
||||
}
|
||||
return (await response.json()) as T;
|
||||
} catch (error) {
|
||||
throw normalizeRequestError(error);
|
||||
@@ -230,9 +252,8 @@ async function requestBlob(
|
||||
fetcher: ApiFetch = fetch
|
||||
): Promise<Blob> {
|
||||
try {
|
||||
const token = getToken(auth);
|
||||
const response = await fetcher(buildApiUrl(path), {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : undefined
|
||||
const response = await fetcher(resolveRequestUrl(path, fetcher), {
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -326,6 +347,9 @@ export const api = {
|
||||
}),
|
||||
clientSession: (fetcher?: ApiFetch) => request<LoginResponse>('/api/auth/client/session', { method: 'GET' }, 'client', fetcher),
|
||||
adminSession: (fetcher?: ApiFetch) => request<LoginResponse>('/api/auth/admin/session', { method: 'GET' }, 'admin', fetcher),
|
||||
clientLogout: () => request<void>('/api/auth/client/logout', { method: 'POST' }, 'client'),
|
||||
adminLogout: () => request<void>('/api/auth/admin/logout', { method: 'POST' }, 'admin'),
|
||||
internalLogout: () => request<void>('/api/access/logout', { method: 'POST' }, 'client'),
|
||||
login: (email: string, password: string) =>
|
||||
request<LoginResponse>('/api/auth/client/login', {
|
||||
method: 'POST',
|
||||
|
||||
Reference in New Issue
Block a user