trust-module-frontend/src/Charts2/Components/metricsService.jsx

157 lines
4.3 KiB
JavaScript

import { io } from 'socket.io-client';
class MetricsService {
constructor(baseUrl) {
this.baseUrl = baseUrl || window.location.origin;
this.socket = null;
this.subscriptions = new Map();
this.pendingRequests = new Map();
window.addEventListener('beforeunload', this.cleanupAll.bind(this));
window.addEventListener('pagehide', this.cleanupAll.bind(this));
window.addEventListener('beforeunload', () => {
this.cleanupAll();
});
}
connectWebSocket() {
if (this.socket) {
console.log('WebSocket already exists');
return;
}
console.log('Connecting WebSocket...');
this.socket = io(`${this.baseUrl.replace('http', 'ws')}/ws`, {
transports: ['websocket'],
withCredentials: true,
});
this.socket.on('connect', () => {
console.log('WebSocket connected');
// Восстанавливаем подписки при переподключении
this.subscriptions.forEach((_, metricKey) => {
const [metric, query] = metricKey.split('?');
const filters = this.parseFiltersFromKey(metricKey);
this.socket.emit('subscribe-metric', { metric, filters });
});
});
this.socket.on('disconnect', () => {
console.log('WebSocket disconnected');
this.socket = null;
});
this.socket.on('metrics-data', ({ metric, data, requestId }) => {
if (requestId && this.pendingRequests.has(requestId)) {
const { resolve } = this.pendingRequests.get(requestId);
resolve(data);
this.pendingRequests.delete(requestId);
return;
}
const callbacks = this.subscriptions.get(metric) || [];
callbacks.forEach(cb => cb(data));
});
this.socket.on('metrics-error', ({ error, requestId }) => {
if (requestId && this.pendingRequests.has(requestId)) {
const { reject } = this.pendingRequests.get(requestId);
reject(new Error(error));
this.pendingRequests.delete(requestId);
}
});
}
async fetchMetricsRange(metric, start, end, step = 15, filters = {}) {
return new Promise((resolve, reject) => {
this.connectWebSocket();
const requestId = `range-${Date.now()}`;
this.pendingRequests.set(requestId, { resolve, reject });
this.socket.emit('get-metrics', {
metric,
start,
end,
step,
filters,
isRangeQuery: true,
requestId
});
setTimeout(() => {
if (this.pendingRequests.has(requestId)) {
reject(new Error('Request timeout'));
this.pendingRequests.delete(requestId);
}
}, 30000);
});
}
subscribeToMetric(metricKey, callback, interval = 5000, filters = {}) {
this.connectWebSocket();
if (!this.subscriptions.has(metricKey)) {
this.subscriptions.set(metricKey, []);
const [metric] = metricKey.split('?');
this.socket.emit('subscribe-metric', {
metric,
interval,
filters
});
}
const callbacks = this.subscriptions.get(metricKey);
callbacks.push(callback);
return () => {
this.unsubscribeFromMetric(metricKey, callback);
};
}
unsubscribeFromMetric(metricKey, callback) {
const callbacks = this.subscriptions.get(metricKey) || [];
const filtered = callbacks.filter(cb => cb !== callback);
if (filtered.length === 0) {
this.subscriptions.delete(metricKey);
if (this.socket && this.socket.connected) {
const [metric] = metricKey.split('?');
this.socket.emit('unsubscribe-metric', { metric });
}
} else {
this.subscriptions.set(metricKey, filtered);
}
}
parseFiltersFromKey(metricKey) {
const parts = metricKey.split('?');
if (parts.length < 2) return {};
return parts[1].split('&').reduce((acc, pair) => {
const [key, value] = pair.split('=');
if (key && value) acc[key] = value;
return acc;
}, {});
}
cleanupAll() {
if (this.socket && this.socket.connected) {
this.socket.emit('unsubscribe-all');
}
this.subscriptions.clear();
this.disconnectWebSocket();
}
disconnectWebSocket() {
if (this.socket) {
this.socket.disconnect();
this.socket = null;
}
}
}
// Создаем экземпляр сервиса
const metricsService = new MetricsService();
export default metricsService;