183 lines
4.6 KiB
JavaScript
Executable File
183 lines
4.6 KiB
JavaScript
Executable File
let refreshPromise = null;
|
||
|
||
const AUTH_KEYS = ['token', 'role', 'departmentId', 'userId'];
|
||
|
||
export function getToken() {
|
||
return localStorage.getItem('token');
|
||
}
|
||
|
||
export function isAuthenticatedAsAdmin() {
|
||
const role = localStorage.getItem('role');
|
||
return getToken() && role === 'ADMIN';
|
||
}
|
||
|
||
export function isAuthenticatedAsRole(expectedRole) {
|
||
const role = localStorage.getItem('role');
|
||
return getToken() && role === expectedRole;
|
||
}
|
||
|
||
export function isAuthenticatedAsAny(roles) {
|
||
const role = localStorage.getItem('role');
|
||
return getToken() && roles.includes(role);
|
||
}
|
||
|
||
function getHeaders(contentType = 'application/json') {
|
||
const headers = {};
|
||
const token = getToken();
|
||
if (token) {
|
||
headers.Authorization = `Bearer ${token}`;
|
||
}
|
||
if (contentType) {
|
||
headers['Content-Type'] = contentType;
|
||
}
|
||
return headers;
|
||
}
|
||
|
||
export async function apiFetch(endpoint, method = 'GET', body = null, retryOnUnauthorized = true) {
|
||
const options = {
|
||
method,
|
||
headers: getHeaders(body ? 'application/json' : null),
|
||
credentials: 'same-origin'
|
||
};
|
||
|
||
if (body) {
|
||
options.body = JSON.stringify(body);
|
||
}
|
||
|
||
const response = await fetch(endpoint, options);
|
||
|
||
let data;
|
||
try {
|
||
data = await response.json();
|
||
} catch (e) {
|
||
data = null;
|
||
}
|
||
|
||
if (response.status === 401 && retryOnUnauthorized) {
|
||
const refreshed = await refreshAccessToken();
|
||
if (refreshed) {
|
||
return apiFetch(endpoint, method, body, false);
|
||
}
|
||
clearAuthState();
|
||
window.location.href = '/';
|
||
}
|
||
|
||
if (!response.ok) {
|
||
throw new Error(data?.message || `Ошибка HTTP: ${response.status}`);
|
||
}
|
||
|
||
return data;
|
||
}
|
||
|
||
export async function refreshAccessToken() {
|
||
if (refreshPromise) {
|
||
return refreshPromise;
|
||
}
|
||
|
||
refreshPromise = (async () => {
|
||
const response = await fetch('/api/auth/refresh', {
|
||
method: 'POST',
|
||
credentials: 'same-origin'
|
||
});
|
||
const data = await response.json().catch(() => null);
|
||
if (!response.ok || !data?.token) {
|
||
return false;
|
||
}
|
||
storeAuthState(data);
|
||
return true;
|
||
})().finally(() => {
|
||
refreshPromise = null;
|
||
});
|
||
|
||
return refreshPromise;
|
||
}
|
||
|
||
const cache = new Map();
|
||
|
||
const CACHABLE_PREFIXES = [
|
||
'/api/departments',
|
||
'/api/specialties',
|
||
'/api/education-forms',
|
||
'/api/users/teachers',
|
||
'/api/equipments',
|
||
'/api/classrooms'
|
||
];
|
||
|
||
function invalidateCache(url) {
|
||
const cacheKeysToClear = [
|
||
'/api/departments',
|
||
'/api/specialties',
|
||
'/api/education-forms',
|
||
'/api/users',
|
||
'/api/equipments',
|
||
'/api/classrooms'
|
||
];
|
||
for (const prefix of cacheKeysToClear) {
|
||
if (url.includes(prefix)) {
|
||
for (const cacheKey of cache.keys()) {
|
||
if (cacheKey.includes(prefix)) {
|
||
cache.delete(cacheKey);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
export function clearCache() {
|
||
cache.clear();
|
||
}
|
||
|
||
export async function logout() {
|
||
try {
|
||
await fetch('/api/auth/logout', {
|
||
method: 'POST',
|
||
credentials: 'same-origin'
|
||
});
|
||
} catch (e) {
|
||
console.warn('Не удалось завершить серверную сессию:', e.message);
|
||
} finally {
|
||
clearAuthState();
|
||
clearCache();
|
||
}
|
||
}
|
||
|
||
export function storeAuthState(data) {
|
||
if (data.token) localStorage.setItem('token', data.token);
|
||
if (data.role) localStorage.setItem('role', data.role);
|
||
if (data.departmentId !== undefined && data.departmentId !== null) {
|
||
localStorage.setItem('departmentId', data.departmentId);
|
||
}
|
||
if (data.userId !== undefined && data.userId !== null) {
|
||
localStorage.setItem('userId', data.userId);
|
||
}
|
||
}
|
||
|
||
export function clearAuthState() {
|
||
AUTH_KEYS.forEach(key => localStorage.removeItem(key));
|
||
}
|
||
|
||
export const api = {
|
||
get: async (url) => {
|
||
const isCachable = CACHABLE_PREFIXES.some(prefix => url.startsWith(prefix));
|
||
if (isCachable) {
|
||
if (!cache.has(url)) {
|
||
cache.set(url, await apiFetch(url, 'GET'));
|
||
}
|
||
return cache.get(url);
|
||
}
|
||
return apiFetch(url, 'GET');
|
||
},
|
||
post: async (url, body) => {
|
||
invalidateCache(url);
|
||
return apiFetch(url, 'POST', body);
|
||
},
|
||
put: async (url, body) => {
|
||
invalidateCache(url);
|
||
return apiFetch(url, 'PUT', body);
|
||
},
|
||
delete: async (url, body = null) => {
|
||
invalidateCache(url);
|
||
return apiFetch(url, 'DELETE', body);
|
||
}
|
||
};
|