first commit
This commit is contained in:
109
lib/api/fetchClient.ts
Normal file
109
lib/api/fetchClient.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
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();
|
||||
};
|
||||
Reference in New Issue
Block a user