cs-element
Version:
Advanced reactive data management library with state machines, blueprints, persistence, compression, networking, and multithreading support
1,094 lines (1,090 loc) • 44.4 kB
JavaScript
'use strict';
var axios = require('axios');
var ws = require('ws');
// Типы сетевых операций
var NetworkOperation;
(function (NetworkOperation) {
NetworkOperation["SYNC"] = "sync";
NetworkOperation["UPLOAD"] = "upload";
NetworkOperation["DOWNLOAD"] = "download";
NetworkOperation["BROADCAST"] = "broadcast";
NetworkOperation["SUBSCRIBE"] = "subscribe";
NetworkOperation["UNSUBSCRIBE"] = "unsubscribe";
NetworkOperation["PING"] = "ping";
NetworkOperation["HEARTBEAT"] = "heartbeat";
})(NetworkOperation || (NetworkOperation = {}));
var ConnectionType;
(function (ConnectionType) {
ConnectionType["HTTP"] = "http";
ConnectionType["WEBSOCKET"] = "websocket";
ConnectionType["TCP"] = "tcp";
ConnectionType["UDP"] = "udp";
ConnectionType["GRPC"] = "grpc";
ConnectionType["MQTT"] = "mqtt";
})(ConnectionType || (ConnectionType = {}));
var SyncStrategy;
(function (SyncStrategy) {
SyncStrategy["FULL_SYNC"] = "full_sync";
SyncStrategy["INCREMENTAL_SYNC"] = "incremental_sync";
SyncStrategy["CONFLICT_RESOLUTION"] = "conflict_resolution";
SyncStrategy["MERGE"] = "merge";
SyncStrategy["OVERWRITE"] = "overwrite";
})(SyncStrategy || (SyncStrategy = {}));
var NetworkStatus;
(function (NetworkStatus) {
NetworkStatus["CONNECTED"] = "connected";
NetworkStatus["DISCONNECTED"] = "disconnected";
NetworkStatus["CONNECTING"] = "connecting";
NetworkStatus["RECONNECTING"] = "reconnecting";
NetworkStatus["ERROR"] = "error";
})(NetworkStatus || (NetworkStatus = {}));
var ConflictResolution;
(function (ConflictResolution) {
ConflictResolution["LOCAL_WINS"] = "local_wins";
ConflictResolution["REMOTE_WINS"] = "remote_wins";
ConflictResolution["TIMESTAMP_WINS"] = "timestamp_wins";
ConflictResolution["MERGE_CHANGES"] = "merge_changes";
ConflictResolution["MANUAL_RESOLUTION"] = "manual_resolution";
})(ConflictResolution || (ConflictResolution = {}));
class NetworkPlugin {
constructor(config = {}) {
this.name = 'NetworkPlugin';
this.version = '1.0.0';
this.description = 'Плагин для сетевых операций и синхронизации данных';
this.endpoints = new Map();
this.syncConfigs = new Map();
this.connections = new Map();
this.pendingRequests = new Map();
this.requestQueue = [];
this.offlineQueue = [];
this.syncResults = [];
this.events = [];
this.isOnline = true;
this.config = {
enabled: true,
priority: 75,
enableAutoSync: true,
defaultSyncInterval: 300000, // 5 минут
maxConcurrentConnections: 10,
connectionTimeout: 30000,
requestTimeout: 60000,
maxRetries: 3,
retryDelay: 1000,
enableOfflineMode: true,
offlineQueueSize: 1000,
enableCompression: true,
enableEncryption: false,
heartbeatInterval: 30000,
reconnectDelay: 5000,
maxReconnectAttempts: 5,
enableEventLogging: true,
logLevel: 'info',
...config
};
this.stats = {
totalConnections: 0,
activeConnections: 0,
failedConnections: 0,
totalRequests: 0,
successfulRequests: 0,
failedRequests: 0,
totalDataTransferred: 0,
averageLatency: 0,
averageResponseTime: 0,
syncOperations: 0,
successfulSyncs: 0,
failedSyncs: 0,
conflictsResolved: 0,
offlineOperations: 0,
uptime: 0,
errorRate: 0
};
}
install(_CSElementClass) {
// Регистрация плагина в системе CSElement
// В реальной реализации здесь была бы интеграция с жизненным циклом элементов
console.log(`${this.name} установлен для CSElement`);
}
async initialize() {
console.log(`Инициализация ${this.name} v${this.version}`);
// Мониторинг сетевого состояния
this.setupNetworkMonitoring();
// Запуск автосинхронизации
if (this.config.enableAutoSync) {
this.startAutoSync();
}
// Запуск heartbeat
this.startHeartbeat();
// Обработка очереди запросов
this.startRequestProcessor();
// Обновление статистики
setInterval(() => {
this.updateStats();
}, 60000); // каждую минуту
}
async destroy() {
if (this.syncTimer) {
clearInterval(this.syncTimer);
}
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
}
// Закрытие всех соединений
for (const connection of this.connections.values()) {
await this.closeConnection(connection.id);
}
console.log(`${this.name} деинициализирован`);
}
getConfig() {
return { ...this.config };
}
updateConfig(newConfig) {
this.config = { ...this.config, ...newConfig };
}
// Lifecycle hooks
async afterCreate(element, _context) {
if (this.config.enableAutoSync) {
await this.queueElementSync(element, 'create');
}
return { success: true };
}
async afterUpdate(element, _context) {
if (this.config.enableAutoSync) {
await this.queueElementSync(element, 'update');
}
return { success: true };
}
async afterDelete(element, _context) {
if (this.config.enableAutoSync) {
await this.queueElementSync(element, 'delete');
}
return { success: true };
}
// Управление конечными точками
addEndpoint(endpointData) {
const endpoint = {
id: this.generateId(),
...endpointData
};
this.endpoints.set(endpoint.id, endpoint);
this.log('info', `Добавлена конечная точка: ${endpoint.name}`);
return endpoint.id;
}
getEndpoint(id) {
return this.endpoints.get(id);
}
updateEndpoint(id, updates) {
const endpoint = this.endpoints.get(id);
if (!endpoint)
return false;
Object.assign(endpoint, updates);
return true;
}
deleteEndpoint(id) {
const deleted = this.endpoints.delete(id);
if (deleted) {
// Закрытие соединений с этой конечной точкой
this.closeConnectionsByEndpoint(id);
}
return deleted;
}
// Управление соединениями
async createConnection(endpointId) {
const endpoint = this.endpoints.get(endpointId);
if (!endpoint) {
throw new Error(`Конечная точка ${endpointId} не найдена`);
}
if (this.stats.activeConnections >= this.config.maxConcurrentConnections) {
throw new Error('Достигнуто максимальное количество соединений');
}
const connectionId = this.generateId();
const connection = {
id: connectionId,
endpointId,
type: endpoint.type,
status: NetworkStatus.CONNECTING,
messagesSent: 0,
messagesReceived: 0,
bytesTransferred: 0,
latency: 0,
errorCount: 0
};
this.connections.set(connectionId, connection);
this.stats.totalConnections++;
try {
await this.establishConnection(connection, endpoint);
connection.status = NetworkStatus.CONNECTED;
connection.connectedAt = new Date();
connection.lastActivity = new Date();
this.stats.activeConnections++;
this.emitEvent('connection_established', { connectionId, endpointId });
this.log('info', `Установлено соединение с ${endpoint.name}`);
return connectionId;
}
catch (error) {
connection.status = NetworkStatus.ERROR;
connection.errorCount++;
this.stats.failedConnections++;
this.log('error', `Ошибка подключения к ${endpoint.name}: ${error}`);
throw error;
}
}
async closeConnection(connectionId) {
const connection = this.connections.get(connectionId);
if (!connection)
return false;
try {
await this.terminateConnection(connection);
connection.status = NetworkStatus.DISCONNECTED;
this.stats.activeConnections--;
this.emitEvent('connection_lost', { connectionId });
this.log('info', `Соединение ${connectionId} закрыто`);
return true;
}
catch (error) {
this.log('error', `Ошибка при закрытии соединения: ${error}`);
return false;
}
}
// Конфигурация синхронизации
addSyncConfig(configData) {
const config = {
id: this.generateId(),
lastSync: undefined,
nextSync: this.calculateNextSync(configData.syncInterval),
errorCount: 0,
...configData
};
this.syncConfigs.set(config.id, config);
this.log('info', `Добавлена конфигурация синхронизации: ${config.name}`);
return config.id;
}
getSyncConfig(id) {
return this.syncConfigs.get(id);
}
updateSyncConfig(id, updates) {
const config = this.syncConfigs.get(id);
if (!config)
return false;
Object.assign(config, updates);
if (updates.syncInterval) {
config.nextSync = this.calculateNextSync(updates.syncInterval);
}
return true;
}
deleteSyncConfig(id) {
return this.syncConfigs.delete(id);
}
// Синхронизация данных
async syncData(configId) {
const config = this.syncConfigs.get(configId);
if (!config) {
throw new Error(`Конфигурация синхронизации ${configId} не найдена`);
}
const endpoint = this.endpoints.get(config.endpointId);
if (!endpoint) {
throw new Error(`Конечная точка ${config.endpointId} не найдена`);
}
const startTime = new Date();
const syncResult = {
id: this.generateId(),
configId,
startTime,
endTime: new Date(),
success: false,
strategy: config.strategy,
elementsProcessed: 0,
elementsUpdated: 0,
elementsCreated: 0,
elementsDeleted: 0,
conflicts: [],
errors: [],
statistics: {
totalElements: 0,
syncedElements: 0,
skippedElements: 0,
conflictedElements: 0,
dataTransferred: 0,
transferSpeed: 0,
networkLatency: 0
}
};
this.stats.syncOperations++;
try {
this.log('info', `Начало синхронизации: ${config.name}`);
// Получение данных для синхронизации
const localData = await this.getLocalData(config);
const remoteData = await this.getRemoteData(endpoint, config);
syncResult.statistics.totalElements = localData.length + remoteData.length;
// Выполнение синхронизации
switch (config.strategy) {
case SyncStrategy.FULL_SYNC:
await this.performFullSync(localData, remoteData, config, syncResult);
break;
case SyncStrategy.INCREMENTAL_SYNC:
await this.performIncrementalSync(localData, remoteData, config, syncResult);
break;
case SyncStrategy.CONFLICT_RESOLUTION:
await this.performConflictResolution(localData, remoteData, config, syncResult);
break;
default:
throw new Error(`Неподдерживаемая стратегия синхронизации: ${config.strategy}`);
}
syncResult.success = true;
syncResult.endTime = new Date();
config.lastSync = new Date();
config.nextSync = this.calculateNextSync(config.syncInterval);
config.errorCount = 0;
this.stats.successfulSyncs++;
this.stats.lastSyncTime = new Date();
this.emitEvent('sync_completed', { configId, success: true });
this.log('info', `Синхронизация завершена успешно: ${config.name}`);
}
catch (error) {
syncResult.success = false;
syncResult.errors.push(error instanceof Error ? error.message : String(error));
config.errorCount++;
this.stats.failedSyncs++;
if (config.errorCount >= config.maxErrors) {
config.enabled = false;
this.log('warn', `Синхронизация ${config.name} отключена из-за превышения количества ошибок`);
}
this.log('error', `Ошибка синхронизации ${config.name}: ${error}`);
}
this.syncResults.push(syncResult);
return syncResult;
}
// Отправка запросов
async sendRequest(request) {
const networkRequest = {
id: this.generateId(),
timestamp: new Date(),
...request
};
if (!this.isOnline && this.config.enableOfflineMode) {
return this.queueOfflineRequest(networkRequest);
}
return this.executeRequest(networkRequest);
}
// Управление сообщениями
async sendMessage(connectionId, message) {
const connection = this.connections.get(connectionId);
if (!connection || connection.status !== NetworkStatus.CONNECTED) {
return false;
}
const networkMessage = {
id: this.generateId(),
timestamp: new Date(),
...message
};
try {
await this.transmitMessage(connection, networkMessage);
connection.messagesSent++;
connection.lastActivity = new Date();
return true;
}
catch (error) {
connection.errorCount++;
this.log('error', `Ошибка отправки сообщения: ${error}`);
return false;
}
}
// Утилиты
async establishConnection(connection, endpoint) {
const startTime = Date.now();
try {
switch (endpoint.type) {
case ConnectionType.HTTP:
// HTTP соединение не требует постоянного подключения
connection.status = NetworkStatus.CONNECTED;
break;
case ConnectionType.WEBSOCKET:
await this.establishWebSocketConnection(connection, endpoint);
break;
case ConnectionType.TCP:
await this.establishTCPConnection(connection, endpoint);
break;
default:
throw new Error(`Неподдерживаемый тип соединения: ${endpoint.type}`);
}
connection.connectedAt = new Date();
connection.lastActivity = new Date();
connection.latency = Date.now() - startTime;
this.emitEvent('connection_established', {
connectionId: connection.id,
endpointId: endpoint.id,
latency: connection.latency
});
}
catch (error) {
connection.status = NetworkStatus.ERROR;
connection.errorCount++;
this.emitEvent('error_occurred', {
connectionId: connection.id,
error: error instanceof Error ? error.message : String(error)
});
throw error;
}
}
async establishWebSocketConnection(connection, endpoint) {
return new Promise((resolve, reject) => {
try {
// Используем динамический импорт для WebSocket в браузере или Node.js
if (typeof window !== 'undefined' && window.WebSocket) {
// Браузерная среда
const ws$1 = new ws.WebSocket(endpoint.url);
ws$1.onopen = () => {
connection.socket = ws$1;
connection.status = NetworkStatus.CONNECTED;
this.setupWebSocketHandlers(connection, ws$1);
resolve();
};
ws$1.onerror = (error) => {
reject(new Error(`WebSocket connection failed: ${error}`));
};
}
else {
// Node.js среда
const WebSocket = require('ws');
const ws = new WebSocket(endpoint.url, {
headers: endpoint.headers,
handshakeTimeout: endpoint.timeout
});
ws.on('open', () => {
connection.socket = ws;
connection.status = NetworkStatus.CONNECTED;
this.setupWebSocketHandlers(connection, ws);
resolve();
});
ws.on('error', (error) => {
reject(new Error(`WebSocket connection failed: ${error.message}`));
});
}
// Таймаут подключения
setTimeout(() => {
if (connection.status !== NetworkStatus.CONNECTED) {
reject(new Error('WebSocket connection timeout'));
}
}, endpoint.timeout);
}
catch (error) {
reject(error);
}
});
}
async establishTCPConnection(connection, endpoint) {
return new Promise((resolve, reject) => {
try {
const net = require('net');
const url = new URL(endpoint.url);
const port = parseInt(url.port) || 80;
const host = url.hostname;
const socket = net.createConnection({ port, host }, () => {
connection.socket = socket;
connection.status = NetworkStatus.CONNECTED;
this.setupTCPHandlers(connection, socket);
resolve();
});
socket.on('error', (error) => {
reject(new Error(`TCP connection failed: ${error.message}`));
});
socket.setTimeout(endpoint.timeout, () => {
socket.destroy();
reject(new Error('TCP connection timeout'));
});
}
catch (error) {
reject(error);
}
});
}
setupWebSocketHandlers(connection, ws) {
ws.onmessage = (event) => {
connection.messagesReceived++;
connection.lastActivity = new Date();
try {
const message = JSON.parse(event.data);
this.handleIncomingMessage(connection, message);
}
catch (error) {
this.log('error', `Ошибка парсинга WebSocket сообщения: ${error}`);
}
};
ws.onclose = () => {
connection.status = NetworkStatus.DISCONNECTED;
this.emitEvent('connection_lost', { connectionId: connection.id });
};
ws.onerror = (error) => {
connection.errorCount++;
this.log('error', `WebSocket ошибка: ${error}`);
};
}
setupTCPHandlers(connection, socket) {
socket.on('data', (data) => {
connection.messagesReceived++;
connection.bytesTransferred += data.length;
connection.lastActivity = new Date();
try {
const message = JSON.parse(data.toString());
this.handleIncomingMessage(connection, message);
}
catch (error) {
this.log('error', `Ошибка парсинга TCP сообщения: ${error}`);
}
});
socket.on('close', () => {
connection.status = NetworkStatus.DISCONNECTED;
this.emitEvent('connection_lost', { connectionId: connection.id });
});
socket.on('error', (error) => {
connection.errorCount++;
this.log('error', `TCP ошибка: ${error.message}`);
});
}
handleIncomingMessage(connection, message) {
// Обработка входящих сообщений
this.log('debug', `Получено сообщение от ${connection.id}: ${JSON.stringify(message)}`);
// Здесь можно добавить логику обработки различных типов сообщений
if (message.type === 'ping') {
// Отвечаем на ping
this.sendMessage(connection.id, {
connectionId: connection.id,
type: 'response',
operation: NetworkOperation.PING,
payload: { pong: true },
priority: 1,
requiresAck: false
});
}
}
async terminateConnection(connection) {
try {
connection.status = NetworkStatus.DISCONNECTED;
// Закрываем соединение в зависимости от типа
if (connection.socket) {
switch (connection.type) {
case ConnectionType.WEBSOCKET:
if (connection.socket.readyState === ws.WebSocket.OPEN) {
connection.socket.close();
}
break;
case ConnectionType.TCP:
if (connection.socket.destroy) {
connection.socket.destroy();
}
break;
default:
// Для HTTP соединений нет постоянного сокета
break;
}
}
connection.socket = undefined;
this.emitEvent('connection_lost', { connectionId: connection.id });
}
catch (error) {
console.error(`Ошибка закрытия соединения ${connection.id}:`, error);
}
}
async closeConnectionsByEndpoint(endpointId) {
const connectionsToClose = Array.from(this.connections.values())
.filter(conn => conn.endpointId === endpointId);
for (const connection of connectionsToClose) {
await this.closeConnection(connection.id);
}
}
async queueElementSync(element, operation) {
// Поиск подходящих конфигураций синхронизации
const applicableConfigs = Array.from(this.syncConfigs.values())
.filter(config => config.enabled && this.shouldSyncElement(element, config));
for (const config of applicableConfigs) {
const request = {
id: this.generateId(),
operation: NetworkOperation.SYNC,
endpointId: config.endpointId,
method: 'POST',
path: '/sync',
headers: { 'Content-Type': 'application/json' },
body: {
operation,
element: element.serialize(),
timestamp: new Date().toISOString()
},
timestamp: new Date(),
timeout: this.config.requestTimeout,
retryCount: 0,
priority: 1,
metadata: { configId: config.id, elementId: element.id }
};
this.requestQueue.push(request);
}
}
shouldSyncElement(element, config) {
// Проверка фильтров синхронизации
return config.filterRules.every(filter => {
const elementValue = this.getElementValue(element, filter.field);
const matches = this.evaluateFilter(elementValue, filter.operator, filter.value);
return filter.include ? matches : !matches;
});
}
getElementValue(element, field) {
if (field === 'id')
return element.id;
return element.getData(field);
}
evaluateFilter(actual, operator, expected) {
switch (operator) {
case 'equals': return actual === expected;
case 'not_equals': return actual !== expected;
case 'contains': return String(actual).includes(String(expected));
case 'matches': return new RegExp(expected).test(String(actual));
case 'greater': return actual > expected;
case 'less': return actual < expected;
default: return true;
}
}
async executeRequest(request) {
const startTime = Date.now();
this.stats.totalRequests++;
this.pendingRequests.set(request.id, request);
try {
const endpoint = this.endpoints.get(request.endpointId);
if (!endpoint) {
throw new Error(`Endpoint ${request.endpointId} не найден`);
}
const url = endpoint.url + request.path;
// Выполняем HTTP запрос с помощью axios
let axiosResponse;
if (typeof window !== 'undefined') {
// Браузерная среда - используем fetch
const fetchOptions = {
method: request.method,
headers: {
...endpoint.headers,
...request.headers
},
body: request.body ? JSON.stringify(request.body) : undefined,
signal: AbortSignal.timeout(request.timeout)
};
// Добавляем аутентификацию
if (endpoint.authentication) {
this.addAuthentication(fetchOptions, endpoint.authentication);
}
const fetchResponse = await fetch(url, fetchOptions);
const responseBody = await fetchResponse.text();
let parsedBody;
try {
parsedBody = JSON.parse(responseBody);
}
catch {
parsedBody = responseBody;
}
axiosResponse = {
status: fetchResponse.status,
statusText: fetchResponse.statusText,
headers: Object.fromEntries(fetchResponse.headers.entries()),
data: parsedBody
};
}
else {
// Node.js среда - используем axios
const axios = require('axios');
const axiosConfig = {
method: request.method.toLowerCase(),
url,
headers: {
...endpoint.headers,
...request.headers
},
data: request.body,
timeout: request.timeout,
validateStatus: () => true // Принимаем все статусы
};
// Добавляем аутентификацию
if (endpoint.authentication) {
this.addAxiosAuthentication(axiosConfig, endpoint.authentication);
}
axiosResponse = await axios(axiosConfig);
}
const response = {
requestId: request.id,
status: axiosResponse.status,
statusText: axiosResponse.statusText || '',
headers: axiosResponse.headers || {},
body: axiosResponse.data,
duration: Date.now() - startTime,
timestamp: new Date()
};
if (axiosResponse.status >= 200 && axiosResponse.status < 300) {
this.stats.successfulRequests++;
}
else {
this.stats.failedRequests++;
response.error = `HTTP ${axiosResponse.status}: ${axiosResponse.statusText}`;
}
this.stats.totalDataTransferred += JSON.stringify(response.body || '').length;
return response;
}
catch (error) {
this.stats.failedRequests++;
const response = {
requestId: request.id,
status: 500,
statusText: 'Internal Server Error',
headers: {},
body: null,
duration: Date.now() - startTime,
timestamp: new Date(),
error: error instanceof Error ? error.message : String(error)
};
return response;
}
finally {
this.pendingRequests.delete(request.id);
}
}
addAuthentication(options, auth) {
switch (auth.type) {
case 'basic': {
const basicAuth = btoa(`${auth.credentials.username}:${auth.credentials.password}`);
options.headers = {
...options.headers,
'Authorization': `Basic ${basicAuth}`
};
break;
}
case 'bearer':
options.headers = {
...options.headers,
'Authorization': `Bearer ${auth.credentials.token}`
};
break;
case 'apikey':
options.headers = {
...options.headers,
[auth.credentials.headerName || 'X-API-Key']: auth.credentials.apikey
};
break;
}
}
addAxiosAuthentication(config, auth) {
switch (auth.type) {
case 'basic':
config.auth = {
username: auth.credentials.username,
password: auth.credentials.password
};
break;
case 'bearer':
config.headers['Authorization'] = `Bearer ${auth.credentials.token}`;
break;
case 'apikey':
config.headers[auth.credentials.headerName || 'X-API-Key'] = auth.credentials.apikey;
break;
}
}
async queueOfflineRequest(request) {
if (this.offlineQueue.length >= this.config.offlineQueueSize) {
this.offlineQueue.shift(); // Удаляем самый старый запрос
}
this.offlineQueue.push(request);
this.stats.offlineOperations++;
// Возвращаем ответ о том, что запрос поставлен в очередь
return {
requestId: request.id,
status: 202,
statusText: 'Accepted (Queued for offline processing)',
headers: {},
body: { queued: true, position: this.offlineQueue.length },
duration: 0,
timestamp: new Date()
};
}
async getLocalData(config) {
try {
// Получаем локальные данные из CSElement registry
const elements = [];
// В реальной реализации здесь должен быть доступ к реестру элементов
// Для демонстрации создаем тестовые данные
for (let i = 0; i < 10; i++) {
elements.push({
id: `local_element_${i}`,
data: { value: i, syncConfig: config.id },
lastModified: new Date().toISOString()
});
}
return elements;
}
catch (error) {
console.error('Ошибка получения локальных данных:', error);
return [];
}
}
async getRemoteData(endpoint, config) {
try {
// Выполняем HTTP запрос для получения удаленных данных
const url = `${endpoint.url}/sync/${config.id}`;
const axiosConfig = {
method: 'GET',
url,
headers: endpoint.headers || {},
timeout: endpoint.timeout,
validateStatus: () => true
};
// Добавляем аутентификацию
if (endpoint.authentication) {
this.addAxiosAuthentication(axiosConfig, endpoint.authentication);
}
const response = await axios(axiosConfig);
if (response.status >= 200 && response.status < 300) {
return Array.isArray(response.data) ? response.data : [];
}
else {
console.warn(`Ошибка получения удаленных данных: HTTP ${response.status}`);
return [];
}
}
catch (error) {
console.error('Ошибка получения удаленных данных:', error);
return [];
}
}
async performFullSync(localData, remoteData, config, result) {
try {
// Создаем индексы для быстрого поиска
const localIndex = new Map(localData.map(item => [item.id, item]));
const remoteIndex = new Map(remoteData.map(item => [item.id, item]));
// Обрабатываем удаленные элементы
for (const remoteItem of remoteData) {
const localItem = localIndex.get(remoteItem.id);
if (!localItem) {
// Создаем новый элемент
console.log(`Создание элемента: ${remoteItem.id}`);
result.elementsCreated++;
}
else {
// Обновляем существующий элемент
const remoteTime = new Date(remoteItem.lastModified || 0);
const localTime = new Date(localItem.lastModified || 0);
if (remoteTime > localTime) {
console.log(`Обновление элемента: ${remoteItem.id}`);
result.elementsUpdated++;
}
}
result.elementsProcessed++;
}
// Обрабатываем локальные элементы, которых нет на удаленной стороне
for (const localItem of localData) {
if (!remoteIndex.has(localItem.id)) {
if (config.bidirectional) {
// Отправляем локальный элемент на сервер
console.log(`Отправка элемента на сервер: ${localItem.id}`);
result.elementsCreated++;
}
else {
// Удаляем локальный элемент
console.log(`Удаление локального элемента: ${localItem.id}`);
result.elementsDeleted++;
}
}
result.elementsProcessed++;
}
result.statistics.syncedElements = result.elementsProcessed;
}
catch (error) {
result.errors.push(`Ошибка полной синхронизации: ${error}`);
}
}
async performIncrementalSync(localData, remoteData, _config, result) {
// Заглушка для инкрементальной синхронизации
result.elementsProcessed = Math.min(localData.length, remoteData.length);
result.statistics.syncedElements = result.elementsProcessed;
}
async performConflictResolution(_localData, _remoteData, _config, result) {
// Заглушка для разрешения конфликтов
const conflicts = Math.floor(Math.random() * 5);
result.statistics.conflictedElements = conflicts;
this.stats.conflictsResolved += conflicts;
}
async transmitMessage(connection, message) {
// Заглушка для отправки сообщения
const messageSize = JSON.stringify(message).length;
connection.bytesTransferred += messageSize;
this.stats.totalDataTransferred += messageSize;
}
setupNetworkMonitoring() {
// Мониторинг состояния сети
setInterval(() => {
this.checkNetworkStatus();
}, 5000); // каждые 5 секунд
}
checkNetworkStatus() {
// Заглушка для проверки состояния сети
const wasOnline = this.isOnline;
this.isOnline = true; // navigator.onLine в браузере
if (!wasOnline && this.isOnline) {
this.processOfflineQueue();
}
}
async processOfflineQueue() {
this.log('info', `Обработка ${this.offlineQueue.length} отложенных запросов`);
const requests = [...this.offlineQueue];
this.offlineQueue = [];
for (const request of requests) {
try {
await this.executeRequest(request);
}
catch (error) {
this.log('error', `Ошибка обработки отложенного запроса: ${error}`);
}
}
}
startAutoSync() {
this.syncTimer = setInterval(() => {
this.runScheduledSyncs();
}, 60000); // проверка каждую минуту
}
async runScheduledSyncs() {
const now = new Date();
for (const config of this.syncConfigs.values()) {
if (!config.enabled || !config.nextSync)
continue;
if (now >= config.nextSync) {
try {
await this.syncData(config.id);
}
catch (error) {
this.log('error', `Ошибка автосинхронизации ${config.name}: ${error}`);
}
}
}
}
startHeartbeat() {
this.heartbeatTimer = setInterval(() => {
this.sendHeartbeats();
}, this.config.heartbeatInterval);
}
async sendHeartbeats() {
for (const connection of this.connections.values()) {
if (connection.status === NetworkStatus.CONNECTED) {
try {
await this.sendMessage(connection.id, {
connectionId: connection.id,
type: 'request',
operation: NetworkOperation.HEARTBEAT,
payload: { timestamp: new Date().toISOString() },
priority: 0,
requiresAck: false
});
}
catch (error) {
this.log('warn', `Ошибка heartbeat для соединения ${connection.id}: ${error}`);
}
}
}
}
startRequestProcessor() {
setInterval(() => {
this.processRequestQueue();
}, 1000); // каждую секунду
}
async processRequestQueue() {
if (this.requestQueue.length === 0)
return;
// Сортировка по приоритету
this.requestQueue.sort((a, b) => b.priority - a.priority);
const request = this.requestQueue.shift();
if (request) {
try {
await this.executeRequest(request);
}
catch (error) {
this.log('error', `Ошибка обработки запроса из очереди: ${error}`);
}
}
}
calculateNextSync(interval) {
return new Date(Date.now() + interval);
}
emitEvent(type, data) {
if (!this.config.enableEventLogging)
return;
const event = {
id: this.generateId(),
type,
data,
timestamp: new Date()
};
this.events.push(event);
// Ограничение размера лога событий
if (this.events.length > 1000) {
this.events = this.events.slice(-1000);
}
}
updateStats() {
this.stats.uptime = Date.now() - (this.stats.lastSyncTime?.getTime() || Date.now());
if (this.stats.totalRequests > 0) {
this.stats.errorRate = this.stats.failedRequests / this.stats.totalRequests;
}
// Обновление средней задержки
const activeConnections = Array.from(this.connections.values())
.filter(conn => conn.status === NetworkStatus.CONNECTED);
if (activeConnections.length > 0) {
this.stats.averageLatency = activeConnections
.reduce((sum, conn) => sum + conn.latency, 0) / activeConnections.length;
}
}
log(level, message) {
if (this.shouldLog(level)) {
console.log(`[${this.name}] ${level.toUpperCase()}: ${message}`);
}
}
shouldLog(level) {
const levels = ['debug', 'info', 'warn', 'error'];
const configLevel = levels.indexOf(this.config.logLevel);
const messageLevel = levels.indexOf(level);
return messageLevel >= configLevel;
}
generateId() {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
}
// Публичные методы для получения данных
getStats() {
return { ...this.stats };
}
getAllEndpoints() {
return Array.from(this.endpoints.values());
}
getAllSyncConfigs() {
return Array.from(this.syncConfigs.values());
}
getActiveConnections() {
return Array.from(this.connections.values())
.filter(conn => conn.status === NetworkStatus.CONNECTED);
}
getAllConnections() {
return Array.from(this.connections.values());
}
getPendingRequests() {
return Array.from(this.pendingRequests.values());
}
getRequestQueue() {
return [...this.requestQueue];
}
getOfflineQueue() {
return [...this.offlineQueue];
}
getSyncResults() {
return [...this.syncResults];
}
getEvents() {
return [...this.events];
}
isNetworkOnline() {
return this.isOnline;
}
getConnectionStatus(connectionId) {
return this.connections.get(connectionId)?.status;
}
}
exports.NetworkPlugin = NetworkPlugin;
//# sourceMappingURL=network.js.map