Files
dating-app-frontend/src/api/client.ts
Oscar f5e34f3a97 feat(src/stores/auth.store.ts): обновляет логику получения пользователя после входа и регистрации
 feat(src/components/layout/TauriTitlebar.vue): изменяет название приложения с "Daiting" на "Dating"

 feat(src/components/layout/SideNav.vue): изменяет название приложения с "Daiting" на "Dating"

 fix(README.md): исправляет название приложения с "Daiting" на "Dating"

 fix(src/api/client.ts): обновляет базовый URL API с 'localhost:3000' на 'localhost:1337'

 fix(src/views/auth/LoginView.vue): изменяет название приложения с "Daiting" на "Dating"

 fix(src/views/auth/RegisterView.vue): изменяет название приложения с "Daiting" на "Dating"

 fix(src-tauri/Cargo.toml): исправляет описание приложения с "Daiting" на "Dating"

 fix(src-tauri/tauri.conf.json): изменяет имя продукта с "Daiting" на "Dating"

 fix(vite.config.ts): обновляет порт разработки с 1420 на 3000

 fix(index.html): изменяет заголовок страницы с "Daiting" на "Dating"

 fix(dating-app-frontend-prompt.md): исправляет название приложения с "Daiting" на "Dating"

 fix(PRODUCT.md): изменяет название приложения с "Daiting" на "Dating"
2026-06-08 14:17:45 +03:00

125 lines
4.1 KiB
TypeScript

import axios from 'axios';
import type { AxiosInstance, InternalAxiosRequestConfig } from 'axios';
import { Api, HttpClient } from './api';
const BASE_URL = import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:1337';
// ─── Raw axios instance with interceptors ────────────────────────────────────
export const axiosInstance: AxiosInstance = axios.create({
baseURL: BASE_URL,
timeout: 15_000,
});
// Request interceptor — inject access token
axiosInstance.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
const token = _getAccessToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error),
);
// Response interceptor — silent token refresh on 401
let _isRefreshing = false;
let _failedQueue: Array<{ resolve: (v: unknown) => void; reject: (r: unknown) => void }> = [];
function _processQueue(error: unknown, token: string | null) {
_failedQueue.forEach(({ resolve, reject }) => {
if (error) reject(error);
else resolve(token);
});
_failedQueue = [];
}
axiosInstance.interceptors.response.use(
(response) => {
if (response.data !== null && typeof response.data === 'object' && 'data' in response.data) {
response.data = response.data.data;
}
return response;
},
async (error) => {
const originalRequest = error.config as InternalAxiosRequestConfig & { _retry?: boolean };
if (error.response?.status === 401 && !originalRequest._retry) {
if (_isRefreshing) {
return new Promise((resolve, reject) => {
_failedQueue.push({ resolve, reject });
}).then((token) => {
originalRequest.headers.Authorization = `Bearer ${token}`;
return axiosInstance(originalRequest);
});
}
originalRequest._retry = true;
_isRefreshing = true;
const refreshToken = localStorage.getItem('refreshToken');
if (!refreshToken) {
_processQueue(error, null);
_isRefreshing = false;
_redirectToLogin();
return Promise.reject(error);
}
try {
const res = await axios.post<{ data: { accessToken: string; refreshToken: string } }>(
`${BASE_URL}/api/v1/auth/refresh`,
{ refreshToken },
);
const { accessToken, refreshToken: newRefresh } = res.data.data;
_setAccessToken(accessToken);
localStorage.setItem('refreshToken', newRefresh);
_processQueue(null, accessToken);
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
return axiosInstance(originalRequest);
} catch (refreshError) {
_processQueue(refreshError, null);
_clearAuth();
_redirectToLogin();
return Promise.reject(refreshError);
} finally {
_isRefreshing = false;
}
}
return Promise.reject(error);
},
);
// ─── In-memory token storage ─────────────────────────────────────────────────
// Access token lives only in memory; refresh token lives in localStorage
let _accessToken: string | null = null;
export function _getAccessToken() { return _accessToken; }
export function _setAccessToken(token: string) { _accessToken = token; }
export function _clearAuth() {
_accessToken = null;
localStorage.removeItem('refreshToken');
}
function _redirectToLogin() {
// Dynamic import to avoid circular dependency with router
import('@/router').then(({ router }) => router.replace('/login'));
}
// ─── Typed API client ─────────────────────────────────────────────────────────
const httpClient = new HttpClient({
baseURL: BASE_URL,
securityWorker: () => {
const token = _getAccessToken();
return token ? { headers: { Authorization: `Bearer ${token}` } } : {};
},
});
// Plug our axios instance into the generated client
httpClient.instance = axiosInstance;
export const apiClient = new Api(httpClient);