e65111a7f4
Permisos (superadmin, viewer, busqueda, tareas recurrentes), bootstrap atomico, revocacion de JWT por version de sesion (028), control optimista de edicion (029), logo sin SVG + CSP, ingesta endurecida, correos escapados, borradores, paginacion de proyectos, accesibilidad de dialogos/controles e i18n. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
67 lines
2.4 KiB
JavaScript
67 lines
2.4 KiB
JavaScript
import { isTokenExpired } from '../utils/jwt.js';
|
|
|
|
const API_URL = import.meta.env.VITE_API_URL || '/api';
|
|
|
|
let onUnauthorized = () => {};
|
|
export function setUnauthorizedHandler(fn) {
|
|
onUnauthorized = fn;
|
|
}
|
|
|
|
function getToken() {
|
|
return localStorage.getItem('plc_token');
|
|
}
|
|
|
|
async function request(method, path, { body, isForm, extraHeaders } = {}) {
|
|
const headers = { ...extraHeaders };
|
|
const token = getToken();
|
|
// Nunca operar con un token vencido: se corta la sesion aqui mismo, sin enviar la peticion.
|
|
if (token && isTokenExpired(token)) {
|
|
onUnauthorized({ expired: true });
|
|
throw new Error('Sesion vencida');
|
|
}
|
|
if (token) headers.Authorization = `Bearer ${token}`;
|
|
if (!isForm && body !== undefined) headers['Content-Type'] = 'application/json';
|
|
|
|
const res = await fetch(`${API_URL}${path}`, {
|
|
method,
|
|
headers,
|
|
body: isForm ? body : body !== undefined ? JSON.stringify(body) : undefined,
|
|
});
|
|
|
|
// El servidor renueva el token de desbloqueo de Contraseñas en cada request valido (ver vault/middleware.js).
|
|
const refreshedVaultToken = res.headers.get('x-vault-token');
|
|
if (refreshedVaultToken) sessionStorage.setItem('plc_vault_token', refreshedVaultToken);
|
|
|
|
if (res.status === 401 && token) {
|
|
onUnauthorized({ expired: true });
|
|
}
|
|
|
|
if (res.status === 204) return null;
|
|
|
|
const isJson = (res.headers.get('content-type') || '').includes('application/json');
|
|
const payload = isJson ? await res.json() : null;
|
|
|
|
if (!res.ok) {
|
|
const message = payload?.error || `Error ${res.status}`;
|
|
const error = new Error(message);
|
|
error.status = res.status;
|
|
throw error;
|
|
}
|
|
return payload?.data;
|
|
}
|
|
|
|
export const api = {
|
|
get: (path, extraHeaders) => request('GET', path, { extraHeaders }),
|
|
post: (path, body, extraHeaders) => request('POST', path, { body, extraHeaders }),
|
|
put: (path, body, extraHeaders) => request('PUT', path, { body, extraHeaders }),
|
|
patch: (path, body) => request('PATCH', path, { body }),
|
|
delete: (path, extraHeaders) => request('DELETE', path, { extraHeaders }),
|
|
postForm: (path, formData) => request('POST', path, { body: formData, isForm: true }),
|
|
putForm: (path, formData) => request('PUT', path, { body: formData, isForm: true }),
|
|
setToken: (token) => {
|
|
if (token) localStorage.setItem('plc_token', token);
|
|
else localStorage.removeItem('plc_token');
|
|
},
|
|
downloadUrl: (path) => `${API_URL}${path}`,
|
|
};
|