Files
calchat/apps/client/src/services/ApiClient.ts
Linus Waldowsky a42e2a7c1c refactor: add AuthGuard component and fix API client empty response handling
- Add reusable AuthGuard component for protected routes
- Move auth logic from index.tsx to (tabs)/_layout.tsx via AuthGuard
- Fix ApiClient to handle empty responses (204 No Content)
- Use useFocusEffect in chat.tsx to load messages after auth is ready
- Extract getDateKey() helper function in calendar.tsx
2026-01-25 13:03:17 +01:00

104 lines
2.5 KiB
TypeScript

import { Platform } from "react-native";
import { apiLogger } from "../logging";
import { useAuthStore } from "../stores";
const API_BASE_URL =
process.env.EXPO_PUBLIC_API_URL ||
Platform.select({
android: "http://10.0.2.2:3001/api",
default: "http://localhost:3001/api",
});
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
interface RequestOptions {
headers?: Record<string, string>;
body?: unknown;
skipAuth?: boolean;
}
function getAuthHeaders(): Record<string, string> {
const user = useAuthStore.getState().user;
apiLogger.debug(`getAuthHeaders - user: ${JSON.stringify(user)}`);
if (user?.id) {
return { "X-User-Id": user.id };
}
return {};
}
async function request<T>(
method: HttpMethod,
endpoint: string,
options?: RequestOptions,
): Promise<T> {
const start = performance.now();
apiLogger.debug(`${method} ${endpoint}`);
try {
const authHeaders = options?.skipAuth ? {} : getAuthHeaders();
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
method,
headers: {
"Content-Type": "application/json",
...authHeaders,
...options?.headers,
},
body: options?.body ? JSON.stringify(options.body) : undefined,
});
const duration = Math.round(performance.now() - start);
if (!response.ok) {
apiLogger.error(`${method} ${endpoint} - ${response.status} (${duration}ms)`);
throw new Error(`HTTP ${response.status}`);
}
apiLogger.debug(`${method} ${endpoint} - ${response.status} (${duration}ms)`);
const text = await response.text();
if (!text) {
return undefined as T;
}
return JSON.parse(text);
} catch (error) {
const duration = Math.round(performance.now() - start);
apiLogger.error(`${method} ${endpoint} failed (${duration}ms): ${error}`);
throw error;
}
}
export const ApiClient = {
get: async <T>(
endpoint: string,
options?: Omit<RequestOptions, "body">,
): Promise<T> => {
return request<T>("GET", endpoint, options);
},
post: async <T>(
endpoint: string,
body?: unknown,
options?: RequestOptions,
): Promise<T> => {
return request<T>("POST", endpoint, { ...options, body });
},
put: async <T>(
endpoint: string,
body?: unknown,
options?: RequestOptions,
): Promise<T> => {
return request<T>("PUT", endpoint, { ...options, body });
},
delete: async <T>(
endpoint: string,
options?: Omit<RequestOptions, "body">,
): Promise<T> => {
return request<T>("DELETE", endpoint, options);
},
};
export { API_BASE_URL };