110 lines
3.5 KiB
TypeScript
110 lines
3.5 KiB
TypeScript
import Cookies from "js-cookie";
|
||
|
||
// .env'deki NEXT_PUBLIC_API_BASE kullanılır (örn: http://127.0.0.1:8080). /v1 prefix'i burada eklenir.
|
||
const getBaseUrl = () => {
|
||
const base = process.env.NEXT_PUBLIC_API_BASE || "http://localhost:8080";
|
||
const normalized = base.replace(/\/$/, "");
|
||
return `${normalized}/v1`;
|
||
};
|
||
const BASE_URL = getBaseUrl();
|
||
|
||
interface FetchOptions extends RequestInit {
|
||
headers?: Record<string, string>;
|
||
}
|
||
|
||
interface AuthResponse {
|
||
access_token: string;
|
||
refresh_token: string;
|
||
}
|
||
|
||
export const fetchClient = async (endpoint: string, options: FetchOptions = {}) => {
|
||
const getAccessToken = () => Cookies.get("access_token");
|
||
const getRefreshToken = () => Cookies.get("refresh_token");
|
||
|
||
const setTokens = (access: string, refresh: string) => {
|
||
Cookies.set("access_token", access, { secure: true, sameSite: 'strict' });
|
||
Cookies.set("refresh_token", refresh, { secure: true, sameSite: 'strict' });
|
||
// Dispatch event for other tabs or parts of the app to know (optional)
|
||
if (typeof window !== "undefined") {
|
||
window.dispatchEvent(new Event("storage"));
|
||
}
|
||
};
|
||
|
||
const clearTokens = () => {
|
||
Cookies.remove("access_token");
|
||
Cookies.remove("refresh_token");
|
||
if (typeof window !== "undefined") {
|
||
try {
|
||
localStorage.removeItem("user");
|
||
} catch (e) { }
|
||
window.location.href = "/login";
|
||
}
|
||
};
|
||
|
||
const headers: Record<string, string> = {
|
||
...options.headers,
|
||
};
|
||
|
||
if (!(options.body instanceof FormData)) {
|
||
headers["Content-Type"] = "application/json";
|
||
}
|
||
|
||
const token = getAccessToken();
|
||
if (token) {
|
||
headers["Authorization"] = `Bearer ${token}`;
|
||
}
|
||
|
||
const config: RequestInit = {
|
||
...options,
|
||
headers,
|
||
};
|
||
|
||
let response = await fetch(`${BASE_URL}${endpoint}`, config);
|
||
|
||
// Handle 401 - Token Expired
|
||
if (response.status === 401) {
|
||
const refreshToken = getRefreshToken();
|
||
if (!refreshToken) {
|
||
clearTokens();
|
||
throw new Error("Session expired");
|
||
}
|
||
|
||
try {
|
||
// Attempt to refresh token
|
||
const refreshResponse = await fetch(`${BASE_URL}/auth/refresh`, {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ refresh_token: refreshToken }),
|
||
});
|
||
|
||
if (!refreshResponse.ok) {
|
||
throw new Error("Refresh failed");
|
||
}
|
||
|
||
const data: AuthResponse = await refreshResponse.json();
|
||
setTokens(data.access_token, data.refresh_token);
|
||
|
||
// Retry original request with new token
|
||
headers["Authorization"] = `Bearer ${data.access_token}`;
|
||
response = await fetch(`${BASE_URL}${endpoint}`, { ...options, headers });
|
||
} catch (error) {
|
||
clearTokens();
|
||
throw new Error("Session expired. Please login again.");
|
||
}
|
||
}
|
||
|
||
// Handle other errors
|
||
if (!response.ok) {
|
||
const errorData = await response.json().catch(() => ({}));
|
||
const errorMessage = errorData.error || errorData.message || response.statusText;
|
||
throw new Error(errorMessage);
|
||
}
|
||
|
||
// Return json if content type is json, otherwise text or null
|
||
const contentType = response.headers.get("content-type");
|
||
if (contentType && contentType.includes("application/json")) {
|
||
return response.json();
|
||
}
|
||
return response.text();
|
||
};
|