612 lines
16 KiB
Markdown
612 lines
16 KiB
Markdown
|
|
# API EEV_Proj
|
|||
|
|
|
|||
|
|
## Обзор
|
|||
|
|
|
|||
|
|
API функции для взаимодействия с серверной частью проекта EEV_Proj. Все функции используют современный JavaScript и обеспечивают единообразный интерфейс для работы с сервером.
|
|||
|
|
|
|||
|
|
## Структура
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
api/
|
|||
|
|
├── auth.js # Функции аутентификации
|
|||
|
|
└── README.md # Эта документация
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## auth.js
|
|||
|
|
|
|||
|
|
### Функции аутентификации
|
|||
|
|
|
|||
|
|
#### getUsersStatus(token)
|
|||
|
|
Получает статус всех пользователей в системе.
|
|||
|
|
|
|||
|
|
**Параметры:**
|
|||
|
|
- `token` (string) - JWT токен аутентификации
|
|||
|
|
|
|||
|
|
**Возвращает:**
|
|||
|
|
- Promise<Array> - Массив пользователей с их статусами
|
|||
|
|
|
|||
|
|
**Пример использования:**
|
|||
|
|
```javascript
|
|||
|
|
import { getUsersStatus } from './api/auth.js';
|
|||
|
|
|
|||
|
|
const token = localStorage.getItem('token');
|
|||
|
|
try {
|
|||
|
|
const users = await getUsersStatus(token);
|
|||
|
|
console.log('Пользователи:', users);
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Ошибка получения статуса пользователей:', error);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### loadUserInfo(userId, token)
|
|||
|
|
Загружает информацию о конкретном пользователе.
|
|||
|
|
|
|||
|
|
**Параметры:**
|
|||
|
|
- `userId` (string|number) - ID пользователя
|
|||
|
|
- `token` (string) - JWT токен аутентификации
|
|||
|
|
|
|||
|
|
**Возвращает:**
|
|||
|
|
- Promise<Object> - Объект с информацией о пользователе
|
|||
|
|
|
|||
|
|
**Пример использования:**
|
|||
|
|
```javascript
|
|||
|
|
import { loadUserInfo } from './api/auth.js';
|
|||
|
|
|
|||
|
|
const token = localStorage.getItem('token');
|
|||
|
|
try {
|
|||
|
|
const userInfo = await loadUserInfo('123', token);
|
|||
|
|
console.log('Информация о пользователе:', userInfo);
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Ошибка загрузки информации о пользователе:', error);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Создание новых API функций
|
|||
|
|
|
|||
|
|
### Шаблон API функции
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
/**
|
|||
|
|
* Описание функции
|
|||
|
|
* @param {string} param1 - Описание параметра 1
|
|||
|
|
* @param {number} param2 - Описание параметра 2
|
|||
|
|
* @returns {Promise<Object>} Описание возвращаемого значения
|
|||
|
|
*/
|
|||
|
|
export async function apiFunction(param1, param2) {
|
|||
|
|
try {
|
|||
|
|
const token = localStorage.getItem('token');
|
|||
|
|
if (!token) {
|
|||
|
|
throw new Error('Токен не найден');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const response = await fetch(`/api/endpoint`, {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: {
|
|||
|
|
'Content-Type': 'application/json',
|
|||
|
|
'Authorization': `Bearer ${token}`
|
|||
|
|
},
|
|||
|
|
body: JSON.stringify({ param1, param2 })
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (!response.ok) {
|
|||
|
|
const errorData = await response.json().catch(() => ({}));
|
|||
|
|
throw new Error(errorData.message || `HTTP ${response.status}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return await response.json();
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Ошибка API функции:', error);
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Принципы
|
|||
|
|
|
|||
|
|
1. **Единообразие** - Все функции следуют одному паттерну
|
|||
|
|
2. **Обработка ошибок** - Всегда обрабатывайте ошибки
|
|||
|
|
3. **Валидация** - Проверяйте входные параметры
|
|||
|
|
4. **Логирование** - Логируйте ошибки для отладки
|
|||
|
|
5. **Типизация** - Используйте JSDoc для документирования типов
|
|||
|
|
|
|||
|
|
## Обработка ошибок
|
|||
|
|
|
|||
|
|
### Типы ошибок
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// Сетевые ошибки
|
|||
|
|
class NetworkError extends Error {
|
|||
|
|
constructor(message, status) {
|
|||
|
|
super(message);
|
|||
|
|
this.name = 'NetworkError';
|
|||
|
|
this.status = status;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Ошибки аутентификации
|
|||
|
|
class AuthError extends Error {
|
|||
|
|
constructor(message) {
|
|||
|
|
super(message);
|
|||
|
|
this.name = 'AuthError';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Ошибки валидации
|
|||
|
|
class ValidationError extends Error {
|
|||
|
|
constructor(message, field) {
|
|||
|
|
super(message);
|
|||
|
|
this.name = 'ValidationError';
|
|||
|
|
this.field = field;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Обработка в компонентах
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
import { apiFunction } from './api/api.js';
|
|||
|
|
|
|||
|
|
const MyComponent = () => {
|
|||
|
|
const [data, setData] = useState(null);
|
|||
|
|
const [error, setError] = useState(null);
|
|||
|
|
const [loading, setLoading] = useState(false);
|
|||
|
|
|
|||
|
|
const handleApiCall = async () => {
|
|||
|
|
try {
|
|||
|
|
setLoading(true);
|
|||
|
|
setError(null);
|
|||
|
|
|
|||
|
|
const result = await apiFunction('param1', 'param2');
|
|||
|
|
setData(result);
|
|||
|
|
} catch (error) {
|
|||
|
|
if (error.name === 'AuthError') {
|
|||
|
|
// Перенаправление на страницу входа
|
|||
|
|
navigate('/login');
|
|||
|
|
} else if (error.name === 'ValidationError') {
|
|||
|
|
// Показать ошибку валидации
|
|||
|
|
setError(`Ошибка в поле ${error.field}: ${error.message}`);
|
|||
|
|
} else {
|
|||
|
|
// Общая ошибка
|
|||
|
|
setError(error.message);
|
|||
|
|
}
|
|||
|
|
} finally {
|
|||
|
|
setLoading(false);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div>
|
|||
|
|
{loading && <div>Загрузка...</div>}
|
|||
|
|
{error && <div className="error">{error}</div>}
|
|||
|
|
{data && <div>{/* Отображение данных */}</div>}
|
|||
|
|
<button onClick={handleApiCall}>Вызвать API</button>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Кэширование
|
|||
|
|
|
|||
|
|
### Простое кэширование
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
const cache = new Map();
|
|||
|
|
|
|||
|
|
export async function cachedApiCall(key, apiFunction) {
|
|||
|
|
if (cache.has(key)) {
|
|||
|
|
const { data, timestamp } = cache.get(key);
|
|||
|
|
const now = Date.now();
|
|||
|
|
|
|||
|
|
// Кэш действителен 5 минут
|
|||
|
|
if (now - timestamp < 5 * 60 * 1000) {
|
|||
|
|
return data;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const data = await apiFunction();
|
|||
|
|
cache.set(key, { data, timestamp: Date.now() });
|
|||
|
|
return data;
|
|||
|
|
} catch (error) {
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Использование
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
import { cachedApiCall } from './api/cache.js';
|
|||
|
|
import { getUsersStatus } from './api/auth.js';
|
|||
|
|
|
|||
|
|
const loadUsers = async () => {
|
|||
|
|
const token = localStorage.getItem('token');
|
|||
|
|
return await cachedApiCall('users-status', () => getUsersStatus(token));
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Retry логика
|
|||
|
|
|
|||
|
|
### Автоматические повторы
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
/**
|
|||
|
|
* Выполняет API вызов с автоматическими повторами
|
|||
|
|
* @param {Function} apiFunction - Функция API
|
|||
|
|
* @param {number} maxRetries - Максимальное количество повторов
|
|||
|
|
* @param {number} delay - Задержка между повторами в мс
|
|||
|
|
*/
|
|||
|
|
export async function retryApiCall(apiFunction, maxRetries = 3, delay = 1000) {
|
|||
|
|
let lastError;
|
|||
|
|
|
|||
|
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|||
|
|
try {
|
|||
|
|
return await apiFunction();
|
|||
|
|
} catch (error) {
|
|||
|
|
lastError = error;
|
|||
|
|
|
|||
|
|
if (attempt === maxRetries) {
|
|||
|
|
throw lastError;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Ждем перед следующим попыткой
|
|||
|
|
await new Promise(resolve => setTimeout(resolve, delay * attempt));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Использование
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
import { retryApiCall } from './api/retry.js';
|
|||
|
|
import { getUsersStatus } from './api/auth.js';
|
|||
|
|
|
|||
|
|
const loadUsersWithRetry = async () => {
|
|||
|
|
const token = localStorage.getItem('token');
|
|||
|
|
return await retryApiCall(() => getUsersStatus(token), 3, 1000);
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Batch запросы
|
|||
|
|
|
|||
|
|
### Группировка запросов
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
/**
|
|||
|
|
* Выполняет несколько API запросов параллельно
|
|||
|
|
* @param {Array<Function>} apiFunctions - Массив функций API
|
|||
|
|
* @returns {Promise<Array>} Массив результатов
|
|||
|
|
*/
|
|||
|
|
export async function batchApiCalls(apiFunctions) {
|
|||
|
|
try {
|
|||
|
|
const results = await Promise.allSettled(apiFunctions.map(fn => fn()));
|
|||
|
|
|
|||
|
|
return results.map((result, index) => {
|
|||
|
|
if (result.status === 'fulfilled') {
|
|||
|
|
return result.value;
|
|||
|
|
} else {
|
|||
|
|
console.error(`Ошибка в запросе ${index}:`, result.reason);
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Ошибка batch запросов:', error);
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Использование
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
import { batchApiCalls } from './api/batch.js';
|
|||
|
|
import { getUsersStatus, loadUserInfo } from './api/auth.js';
|
|||
|
|
|
|||
|
|
const loadAllData = async () => {
|
|||
|
|
const token = localStorage.getItem('token');
|
|||
|
|
|
|||
|
|
const results = await batchApiCalls([
|
|||
|
|
() => getUsersStatus(token),
|
|||
|
|
() => loadUserInfo('123', token),
|
|||
|
|
() => loadUserInfo('456', token)
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
const [users, user1, user2] = results;
|
|||
|
|
return { users, user1, user2 };
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## WebSocket API
|
|||
|
|
|
|||
|
|
### Подключение
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
import { io } from 'socket.io-client';
|
|||
|
|
|
|||
|
|
class WebSocketAPI {
|
|||
|
|
constructor() {
|
|||
|
|
this.socket = null;
|
|||
|
|
this.isConnected = false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
connect(token) {
|
|||
|
|
const serverUrl = window.location.hostname === 'localhost'
|
|||
|
|
? 'http://localhost:4000'
|
|||
|
|
: window.location.origin;
|
|||
|
|
|
|||
|
|
this.socket = io(serverUrl, {
|
|||
|
|
transports: ['websocket', 'polling'],
|
|||
|
|
auth: { token },
|
|||
|
|
timeout: 20000
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
this.socket.on('connect', () => {
|
|||
|
|
this.isConnected = true;
|
|||
|
|
console.log('WebSocket подключен');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
this.socket.on('disconnect', () => {
|
|||
|
|
this.isConnected = false;
|
|||
|
|
console.log('WebSocket отключен');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return this.socket;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
disconnect() {
|
|||
|
|
if (this.socket) {
|
|||
|
|
this.socket.disconnect();
|
|||
|
|
this.socket = null;
|
|||
|
|
this.isConnected = false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
emit(event, data) {
|
|||
|
|
if (this.socket && this.isConnected) {
|
|||
|
|
this.socket.emit(event, data);
|
|||
|
|
} else {
|
|||
|
|
console.warn('WebSocket не подключен');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
on(event, callback) {
|
|||
|
|
if (this.socket) {
|
|||
|
|
this.socket.on(event, callback);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
off(event, callback) {
|
|||
|
|
if (this.socket) {
|
|||
|
|
this.socket.off(event, callback);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export const wsAPI = new WebSocketAPI();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Использование
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
import { wsAPI } from './api/websocket.js';
|
|||
|
|
|
|||
|
|
// Подключение
|
|||
|
|
const token = localStorage.getItem('token');
|
|||
|
|
wsAPI.connect(token);
|
|||
|
|
|
|||
|
|
// Отправка события
|
|||
|
|
wsAPI.emit('playerMovement', { x: 100, y: 0, z: 200 });
|
|||
|
|
|
|||
|
|
// Подписка на события
|
|||
|
|
wsAPI.on('economy:balanceChanged', ({ userId, newBalance }) => {
|
|||
|
|
console.log('Баланс изменился:', newBalance);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Отписка
|
|||
|
|
wsAPI.off('economy:balanceChanged');
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Тестирование API
|
|||
|
|
|
|||
|
|
### Mock функции
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// __mocks__/api/auth.js
|
|||
|
|
export const getUsersStatus = jest.fn();
|
|||
|
|
export const loadUserInfo = jest.fn();
|
|||
|
|
|
|||
|
|
// Сброс моков
|
|||
|
|
beforeEach(() => {
|
|||
|
|
getUsersStatus.mockClear();
|
|||
|
|
loadUserInfo.mockClear();
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Тесты
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|||
|
|
import { getUsersStatus } from './api/auth.js';
|
|||
|
|
|
|||
|
|
// Мокаем модуль
|
|||
|
|
jest.mock('./api/auth.js');
|
|||
|
|
|
|||
|
|
describe('API Functions', () => {
|
|||
|
|
it('загружает статус пользователей', async () => {
|
|||
|
|
const mockUsers = [
|
|||
|
|
{ id: 1, name: 'User 1', status: 'online' },
|
|||
|
|
{ id: 2, name: 'User 2', status: 'offline' }
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
getUsersStatus.mockResolvedValue(mockUsers);
|
|||
|
|
|
|||
|
|
const result = await getUsersStatus('token');
|
|||
|
|
|
|||
|
|
expect(result).toEqual(mockUsers);
|
|||
|
|
expect(getUsersStatus).toHaveBeenCalledWith('token');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('обрабатывает ошибки API', async () => {
|
|||
|
|
const errorMessage = 'Unauthorized';
|
|||
|
|
getUsersStatus.mockRejectedValue(new Error(errorMessage));
|
|||
|
|
|
|||
|
|
await expect(getUsersStatus('invalid-token')).rejects.toThrow(errorMessage);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Мониторинг и метрики
|
|||
|
|
|
|||
|
|
### Логирование API вызовов
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
class APIMonitor {
|
|||
|
|
constructor() {
|
|||
|
|
this.calls = [];
|
|||
|
|
this.errors = [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
logCall(endpoint, method, duration, success) {
|
|||
|
|
const call = {
|
|||
|
|
endpoint,
|
|||
|
|
method,
|
|||
|
|
duration,
|
|||
|
|
success,
|
|||
|
|
timestamp: Date.now()
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
this.calls.push(call);
|
|||
|
|
|
|||
|
|
// Ограничиваем размер массива
|
|||
|
|
if (this.calls.length > 1000) {
|
|||
|
|
this.calls.shift();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
logError(endpoint, method, error) {
|
|||
|
|
const errorLog = {
|
|||
|
|
endpoint,
|
|||
|
|
method,
|
|||
|
|
error: error.message,
|
|||
|
|
timestamp: Date.now()
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
this.errors.push(errorLog);
|
|||
|
|
|
|||
|
|
if (this.errors.length > 100) {
|
|||
|
|
this.errors.shift();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
getStats() {
|
|||
|
|
const totalCalls = this.calls.length;
|
|||
|
|
const successfulCalls = this.calls.filter(call => call.success).length;
|
|||
|
|
const errorCalls = this.errors.length;
|
|||
|
|
const avgDuration = this.calls.reduce((sum, call) => sum + call.duration, 0) / totalCalls;
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
totalCalls,
|
|||
|
|
successfulCalls,
|
|||
|
|
errorCalls,
|
|||
|
|
successRate: (successfulCalls / totalCalls) * 100,
|
|||
|
|
avgDuration
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export const apiMonitor = new APIMonitor();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Использование в API функциях
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
import { apiMonitor } from './api/monitor.js';
|
|||
|
|
|
|||
|
|
export async function monitoredApiCall(endpoint, options) {
|
|||
|
|
const startTime = Date.now();
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const response = await fetch(endpoint, options);
|
|||
|
|
const duration = Date.now() - startTime;
|
|||
|
|
|
|||
|
|
apiMonitor.logCall(endpoint, options.method || 'GET', duration, response.ok);
|
|||
|
|
|
|||
|
|
if (!response.ok) {
|
|||
|
|
throw new Error(`HTTP ${response.status}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return await response.json();
|
|||
|
|
} catch (error) {
|
|||
|
|
const duration = Date.now() - startTime;
|
|||
|
|
apiMonitor.logCall(endpoint, options.method || 'GET', duration, false);
|
|||
|
|
apiMonitor.logError(endpoint, options.method || 'GET', error);
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Безопасность
|
|||
|
|
|
|||
|
|
### Валидация токенов
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
export function validateToken(token) {
|
|||
|
|
if (!token) {
|
|||
|
|
throw new Error('Токен не предоставлен');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Проверяем формат JWT токена
|
|||
|
|
const tokenParts = token.split('.');
|
|||
|
|
if (tokenParts.length !== 3) {
|
|||
|
|
throw new Error('Неверный формат токена');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// Декодируем payload
|
|||
|
|
const payload = JSON.parse(atob(tokenParts[1]));
|
|||
|
|
|
|||
|
|
// Проверяем срок действия
|
|||
|
|
if (payload.exp && Date.now() >= payload.exp * 1000) {
|
|||
|
|
throw new Error('Токен истек');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return payload;
|
|||
|
|
} catch (error) {
|
|||
|
|
throw new Error('Неверный токен');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Санитизация данных
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
export function sanitizeInput(input) {
|
|||
|
|
if (typeof input !== 'string') {
|
|||
|
|
return input;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Удаляем потенциально опасные символы
|
|||
|
|
return input
|
|||
|
|
.replace(/[<>]/g, '')
|
|||
|
|
.replace(/javascript:/gi, '')
|
|||
|
|
.trim();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function sanitizeObject(obj) {
|
|||
|
|
const sanitized = {};
|
|||
|
|
|
|||
|
|
for (const [key, value] of Object.entries(obj)) {
|
|||
|
|
if (typeof value === 'string') {
|
|||
|
|
sanitized[key] = sanitizeInput(value);
|
|||
|
|
} else if (typeof value === 'object' && value !== null) {
|
|||
|
|
sanitized[key] = sanitizeObject(value);
|
|||
|
|
} else {
|
|||
|
|
sanitized[key] = value;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return sanitized;
|
|||
|
|
}
|
|||
|
|
```
|