User: 
// HTTPS сервер
const httpsServer = https.createServer(serverOptions);

// Создайте WebSocket-сервер, используя существующий HTTPS сервер
const wss = new WebSocket.Server({ server: httpsServer });

// Запустите сервер
httpsServer.listen(3000, () => {
    console.log('HTTPS сервер и WebSocket сервер запущены на порту 3000');
});

// Создаем экземпляр Ajv (проверка входящей переписки на соответствие схеме)
const ajv = new Ajv(); 
// Объект для хранения действий и состояния каждого клиента
const clientActions = {};
// Объект для хранения ограничителей по каждому клиенту
const limiters = {}; 
// Домен
const allowedOrigin = 'https://gpt.loc'; 

wss.on('connection', (ws, req) => {
	//console.log('Новое соединение:', req.socket.remoteAddress);
	const urlParams = new URLSearchParams(req.url.split('?')[1]);
    const userId = urlParams.get('token'); // Получаем UUID из URL
    const userIP = req.socket.remoteAddress;
	//console.log("IP-адрес - ", userIP, "Время подключения - ", new Date().toISOString());
	const userHeaders = req.headers;
	
	// Проверяем Origin
    if (!checkOrigin(userHeaders.origin, ws)) {
        return; // Если отклонено, выходим из обработчика
    }

	// Если количество подключений с этого IP превышает лимит, закрываем соединение
    if (ipConnectionCount[userIP] >= MAX_CONNECTIONS_PER_IP) {
        console.log(`Превышен лимит подключений для IP: ${userIP}, отказано в соединении.`);
		stats.totalIPBlocks++;
        ws.close(); // Закрываем соединение
        return; // Выходим из обработчика 
    }
	
	// Увеличиваем счетчик подключений
    currentConnectedClients++;
	
	// Обновляем максимальное количество клиентов, если текущее количество больше 
	if (currentConnectedClients > stats.maxConnectedClients) {
        stats.maxConnectedClients = currentConnectedClients;
        console.log(`Максимальное количество подключенных клиентов: ${stats.maxConnectedClients}`);
    }
	
    // Увеличиваем счетчик подключений для этого IP
    ipConnectionCount[userIP] = (ipConnectionCount[userIP] || 0) + 1;
	
	// Увеличиваем общий счетчик подключений
    stats.totalConnections++;
	
	console.log('Client connected with user ID:', userId, ' and IP:', userIP);
	
	// Инициализируем состояние клиента, если его еще нет
    if (!clientActions[userId]) {
		clientActions[userId] = {
			actions: [], // Массив для хранения действий 
			connected: true, // Статус подключения
			lastRequestTime: new Date(), // Добавляем время последнего запроса 
			ws: ws,
			processing: false,
			delay: 1000,                // Задержка по умолчанию, например, 1000 мс
			responseTimes: []			// массив для хранения временных меток (начало и конец 3-х ответов)
        };
	}
	else {
        // обновляем ws при переподключении
        clientActions[userId].connected = true; 
        clientActions[userId].ws = ws;
        // вы можете обновить другие параметры здесь
    }
	
	if (!limiters[userId]) { 
	  // Создаем экземпляр Bottleneck для каждого клиента (ограничение на количество запросов по Id)
		limiters[userId] = new Bottleneck({
            maxConcurrent: 1, // Максимальное количество одновременных задач
            minTime: 1000, // Минимальное время между запросами в миллисекундах
		    highWater: 0 // Не разрешать в очереди больше 0 запросов (игнорировать)
        });
    }
	
    ws.on('message', (message) => {console.log(clientActions[userId].processing);
		// Обновляем время последнего запроса
		clientActions[userId].lastRequestTime = new Date(); 
		
		// Проверяем, является ли сообщение экземпляром Buffer
		if (Buffer.isBuffer(message))
			message = message.toString(); 
		try {
			// Преобразуем строку JSON обратно в объект
			message = JSON.parse(message); 
			//Получаем историю чата из запроса
			let messages = message.messages;
			let model = message.model;
			let rewrite = message.rewrite;
			
			// Выполняем валидацию сообщений
			messages = validateMessages(messages);
			
			// Проверяем, остались ли сообщения после валидации
			if (messages.length === 0) {
				const response = { code: 2, message: "Нет валидных сообщений для обработки." };
				sendMessage(ws, response);
				return;
			}
			
			// Урезаем последнее сообщение до 2000 символов, если оно от клиента
			if (messages[messages.length - 1].role === 'user') {
				messages[messages.length - 1].content = messages[messages.length - 1].content.substring(0, 2000000);
			}
			
			// Увеличиваем общий счетчик запросов 
            stats.totalRequests++;
			
			// Проверяем не обрабатывается ли в данный момент запрос клиента
			if(clientActions[userId].processing){
				console.log('Обрабатывается предыдущий запрос.');
				const response = { code: 2, message: "Сервер занят. Пожалуйста, попробуйте позже." };
				// Отправка ответа клиенту
				setTimeout(() => {
					sendMessage(ws, response);
				}, 5 * 1000);
				return;
			}
			
			
			
			// Задержка перед отправкой запроса
			const responseTimes = clientActions[userId].responseTimes; // Получаем массив responseTimes

			// Проверяем, прошло ли больше 15 секунд с момента окончания последнего ответа
			if (responseTimes.length === 3) {
				const lastEndTime = responseTimes[responseTimes.length - 1].end;
				const currentTime = new Date();

				// Вычисляем разницу во времени
				const timeSinceLastEnd = currentTime - lastEndTime; // Время в миллисекундах

				if (timeSinceLastEnd > 20000) { // Если прошло больше 15 секунд
					clientActions[userId].delay = 1000; // Устанавливаем delay на 1 мс
				}
			}
			console.log('Задержка - ', clientActions[userId].delay)
			// Используем Bottleneck для управления количеством запросов
			limiters[userId].schedule(() => {
				clientActions[userId].processing = true;
				// Отправка запроса на сервер чата с задержкой
				setTimeout(() => {
					call(messages, model, ws, userId, rewrite);
				}, clientActions[userId].delay);
			}).catch(error => {
				stats.totalIPBlocks++;
				//console.error('Rate limit exceeded, message ignored:', error);
				//const response = { code: 2, message: "Превышен лимит запросов. Пожалуйста, попробуйте позже." };
				// Отправка ответа клиенту
				//sendMessage(ws, response);
			});
			
		} catch (error) {
				stats.totalWsMessErrors++;
				console.error('Error parsing JSON:', error);
				const response = { code: 2, message: "Произошла ошибка. Попробуйте еще раз."};
				console.log(response);
				// Отправка ответа клиенту
				sendMessage(ws, response);
		}
		
    });

    ws.on('close', () => {
		try {
			// Проверка, существует ли userId в clientActions
			if (clientActions[userId]) {
				clientActions[userId].connected = false; // Обновление статуса подключения
				//delete clientActions[userId]; // Удаление состояния клиента
			}
			// Проверка, существует ли userId в limiters
			if (limiters[userId]) {
				//delete limiters[userId]; // Удаление ограничителя для данного клиента
			}
			console.log('Client ',userId, ' disconnected'); 
			// Уменьшаем счетчик подключений для этого IP		
			if (ipConnectionCount[userIP] !== undefined) {
				ipConnectionCount[userIP]--;
				
				// Если счетчик равен 0, удаляем запись из объекта
				if (ipConnectionCount[userIP] === 0) {
					delete ipConnectionCount[userIP];
				}
			}
			// Уменьшаем счетчик при отключении клиента
			currentConnectedClients = Math.max(currentConnectedClients - 1, 0); 
		} catch (error) {
			console.error('Error handling client disconnect:', error);
		}
    });
});

// Запускаем планировщик для статистики
scheduleDailyReset();

// Таймер для проверки клиентов каждые 10 минут 
setInterval(() => {
    const currentTime = new Date();
    for (const userId in clientActions) {
        const client = clientActions[userId];
        const lastRequestTime = client.lastRequestTime;
		
        // Проверяем, прошло ли более 30 минут с последнего сообщения
        if (lastRequestTime && (currentTime - lastRequestTime) > 30 * 60 * 1000) {
            const response = { code: 3, message: "" };
            // Отправляем сообщение клиенту 
            if (client.ws) {
                sendMessage(client.ws, response);
                // Закрываем соединение
                client.ws.close(); 
            }
			// Удаляем записи о клиенте, если нет активности более 30 минут
            delete clientActions[userId]; 
            delete limiters[userId];
        }
    }
	console.log('Количество активных клиентов - ', Object.keys(clientActions).length);
}, 5 * 60 * 1000);

Нужно дополнить setInterval условием
сейчас так
 if (lastRequestTime && (currentTime - lastRequestTime) > 30 * 60 * 1000) {
надо чтобы соблюдалось либо это условие либо последнее сообщение более минуты назад и пользователь отключен в данный момент (clientActions[userId].connected == false)
Assistant:  дополнить блок `setInterval` таким образом, чтобы условие выполнялось либо для последнего запроса, который был более 30 минут назад, либо для случая, когда пользователь отключен и его последнее сообщение было более минуты назад, вам нужно изменить условие внутри цикла.

Вот как это можно сделать:

```javascript
setInterval(() => {
    const currentTime = new Date();
    for (const userId in clientActions) {
        const client = clientActions[userId];
        const lastRequestTime = client.lastRequestTime;

        // Проверяем, прошло ли более 30 минут с последнего сообщения
        const fifteenMinutesInactive = lastRequestTime && (currentTime - lastRequestTime) > 30 * 60 * 1000;
        
        // Проверка на отключение и прошло ли более минуты с последнего сообщения
        const userDisconnectedAndInactive = !client.connected && (currentTime - lastRequestTime) > 1 * 60 * 1000;

        if (fifteenMinutesInactive || userDisconnectedAndInactive) {
            const response = { code: 3, message: "" };
            // Отправляем сообщение клиенту 
            if (client.ws) {
                sendMessage(client.ws, response);
                // Закрываем соединение
                client.ws.close(); 
            }
            // Удаляем записи о клиенте, если нет активности более 30 минут или пользователь отключен более минуты
            delete clientActions[userId]; 
            delete limiters[userId];
        }
    }
    console.log('Количество активных клиентов - ', Object.keys(clientActions).length);
}, 5 * 60 * 1000);
```

В этом коде добавлено новое условие `userDisconnectedAndInactive`, которое проверяет, является ли клиент отключенным и прошло ли более 1 минуты с момента его последнего сообщения. Текущее общее условие теперь использует логическое "ИЛИ" (||), что означает, что если выполняется хотя бы одно из условий, будет выполнен блок, который отправляет сообщение клиенту и закрывает соединение.
User: замени fifteenMinutesInactive на longTimeInactive
Assistant: ечно! Вы можете заменить переменную `fifteenMinutesInactive` на `longTimeInactive` в коде. Вот обновленный вариант:

```javascript
setInterval(() => {
    const currentTime = new Date();
    for (const userId in clientActions) {
        const client = clientActions[userId];
        const lastRequestTime = client.lastRequestTime;

        // Проверяем, прошло ли более 30 минут с последнего сообщения
        const longTimeInactive = lastRequestTime && (currentTime - lastRequestTime) > 30 * 60 * 1000;
        
        // Проверка на отключение и прошло ли более минуты с последнего сообщения
        const userDisconnectedAndInactive = !client.connected && (currentTime - lastRequestTime) > 1 * 60 * 1000;

        if (longTimeInactive || userDisconnectedAndInactive) {
            const response = { code: 3, message: "" };
            // Отправляем сообщение клиенту 
            if (client.ws) {
                sendMessage(client.ws, response);
                // Закрываем соединение
                client.ws.close(); 
            }
            // Удаляем записи о клиенте, если нет активности более 30 минут или пользователь отключен более минуты
            delete clientActions[userId]; 
            delete limiters[userId];
        }
    }
    console.log('Количество активных клиентов - ', Object.keys(clientActions).length);
}, 5 * 60 * 1000);
```

Теперь переменная, проверяющая длительную неактивность, называется `longTimeInactive`, и все ссылки на неё в условии обновлены соответственно.
User: if (client.ws)
здесь должно проверяться открыто ли вебсокет соединение. Правильно проверяется?
Assistant:  данном случае, проверка `if (client.ws)` лишь удостоверяется в том, что объект `ws` (WebSocket) существует и не равен `null` или `undefined`, но это не гарантирует, что WebSocket-соединение действительно открыто. 

Чтобы проверить, открыто ли соединение WebSocket, вам нужно использовать свойство `readyState` у объекта `ws`. Вебсокеты могут находиться в одном из следующих состояний:

- `0` (CONNECTING) - соединение устанавливается.
- `1` (OPEN) - соединение установлено и готово к обмену данными.
- `2` (CLOSING) - соединение закрывается.
- `3` (CLOSED) - соединение закрыто.

Для проверки, открыто ли соединение, вам нужно убедиться, что `client.ws.readyState === WebSocket.OPEN`. Вот так это должно выглядеть:

```javascript
// Отправляем сообщение клиенту 
if (client.ws && client.ws.readyState === WebSocket.OPEN) {
    sendMessage(client.ws, response);
    // Закрываем соединение
    client.ws.close(); 
}
```

С учетом этого, строка проверки в вашем коде должна выглядеть так:

```javascript
if (client.ws && client.ws.readyState === WebSocket.OPEN) {
    sendMessage(client.ws, response);
    // Закрываем соединение
    client.ws.close(); 
}
```

Это обеспечит более корректное реагирование на попытки отправить сообщение через закрытое или закрывающееся WebSocket-соединение.
User: client.ws.close(); в итоге закрывает это соединение?
Assistant: